Design Prot Merge #1
+10
-4
@@ -15,10 +15,16 @@ artifacts/
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# Secrets / local
|
# Environment files (deploy/.env, deploy/.env.prod, web-player/.env, ...)
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
*.env
|
*.env
|
||||||
|
!.env.example
|
||||||
|
!.env.prod.example
|
||||||
|
|
||||||
# Production env and mounted secrets
|
# Deploy secrets and runtime state
|
||||||
.env.prod
|
deploy/keys/
|
||||||
*.env.*
|
|
||||||
deploy/secrets/
|
deploy/secrets/
|
||||||
|
deploy/nginx/certs/
|
||||||
|
deploy/zitadel/.output/
|
||||||
|
deploy/.tmp/
|
||||||
|
|||||||
+44
-23
@@ -1,40 +1,61 @@
|
|||||||
# Dev stack config (deploy/docker-compose.yml).
|
# Development configuration for deploy/compose.yml + compose.dev.yml.
|
||||||
# Copy to deploy/.env and adjust. .env is not versioned (.gitignore ignores *.env).
|
# Copy to deploy/.env (up.ps1 does it for you) and adjust. Not versioned.
|
||||||
|
|
||||||
# PostgreSQL (shared by Musify and Zitadel). Local apps use localhost:59000.
|
DOTNET_ENVIRONMENT=Development
|
||||||
|
|
||||||
|
# --- Public URLs -------------------------------------------------------------
|
||||||
|
# How each service is reached from *outside* Docker. `host.docker.internal`
|
||||||
|
# resolves to the same address from the browser, from apps running on the host
|
||||||
|
# and from inside the containers, which is what makes the OIDC issuer and the
|
||||||
|
# presigned S3 URLs valid in both run modes.
|
||||||
|
PUBLIC_WEB_URL=http://localhost:3000
|
||||||
|
PUBLIC_API_URL=http://localhost:5111
|
||||||
|
PUBLIC_STREAM_URL=http://localhost:8081
|
||||||
|
PUBLIC_AUTH_URL=http://host.docker.internal:8080
|
||||||
|
|
||||||
|
# --- PostgreSQL (shared by Musify and Zitadel) -------------------------------
|
||||||
POSTGRES_USER=postgres
|
POSTGRES_USER=postgres
|
||||||
POSTGRES_PASSWORD=postgres
|
POSTGRES_PASSWORD=postgres
|
||||||
POSTGRES_DB=musify_db
|
POSTGRES_DB=musify_db
|
||||||
POSTGRES_HOST_PORT=59000
|
POSTGRES_HOST_PORT=59000
|
||||||
|
|
||||||
# pgAdmin (Postgres UI). http://localhost:5050
|
# --- Zitadel (OIDC) ----------------------------------------------------------
|
||||||
PGADMIN_DEFAULT_EMAIL=admin@musify.dev
|
# The masterkey MUST be exactly 32 characters. The external domain/port form
|
||||||
PGADMIN_DEFAULT_PASSWORD=admin
|
# the token issuer, so they have to match PUBLIC_AUTH_URL. Changing them needs
|
||||||
PGADMIN_HOST_PORT=5050
|
# a re-init: ./up.ps1 -Destroy; ./up.ps1
|
||||||
|
|
||||||
# RabbitMQ (MassTransit)
|
|
||||||
RABBITMQ_DEFAULT_USER=guest
|
|
||||||
RABBITMQ_DEFAULT_PASS=guest
|
|
||||||
|
|
||||||
# Zitadel (OIDC). The masterkey MUST be exactly 32 characters.
|
|
||||||
ZITADEL_MASTERKEY=MasterkeyNeedsToHave32Characters
|
ZITADEL_MASTERKEY=MasterkeyNeedsToHave32Characters
|
||||||
# The external domain/port forms the token issuer and must match everywhere.
|
|
||||||
# host.docker.internal resolves the same from browser, local apps and containers,
|
|
||||||
# so auth works both locally and full-docker. Changing it needs a re-init
|
|
||||||
# (up.ps1 -Destroy; up.ps1). Admin login: <user>@zitadel.<domain>.
|
|
||||||
ZITADEL_EXTERNAL_DOMAIN=host.docker.internal
|
ZITADEL_EXTERNAL_DOMAIN=host.docker.internal
|
||||||
ZITADEL_EXTERNAL_PORT=8080
|
ZITADEL_EXTERNAL_PORT=8080
|
||||||
|
ZITADEL_EXTERNAL_SECURE=false
|
||||||
|
ZITADEL_DATABASE_PASSWORD=zitadel
|
||||||
|
# Console login: <username>@zitadel.<external domain>
|
||||||
ZITADEL_ADMIN_USERNAME=admin
|
ZITADEL_ADMIN_USERNAME=admin
|
||||||
ZITADEL_ADMIN_PASSWORD=Password1!
|
ZITADEL_ADMIN_PASSWORD=Password1!
|
||||||
|
|
||||||
# SeaweedFS (S3). Must match deploy/seaweedfs/s3.conf.
|
# OIDC clients, filled in by deploy/zitadel/provision.ps1.
|
||||||
|
AUTH_CLIENT_ID=
|
||||||
|
WEB_CLIENT_ID=
|
||||||
|
WEB_CLIENT_SECRET=
|
||||||
|
|
||||||
|
# --- RabbitMQ (MassTransit) --------------------------------------------------
|
||||||
|
RABBITMQ_DEFAULT_USER=guest
|
||||||
|
RABBITMQ_DEFAULT_PASS=guest
|
||||||
|
|
||||||
|
# --- SeaweedFS (S3) ----------------------------------------------------------
|
||||||
S3_ACCESS_KEY=admin_access_key
|
S3_ACCESS_KEY=admin_access_key
|
||||||
S3_SECRET_KEY=admin_secret_key
|
S3_SECRET_KEY=admin_secret_key
|
||||||
S3_BUCKET=webapi-storage
|
S3_BUCKET=webapi-storage
|
||||||
|
S3_PUBLIC_URL=http://host.docker.internal:8333
|
||||||
|
S3_USE_HTTP=true
|
||||||
|
|
||||||
# Host paths used by up.ps1 and the apps overlay.
|
# --- Web player --------------------------------------------------------------
|
||||||
STREAM_KEYS_DIR=D:/weed
|
# Key material for the encrypted session cookie: any long random string.
|
||||||
WORKER_TEMP_DIR=D:/tempsFilesDev
|
SESSION_SECRET=dev-session-secret-change-me-0123456789abcdef
|
||||||
|
# Allows the plain-HTTP Zitadel issuer in a production Node build (dev only).
|
||||||
|
AUTH_ALLOW_INSECURE_HTTP=true
|
||||||
|
|
||||||
# OIDC ClientId, filled in by zitadel/provision.ps1 (used by the docker path).
|
# --- Misc --------------------------------------------------------------------
|
||||||
AUTH_CLIENT_ID=
|
AUTH_REQUIRE_HTTPS=false
|
||||||
|
PGADMIN_DEFAULT_EMAIL=admin@musify.dev
|
||||||
|
PGADMIN_DEFAULT_PASSWORD=admin
|
||||||
|
PGADMIN_HOST_PORT=5050
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Production configuration for deploy/compose.yml + compose.prod.yml.
|
||||||
|
# Copy to deploy/.env.prod (up.sh does it for you), then:
|
||||||
|
#
|
||||||
|
# 1. replace every `example.com` with your domain,
|
||||||
|
# 2. replace every CHANGE_ME with a strong random value
|
||||||
|
# (`openssl rand -hex 24`; the Zitadel masterkey needs exactly 32 chars),
|
||||||
|
# 3. point these DNS records at the server, all proxied/TLS-terminated the
|
||||||
|
# same way: example.com, www, api, auth, stream, s3.
|
||||||
|
#
|
||||||
|
# Not versioned. Keep it readable only by the deploy user (chmod 600).
|
||||||
|
|
||||||
|
DOTNET_ENVIRONMENT=Production
|
||||||
|
|
||||||
|
# --- Domain ------------------------------------------------------------------
|
||||||
|
# ROOT_DOMAIN drives the nginx vhosts; the URLs below must agree with it.
|
||||||
|
ROOT_DOMAIN=example.com
|
||||||
|
PUBLIC_WEB_URL=https://example.com
|
||||||
|
PUBLIC_API_URL=https://api.example.com
|
||||||
|
PUBLIC_STREAM_URL=https://stream.example.com
|
||||||
|
PUBLIC_AUTH_URL=https://auth.example.com
|
||||||
|
S3_PUBLIC_URL=https://s3.example.com
|
||||||
|
|
||||||
|
# --- PostgreSQL (shared by Musify and Zitadel) -------------------------------
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_PASSWORD=CHANGE_ME
|
||||||
|
POSTGRES_DB=musify_db
|
||||||
|
|
||||||
|
# --- Zitadel (OIDC) ----------------------------------------------------------
|
||||||
|
# The masterkey MUST be exactly 32 characters and can never be rotated in place.
|
||||||
|
ZITADEL_MASTERKEY=CHANGE_ME_32_CHARACTERS_EXACTLY_
|
||||||
|
ZITADEL_EXTERNAL_DOMAIN=auth.example.com
|
||||||
|
ZITADEL_EXTERNAL_PORT=443
|
||||||
|
ZITADEL_EXTERNAL_SECURE=true
|
||||||
|
ZITADEL_DATABASE_PASSWORD=CHANGE_ME
|
||||||
|
# Console login: <username>@zitadel.<external domain>
|
||||||
|
ZITADEL_ADMIN_USERNAME=admin
|
||||||
|
ZITADEL_ADMIN_PASSWORD=CHANGE_ME
|
||||||
|
|
||||||
|
# OIDC clients, filled in by deploy/zitadel/provision.sh.
|
||||||
|
AUTH_CLIENT_ID=
|
||||||
|
WEB_CLIENT_ID=
|
||||||
|
WEB_CLIENT_SECRET=
|
||||||
|
|
||||||
|
# --- RabbitMQ (MassTransit) --------------------------------------------------
|
||||||
|
RABBITMQ_DEFAULT_USER=musify
|
||||||
|
RABBITMQ_DEFAULT_PASS=CHANGE_ME
|
||||||
|
|
||||||
|
# --- SeaweedFS (S3) ----------------------------------------------------------
|
||||||
|
S3_ACCESS_KEY=CHANGE_ME
|
||||||
|
S3_SECRET_KEY=CHANGE_ME
|
||||||
|
S3_BUCKET=webapi-storage
|
||||||
|
S3_USE_HTTP=false
|
||||||
|
|
||||||
|
# --- Web player --------------------------------------------------------------
|
||||||
|
# Key material for the encrypted session cookie. Rotating it logs everyone out.
|
||||||
|
SESSION_SECRET=CHANGE_ME
|
||||||
|
AUTH_ALLOW_INSECURE_HTTP=false
|
||||||
|
|
||||||
|
# --- Misc --------------------------------------------------------------------
|
||||||
|
AUTH_REQUIRE_HTTPS=true
|
||||||
|
# Largest request body nginx accepts (audio uploads go through the web player).
|
||||||
|
MAX_UPLOAD_SIZE=1024m
|
||||||
|
# Extra yt-dlp flags for the Worker. The cookie jar avoids YouTube's bot checks;
|
||||||
|
# drop it here if you do not have one: deploy/secrets/ is mounted at /secrets.
|
||||||
|
YTDLP_ADDITIONAL_ARGUMENTS=--cookies /secrets/youtube_cookies.txt --extractor-retries 1 --socket-timeout 15 -N 4
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
name: musify-dev
|
||||||
|
|
||||||
|
# Development overlay: publishes the infrastructure ports to the host so the
|
||||||
|
# .NET apps and the web player can run from the IDE, and adds pgAdmin.
|
||||||
|
#
|
||||||
|
# docker compose -f deploy/compose.yml -f deploy/compose.dev.yml up -d
|
||||||
|
# docker compose -f deploy/compose.yml -f deploy/compose.dev.yml --profile apps up -d --build
|
||||||
|
#
|
||||||
|
# `host.docker.internal` (see ZITADEL_EXTERNAL_DOMAIN / *_PUBLIC_URL in .env)
|
||||||
|
# resolves to the same address from the browser, from apps on the host and from
|
||||||
|
# inside the containers, so the OIDC issuer and the presigned S3 URLs are valid
|
||||||
|
# in both modes. `extra_hosts` gives Linux containers that same name.
|
||||||
|
|
||||||
|
x-host-alias: &host-alias
|
||||||
|
- "${ZITADEL_EXTERNAL_DOMAIN}:host-gateway"
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
ports:
|
||||||
|
- "${POSTGRES_HOST_PORT}:5432"
|
||||||
|
|
||||||
|
zitadel:
|
||||||
|
ports:
|
||||||
|
- "${ZITADEL_EXTERNAL_PORT}:8080"
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
ports:
|
||||||
|
- "5672:5672" # AMQP -> MassTransit
|
||||||
|
- "15672:15672" # management UI
|
||||||
|
|
||||||
|
seaweedfs:
|
||||||
|
ports:
|
||||||
|
- "9333:9333" # master / admin UI
|
||||||
|
- "8888:8888" # filer -> StreamingGateway
|
||||||
|
- "8333:8333" # S3 API -> API, Worker and presigned uploads
|
||||||
|
|
||||||
|
jaeger:
|
||||||
|
ports:
|
||||||
|
- "16686:16686" # UI
|
||||||
|
- "4317:4317" # OTLP gRPC
|
||||||
|
- "4318:4318" # OTLP HTTP
|
||||||
|
|
||||||
|
# Optional Postgres UI: docker compose ... --profile tools up -d
|
||||||
|
pgadmin:
|
||||||
|
profiles: [tools]
|
||||||
|
image: dpage/pgadmin4:latest
|
||||||
|
container_name: musify-pgadmin
|
||||||
|
environment:
|
||||||
|
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
|
||||||
|
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
|
||||||
|
PGADMIN_CONFIG_SERVER_MODE: "False"
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- "${PGADMIN_HOST_PORT}:80"
|
||||||
|
volumes:
|
||||||
|
- pgadmin_data:/var/lib/pgadmin
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
api:
|
||||||
|
ports:
|
||||||
|
- "5111:5111"
|
||||||
|
extra_hosts: *host-alias
|
||||||
|
|
||||||
|
worker:
|
||||||
|
extra_hosts: *host-alias
|
||||||
|
|
||||||
|
gateway:
|
||||||
|
ports:
|
||||||
|
- "8081:8081"
|
||||||
|
extra_hosts: *host-alias
|
||||||
|
|
||||||
|
web-player:
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
extra_hosts: *host-alias
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgadmin_data:
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
name: musify-prod
|
||||||
|
|
||||||
|
# Production overlay: adds the nginx edge and the host-mounted secrets.
|
||||||
|
# Nothing but nginx publishes a port; every other service is reachable only on
|
||||||
|
# the internal `musify-prod_default` network.
|
||||||
|
#
|
||||||
|
# docker compose --env-file deploy/.env.prod \
|
||||||
|
# -f deploy/compose.yml -f deploy/compose.prod.yml --profile apps up -d --build
|
||||||
|
#
|
||||||
|
# Use ./up.sh, which also generates the stream keys, provisions Zitadel and
|
||||||
|
# applies the database migrations.
|
||||||
|
|
||||||
|
services:
|
||||||
|
worker:
|
||||||
|
# yt-dlp reads the YouTube cookie jar from here (see YTDLP_ADDITIONAL_ARGUMENTS).
|
||||||
|
volumes:
|
||||||
|
- ./secrets:/secrets:ro
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:1.27-alpine
|
||||||
|
container_name: musify-nginx
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
- gateway
|
||||||
|
- zitadel
|
||||||
|
- web-player
|
||||||
|
environment:
|
||||||
|
# The entrypoint renders /etc/nginx/templates/*.template with envsubst.
|
||||||
|
# The filter keeps it from eating nginx's own $host / $remote_addr.
|
||||||
|
MUSIFY_ROOT_DOMAIN: ${ROOT_DOMAIN}
|
||||||
|
MUSIFY_MAX_UPLOAD_SIZE: ${MAX_UPLOAD_SIZE:-1024m}
|
||||||
|
NGINX_ENVSUBST_FILTER: ^MUSIFY_
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./nginx/templates:/etc/nginx/templates:ro
|
||||||
|
- ./nginx/snippets:/etc/nginx/snippets:ro
|
||||||
|
- ./nginx/certs:/etc/nginx/certs:ro
|
||||||
|
restart: unless-stopped
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
# Musify — base stack, shared by development and production.
|
||||||
|
#
|
||||||
|
# This file publishes no host ports and hardcodes no domain: everything that
|
||||||
|
# differs between environments comes from the .env file (see .env.example) or
|
||||||
|
# from the overlay that is merged on top of it.
|
||||||
|
#
|
||||||
|
# dev docker compose -f deploy/compose.yml -f deploy/compose.dev.yml up -d
|
||||||
|
# prod docker compose --env-file deploy/.env.prod -f deploy/compose.yml -f deploy/compose.prod.yml up -d
|
||||||
|
#
|
||||||
|
# The four application services sit behind the `apps` profile, so a plain `up`
|
||||||
|
# starts only the infrastructure (the usual dev setup: apps from the IDE).
|
||||||
|
# Add `--profile apps` to run them in Docker. Use ./up.ps1 (dev) or ./up.sh
|
||||||
|
# (prod) instead of calling compose by hand.
|
||||||
|
|
||||||
|
x-app-depends: &app-depends
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
rabbitmq:
|
||||||
|
condition: service_healthy
|
||||||
|
seaweedfs:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
# Backend settings that are identical for the API and the Worker. ASP.NET Core
|
||||||
|
# maps `__` to `:`, so these override the matching AppSettings.json sections.
|
||||||
|
x-backend-env: &backend-env
|
||||||
|
Database__ConnectionString: "Host=postgres;Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
|
||||||
|
MassTransit__Address: "rabbitmq://rabbitmq:5672"
|
||||||
|
MassTransit__Username: ${RABBITMQ_DEFAULT_USER}
|
||||||
|
MassTransit__Password: ${RABBITMQ_DEFAULT_PASS}
|
||||||
|
# The public S3 URL, not the internal one: the same address is used to sign
|
||||||
|
# the presigned upload/download URLs the browser has to be able to reach.
|
||||||
|
InfrastructureStorage__Address: ${S3_PUBLIC_URL}
|
||||||
|
InfrastructureStorage__AccessKey: ${S3_ACCESS_KEY}
|
||||||
|
InfrastructureStorage__SecretAccessKey: ${S3_SECRET_KEY}
|
||||||
|
InfrastructureStorage__ForcePathStyle: "true"
|
||||||
|
InfrastructureStorage__UseHttp: ${S3_USE_HTTP}
|
||||||
|
ApplicationStorage__Bucket: ${S3_BUCKET}
|
||||||
|
OpenTelemetry__OtlpEndpoint: "http://jaeger:4317"
|
||||||
|
|
||||||
|
services:
|
||||||
|
# --- Infrastructure -------------------------------------------------------
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
container_name: musify-postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# OIDC provider. `start-from-init` runs init + setup (both idempotent) and
|
||||||
|
# then starts, so the same command works on a cold and on a warm volume.
|
||||||
|
# It reuses the Postgres above: the admin user creates the `zitadel` database
|
||||||
|
# and role on first start.
|
||||||
|
zitadel:
|
||||||
|
image: ghcr.io/zitadel/zitadel:latest
|
||||||
|
container_name: musify-zitadel
|
||||||
|
command: start-from-init --masterkeyFromEnv --tlsMode disabled
|
||||||
|
environment:
|
||||||
|
ZITADEL_MASTERKEY: ${ZITADEL_MASTERKEY}
|
||||||
|
# The external domain/port forms the token issuer and must be identical
|
||||||
|
# for the browser, the apps and the containers (Zitadel routes by Host).
|
||||||
|
ZITADEL_EXTERNALSECURE: ${ZITADEL_EXTERNAL_SECURE}
|
||||||
|
ZITADEL_EXTERNALDOMAIN: ${ZITADEL_EXTERNAL_DOMAIN}
|
||||||
|
ZITADEL_EXTERNALPORT: ${ZITADEL_EXTERNAL_PORT}
|
||||||
|
ZITADEL_TLS_ENABLED: "false"
|
||||||
|
ZITADEL_DATABASE_POSTGRES_HOST: postgres
|
||||||
|
ZITADEL_DATABASE_POSTGRES_PORT: 5432
|
||||||
|
ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
|
||||||
|
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel
|
||||||
|
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: ${ZITADEL_DATABASE_PASSWORD}
|
||||||
|
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
||||||
|
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: ${POSTGRES_USER}
|
||||||
|
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
||||||
|
# Initial console admin. Login: <username>@zitadel.<external domain>
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_USERNAME: ${ZITADEL_ADMIN_USERNAME}
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD: ${ZITADEL_ADMIN_PASSWORD}
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: "false"
|
||||||
|
# Service account + PAT written on init; the provisioning script uses it
|
||||||
|
# to create the Musify project and its OIDC apps.
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME: zitadel-admin-sa
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME: Admin Service Account
|
||||||
|
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE: "2099-01-01T00:00:00Z"
|
||||||
|
ZITADEL_FIRSTINSTANCE_PATPATH: /output/admin-sa.pat
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
volumes:
|
||||||
|
- ./zitadel/.output:/output
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
image: rabbitmq:4-management-alpine
|
||||||
|
container_name: musify-rabbitmq
|
||||||
|
environment:
|
||||||
|
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
|
||||||
|
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
|
||||||
|
volumes:
|
||||||
|
- rabbitmq_data:/var/lib/rabbitmq
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# All-in-one object store: master + volume + filer + S3 gateway.
|
||||||
|
# The S3 identity file is rendered from the environment on start so the
|
||||||
|
# credentials can never drift from the ones handed to the apps.
|
||||||
|
seaweedfs:
|
||||||
|
image: chrislusf/seaweedfs:latest
|
||||||
|
container_name: musify-seaweedfs
|
||||||
|
entrypoint: ["/bin/sh", "-c"]
|
||||||
|
command:
|
||||||
|
- |
|
||||||
|
cat > /tmp/s3.conf <<'JSON'
|
||||||
|
{
|
||||||
|
"identities": [
|
||||||
|
{
|
||||||
|
"name": "musify",
|
||||||
|
"credentials": [{ "accessKey": "${S3_ACCESS_KEY}", "secretKey": "${S3_SECRET_KEY}" }],
|
||||||
|
"actions": ["Admin", "Read", "Write"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
JSON
|
||||||
|
exec weed server \
|
||||||
|
-ip.bind=0.0.0.0 \
|
||||||
|
-dir=/data \
|
||||||
|
-master.volumeSizeLimitMB=1024 \
|
||||||
|
-volume.max=0 \
|
||||||
|
-filer \
|
||||||
|
-s3 \
|
||||||
|
-s3.config=/tmp/s3.conf
|
||||||
|
volumes:
|
||||||
|
- seaweedfs_data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:9333/cluster/healthz"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# One-shot: SeaweedFS does not auto-create buckets.
|
||||||
|
seaweedfs-init:
|
||||||
|
image: amazon/aws-cli:latest
|
||||||
|
container_name: musify-seaweedfs-init
|
||||||
|
depends_on:
|
||||||
|
seaweedfs:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY}
|
||||||
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
|
entrypoint: ["/bin/sh", "-c"]
|
||||||
|
command:
|
||||||
|
- |
|
||||||
|
aws --endpoint-url http://seaweedfs:8333 s3 mb s3://${S3_BUCKET} 2>/dev/null || true
|
||||||
|
aws --endpoint-url http://seaweedfs:8333 s3 ls
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
# OTLP collector + trace UI (MassTransit / EF Core / HTTP spans).
|
||||||
|
jaeger:
|
||||||
|
image: jaegertracing/all-in-one:latest
|
||||||
|
container_name: musify-jaeger
|
||||||
|
environment:
|
||||||
|
COLLECTOR_OTLP_ENABLED: "true"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# --- Applications (profile: apps) -----------------------------------------
|
||||||
|
|
||||||
|
api:
|
||||||
|
profiles: [apps]
|
||||||
|
build:
|
||||||
|
context: ../backend
|
||||||
|
dockerfile: Musify.Api.Dockerfile
|
||||||
|
container_name: musify-api
|
||||||
|
depends_on: *app-depends
|
||||||
|
environment:
|
||||||
|
<<: *backend-env
|
||||||
|
ASPNETCORE_ENVIRONMENT: ${DOTNET_ENVIRONMENT}
|
||||||
|
ASPNETCORE_URLS: http://+:5111
|
||||||
|
StreamGateway__PublicBaseUrl: ${PUBLIC_STREAM_URL}
|
||||||
|
StreamTicket__PrivateKeyPath: /keys/stream_private.pem
|
||||||
|
Workers__Routes__TemporaryFilesDirectory: /tmp/musify
|
||||||
|
# Client id and secret are written to the .env file by the Zitadel
|
||||||
|
# provisioning script (deploy/zitadel/provision.*).
|
||||||
|
Authentication__MetadataAddress: "${PUBLIC_AUTH_URL}/.well-known/openid-configuration"
|
||||||
|
Authentication__IssuerAddress: ${PUBLIC_AUTH_URL}
|
||||||
|
Authentication__ClientId: ${AUTH_CLIENT_ID}
|
||||||
|
Authentication__AudienceAddress: ${AUTH_CLIENT_ID}
|
||||||
|
Authentication__AuthorizationEndpoint: "${PUBLIC_AUTH_URL}/oauth/v2/authorize?prompt=login"
|
||||||
|
Authentication__TokenEndpoint: "${PUBLIC_AUTH_URL}/oauth/v2/token"
|
||||||
|
Authentication__ScalarRedirectUri: "${PUBLIC_API_URL}/scalar/"
|
||||||
|
Authentication__Scopes__0: openid
|
||||||
|
Authentication__Scopes__1: profile
|
||||||
|
Authentication__Scopes__2: email
|
||||||
|
Authentication__RequireHttpsMetadata: ${AUTH_REQUIRE_HTTPS}
|
||||||
|
volumes:
|
||||||
|
- ./keys:/keys:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
worker:
|
||||||
|
profiles: [apps]
|
||||||
|
build:
|
||||||
|
context: ../backend
|
||||||
|
dockerfile: Musify.Worker.Dockerfile
|
||||||
|
container_name: musify-worker
|
||||||
|
depends_on: *app-depends
|
||||||
|
environment:
|
||||||
|
<<: *backend-env
|
||||||
|
DOTNET_ENVIRONMENT: ${DOTNET_ENVIRONMENT}
|
||||||
|
AudioTranscoder__Routes__WorkingDirectory: /tmp/musify
|
||||||
|
Workers__Routes__TemporaryFilesDirectory: /tmp/musify
|
||||||
|
YtDlp__AdditionalArguments: ${YTDLP_ADDITIONAL_ARGUMENTS:-}
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# YARP in front of the SeaweedFS filer: validates the RS256 stream ticket and
|
||||||
|
# streams the bytes, so audio never goes through the API.
|
||||||
|
gateway:
|
||||||
|
profiles: [apps]
|
||||||
|
build:
|
||||||
|
context: ../backend
|
||||||
|
dockerfile: Musify.StreamingGateway.Dockerfile
|
||||||
|
container_name: musify-gateway
|
||||||
|
depends_on:
|
||||||
|
seaweedfs:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
ASPNETCORE_ENVIRONMENT: ${DOTNET_ENVIRONMENT}
|
||||||
|
ASPNETCORE_URLS: http://+:8081
|
||||||
|
StreamTicket__PublicKeyPath: /keys/stream_public.pem
|
||||||
|
ReverseProxy__Clusters__seaweed-filer__Destinations__filer__Address: "http://seaweedfs:8888/"
|
||||||
|
ReverseProxy__Routes__media__Transforms__0__PathPattern: "/buckets/${S3_BUCKET}/{**rest}"
|
||||||
|
Cors__AllowedOrigins__0: ${PUBLIC_WEB_URL}
|
||||||
|
volumes:
|
||||||
|
- ./keys:/keys:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
web-player:
|
||||||
|
profiles: [apps]
|
||||||
|
build:
|
||||||
|
context: ../web-player
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: musify-web-player
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
PORT: 3000
|
||||||
|
ORIGIN: ${PUBLIC_WEB_URL}
|
||||||
|
ZITADEL_ISSUER: ${PUBLIC_AUTH_URL}
|
||||||
|
ZITADEL_CLIENT_ID: ${WEB_CLIENT_ID}
|
||||||
|
ZITADEL_CLIENT_SECRET: ${WEB_CLIENT_SECRET}
|
||||||
|
AUTH_REDIRECT_URI: "${PUBLIC_WEB_URL}/auth/callback"
|
||||||
|
AUTH_POST_LOGOUT_URI: "${PUBLIC_WEB_URL}/"
|
||||||
|
AUTH_ALLOW_INSECURE_HTTP: ${AUTH_ALLOW_INSECURE_HTTP}
|
||||||
|
SESSION_SECRET: ${SESSION_SECRET}
|
||||||
|
API_BASE_URL: http://api:5111
|
||||||
|
# Presigned PUTs are signed for the public S3 host; the container sends
|
||||||
|
# them straight to SeaweedFS instead of going back out through the edge.
|
||||||
|
S3_UPLOAD_ENDPOINT: http://seaweedfs:8333
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
rabbitmq_data:
|
||||||
|
seaweedfs_data:
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
name: musify-dev
|
|
||||||
|
|
||||||
# Optional overlay: runs the .NET apps in Docker. Merges with docker-compose.yml.
|
|
||||||
#
|
|
||||||
# docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.apps.yml up -d --build
|
|
||||||
#
|
|
||||||
# For active development prefer infra in Docker + apps from the IDE (easier to
|
|
||||||
# debug). Auth works in both modes: ZITADEL_EXTERNAL_DOMAIN=host.docker.internal
|
|
||||||
# resolves the same from the browser, local apps and containers, so the token
|
|
||||||
# issuer matches everywhere (containers reach the host via extra_hosts).
|
|
||||||
|
|
||||||
x-backend-depends: &backend-depends
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
rabbitmq:
|
|
||||||
condition: service_healthy
|
|
||||||
seaweedfs:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
x-backend-env: &backend-env
|
|
||||||
Database__ConnectionString: "Host=postgres;Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
|
|
||||||
MassTransit__Address: "rabbitmq://rabbitmq:5672"
|
|
||||||
MassTransit__Username: ${RABBITMQ_DEFAULT_USER}
|
|
||||||
MassTransit__Password: ${RABBITMQ_DEFAULT_PASS}
|
|
||||||
InfrastructureStorage__Address: "http://seaweedfs:8333"
|
|
||||||
InfrastructureStorage__AccessKey: ${S3_ACCESS_KEY}
|
|
||||||
InfrastructureStorage__SecretAccessKey: ${S3_SECRET_KEY}
|
|
||||||
InfrastructureStorage__ForcePathStyle: "true"
|
|
||||||
InfrastructureStorage__UseHttp: "true"
|
|
||||||
ApplicationStorage__Bucket: ${S3_BUCKET}
|
|
||||||
OpenTelemetry__OtlpEndpoint: "http://jaeger:4317"
|
|
||||||
|
|
||||||
services:
|
|
||||||
api:
|
|
||||||
build:
|
|
||||||
context: ../backend
|
|
||||||
dockerfile: Musify.Api.Dockerfile
|
|
||||||
container_name: musify-api
|
|
||||||
depends_on: *backend-depends
|
|
||||||
environment:
|
|
||||||
<<: *backend-env
|
|
||||||
ASPNETCORE_ENVIRONMENT: Development
|
|
||||||
ASPNETCORE_URLS: http://+:5111
|
|
||||||
StreamGateway__PublicBaseUrl: "http://localhost:8081"
|
|
||||||
StreamTicket__PrivateKeyPath: "/keys/stream_private.pem"
|
|
||||||
# Auth values provisioned by zitadel/provision.ps1 (AUTH_CLIENT_ID in .env).
|
|
||||||
Authentication__MetadataAddress: "http://${ZITADEL_EXTERNAL_DOMAIN}:${ZITADEL_EXTERNAL_PORT}/.well-known/openid-configuration"
|
|
||||||
Authentication__IssuerAddress: "http://${ZITADEL_EXTERNAL_DOMAIN}:${ZITADEL_EXTERNAL_PORT}"
|
|
||||||
Authentication__ClientId: "${AUTH_CLIENT_ID}"
|
|
||||||
Authentication__AudienceAddress: "${AUTH_CLIENT_ID}"
|
|
||||||
Authentication__AuthorizationEndpoint: "http://${ZITADEL_EXTERNAL_DOMAIN}:${ZITADEL_EXTERNAL_PORT}/oauth/v2/authorize?prompt=login"
|
|
||||||
Authentication__TokenEndpoint: "http://${ZITADEL_EXTERNAL_DOMAIN}:${ZITADEL_EXTERNAL_PORT}/oauth/v2/token"
|
|
||||||
Authentication__ScalarRedirectUri: "http://localhost:5111/scalar/"
|
|
||||||
Authentication__Scopes__0: "openid"
|
|
||||||
Authentication__Scopes__1: "profile"
|
|
||||||
Authentication__Scopes__2: "email"
|
|
||||||
Authentication__RequireHttpsMetadata: "false"
|
|
||||||
extra_hosts:
|
|
||||||
- "${ZITADEL_EXTERNAL_DOMAIN}:host-gateway"
|
|
||||||
volumes:
|
|
||||||
- "${STREAM_KEYS_DIR:-D:/weed}:/keys:ro"
|
|
||||||
ports:
|
|
||||||
- "5111:5111"
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
worker:
|
|
||||||
build:
|
|
||||||
context: ../backend
|
|
||||||
dockerfile: Musify.Worker.Dockerfile
|
|
||||||
container_name: musify-worker
|
|
||||||
depends_on: *backend-depends
|
|
||||||
environment:
|
|
||||||
<<: *backend-env
|
|
||||||
DOTNET_ENVIRONMENT: Development
|
|
||||||
AudioTranscoder__Routes__WorkingDirectory: "/tmp/musify"
|
|
||||||
Workers__Routes__TemporaryFilesDirectory: "/tmp/musify"
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
gateway:
|
|
||||||
build:
|
|
||||||
context: ../backend
|
|
||||||
dockerfile: Musify.StreamingGateway.Dockerfile
|
|
||||||
container_name: musify-gateway
|
|
||||||
depends_on:
|
|
||||||
seaweedfs:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
ASPNETCORE_ENVIRONMENT: Development
|
|
||||||
ASPNETCORE_URLS: http://+:8081
|
|
||||||
StreamTicket__PublicKeyPath: "/keys/stream_public.pem"
|
|
||||||
ReverseProxy__Clusters__seaweed-filer__Destinations__filer__Address: "http://seaweedfs:8888/"
|
|
||||||
volumes:
|
|
||||||
- "${STREAM_KEYS_DIR:-D:/weed}:/keys:ro"
|
|
||||||
ports:
|
|
||||||
- "8081:8081"
|
|
||||||
restart: unless-stopped
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
name: musify-prod
|
|
||||||
|
|
||||||
# Production stack for the VPS (ikerdemo.cat). Everything runs on the internal
|
|
||||||
# `musify-prod_default` network; only nginx publishes ports to the host (80/443).
|
|
||||||
# Config in deploy/.env.prod.
|
|
||||||
#
|
|
||||||
# docker compose --env-file deploy/.env.prod -f deploy/docker-compose.prod.yml up -d --build
|
|
||||||
|
|
||||||
x-backend-depends: &backend-depends
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
rabbitmq:
|
|
||||||
condition: service_healthy
|
|
||||||
seaweedfs:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
x-backend-env: &backend-env
|
|
||||||
Database__ConnectionString: "Host=postgres;Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
|
|
||||||
MassTransit__Address: "rabbitmq://rabbitmq:5672"
|
|
||||||
MassTransit__Username: ${RABBITMQ_DEFAULT_USER}
|
|
||||||
MassTransit__Password: ${RABBITMQ_DEFAULT_PASS}
|
|
||||||
InfrastructureStorage__Address: "https://s3.ikerdemo.cat"
|
|
||||||
InfrastructureStorage__AccessKey: ${S3_ACCESS_KEY}
|
|
||||||
InfrastructureStorage__SecretAccessKey: ${S3_SECRET_KEY}
|
|
||||||
InfrastructureStorage__ForcePathStyle: "true"
|
|
||||||
InfrastructureStorage__UseHttp: "false"
|
|
||||||
ApplicationStorage__Bucket: ${S3_BUCKET}
|
|
||||||
OpenTelemetry__OtlpEndpoint: "http://jaeger:4317"
|
|
||||||
|
|
||||||
x-zitadel-db-env: &zitadel-db-env
|
|
||||||
ZITADEL_DATABASE_POSTGRES_HOST: postgres
|
|
||||||
ZITADEL_DATABASE_POSTGRES_PORT: 5432
|
|
||||||
ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
|
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel
|
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel
|
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: ${POSTGRES_USER}
|
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: ${POSTGRES_PASSWORD}
|
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
|
||||||
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres:17-alpine
|
|
||||||
container_name: musify-postgres
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
zitadel-init:
|
|
||||||
image: ghcr.io/zitadel/zitadel:latest
|
|
||||||
container_name: musify-zitadel-init
|
|
||||||
command: 'init'
|
|
||||||
environment:
|
|
||||||
<<: *zitadel-db-env
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
restart: "no"
|
|
||||||
|
|
||||||
zitadel-setup:
|
|
||||||
image: ghcr.io/zitadel/zitadel:latest
|
|
||||||
container_name: musify-zitadel-setup
|
|
||||||
command: 'setup --masterkeyFromEnv --tlsMode disabled'
|
|
||||||
environment:
|
|
||||||
<<: *zitadel-db-env
|
|
||||||
ZITADEL_MASTERKEY: ${ZITADEL_MASTERKEY}
|
|
||||||
ZITADEL_EXTERNALSECURE: "true"
|
|
||||||
ZITADEL_EXTERNALDOMAIN: ${ZITADEL_EXTERNAL_DOMAIN}
|
|
||||||
ZITADEL_EXTERNALPORT: ${ZITADEL_EXTERNAL_PORT}
|
|
||||||
ZITADEL_TLS_ENABLED: "false"
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_USERNAME: ${ZITADEL_ADMIN_USERNAME}
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD: ${ZITADEL_ADMIN_PASSWORD}
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: "false"
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME: zitadel-admin-sa
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME: Admin Service Account
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE: "2099-01-01T00:00:00Z"
|
|
||||||
ZITADEL_FIRSTINSTANCE_PATPATH: /output/admin-sa.pat
|
|
||||||
depends_on:
|
|
||||||
zitadel-init:
|
|
||||||
condition: service_completed_successfully
|
|
||||||
volumes:
|
|
||||||
- ./zitadel/.output:/output
|
|
||||||
restart: "no"
|
|
||||||
|
|
||||||
zitadel:
|
|
||||||
image: ghcr.io/zitadel/zitadel:latest
|
|
||||||
container_name: musify-zitadel
|
|
||||||
command: 'start --masterkeyFromEnv --tlsMode disabled'
|
|
||||||
environment:
|
|
||||||
<<: *zitadel-db-env
|
|
||||||
ZITADEL_MASTERKEY: ${ZITADEL_MASTERKEY}
|
|
||||||
ZITADEL_EXTERNALSECURE: "true"
|
|
||||||
ZITADEL_EXTERNALDOMAIN: ${ZITADEL_EXTERNAL_DOMAIN}
|
|
||||||
ZITADEL_EXTERNALPORT: ${ZITADEL_EXTERNAL_PORT}
|
|
||||||
ZITADEL_TLS_ENABLED: "false"
|
|
||||||
depends_on:
|
|
||||||
zitadel-setup:
|
|
||||||
condition: service_completed_successfully
|
|
||||||
volumes:
|
|
||||||
- ./zitadel/.output:/output
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
rabbitmq:
|
|
||||||
image: rabbitmq:4-management-alpine
|
|
||||||
container_name: musify-rabbitmq
|
|
||||||
environment:
|
|
||||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
|
|
||||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
|
|
||||||
volumes:
|
|
||||||
- rabbitmq_data:/var/lib/rabbitmq
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
seaweedfs:
|
|
||||||
image: chrislusf/seaweedfs:latest
|
|
||||||
container_name: musify-seaweedfs
|
|
||||||
command: >
|
|
||||||
server
|
|
||||||
-ip.bind=0.0.0.0
|
|
||||||
-dir=/data
|
|
||||||
-master.volumeSizeLimitMB=1024
|
|
||||||
-volume.max=0
|
|
||||||
-filer
|
|
||||||
-s3
|
|
||||||
-s3.config=/etc/seaweedfs/s3.conf
|
|
||||||
volumes:
|
|
||||||
- seaweedfs_data:/data
|
|
||||||
- ./seaweedfs/s3.conf:/etc/seaweedfs/s3.conf:ro
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:9333/cluster/healthz"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
seaweedfs-init:
|
|
||||||
image: amazon/aws-cli:latest
|
|
||||||
container_name: musify-seaweedfs-init
|
|
||||||
depends_on:
|
|
||||||
seaweedfs:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY}
|
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
|
||||||
entrypoint: ["/bin/sh", "-c"]
|
|
||||||
command:
|
|
||||||
- |
|
|
||||||
aws --endpoint-url http://seaweedfs:8333 s3 mb s3://${S3_BUCKET} 2>/dev/null || true
|
|
||||||
aws --endpoint-url http://seaweedfs:8333 s3 ls
|
|
||||||
restart: "no"
|
|
||||||
|
|
||||||
jaeger:
|
|
||||||
image: jaegertracing/all-in-one:latest
|
|
||||||
container_name: musify-jaeger
|
|
||||||
environment:
|
|
||||||
COLLECTOR_OTLP_ENABLED: "true"
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
api:
|
|
||||||
build:
|
|
||||||
context: ../backend
|
|
||||||
dockerfile: Musify.Api.Dockerfile
|
|
||||||
container_name: musify-api
|
|
||||||
depends_on: *backend-depends
|
|
||||||
environment:
|
|
||||||
<<: *backend-env
|
|
||||||
ASPNETCORE_ENVIRONMENT: Production
|
|
||||||
ASPNETCORE_URLS: http://+:5111
|
|
||||||
StreamGateway__PublicBaseUrl: "https://${STREAM_DOMAIN}"
|
|
||||||
StreamTicket__PrivateKeyPath: "/keys/stream_private.pem"
|
|
||||||
Authentication__MetadataAddress: "https://${AUTH_DOMAIN}/.well-known/openid-configuration"
|
|
||||||
Authentication__IssuerAddress: "https://${AUTH_DOMAIN}"
|
|
||||||
Authentication__ClientId: "${AUTH_CLIENT_ID}"
|
|
||||||
Authentication__AudienceAddress: "${AUTH_CLIENT_ID}"
|
|
||||||
Authentication__AuthorizationEndpoint: "https://${AUTH_DOMAIN}/oauth/v2/authorize?prompt=login"
|
|
||||||
Authentication__TokenEndpoint: "https://${AUTH_DOMAIN}/oauth/v2/token"
|
|
||||||
Authentication__ScalarRedirectUri: "https://${API_DOMAIN}/scalar/"
|
|
||||||
Authentication__Scopes__0: "openid"
|
|
||||||
Authentication__Scopes__1: "profile"
|
|
||||||
Authentication__Scopes__2: "email"
|
|
||||||
Authentication__RequireHttpsMetadata: "true"
|
|
||||||
YtDlp__AdditionalArguments: "--cookies /secrets/youtube_cookies.txt --extractor-retries 1 --socket-timeout 15"
|
|
||||||
volumes:
|
|
||||||
- ./keys:/keys:ro
|
|
||||||
- ./secrets/youtube_cookies.txt:/secrets/youtube_cookies.txt
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
worker:
|
|
||||||
build:
|
|
||||||
context: ../backend
|
|
||||||
dockerfile: Musify.Worker.Dockerfile
|
|
||||||
container_name: musify-worker
|
|
||||||
depends_on: *backend-depends
|
|
||||||
environment:
|
|
||||||
<<: *backend-env
|
|
||||||
DOTNET_ENVIRONMENT: Production
|
|
||||||
AudioTranscoder__Routes__WorkingDirectory: "/tmp/musify"
|
|
||||||
Workers__Routes__TemporaryFilesDirectory: "/tmp/musify"
|
|
||||||
YtDlp__AdditionalArguments: "--cookies /secrets/youtube_cookies.txt --extractor-retries 1 --socket-timeout 15 -N 4"
|
|
||||||
volumes:
|
|
||||||
- ./secrets/youtube_cookies.txt:/secrets/youtube_cookies.txt
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
gateway:
|
|
||||||
build:
|
|
||||||
context: ../backend
|
|
||||||
dockerfile: Musify.StreamingGateway.Dockerfile
|
|
||||||
container_name: musify-gateway
|
|
||||||
depends_on:
|
|
||||||
seaweedfs:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
ASPNETCORE_ENVIRONMENT: Production
|
|
||||||
ASPNETCORE_URLS: http://+:8081
|
|
||||||
StreamTicket__PublicKeyPath: "/keys/stream_public.pem"
|
|
||||||
ReverseProxy__Clusters__seaweed-filer__Destinations__filer__Address: "http://seaweedfs:8888/"
|
|
||||||
volumes:
|
|
||||||
- ./keys:/keys:ro
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
web-player:
|
|
||||||
build:
|
|
||||||
context: ../web-player
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
container_name: musify-web-player
|
|
||||||
depends_on:
|
|
||||||
- api
|
|
||||||
environment:
|
|
||||||
NODE_ENV: production
|
|
||||||
ORIGIN: "https://${WEB_DOMAIN}"
|
|
||||||
ZITADEL_ISSUER: "https://${AUTH_DOMAIN}"
|
|
||||||
ZITADEL_CLIENT_ID: "${WEB_CLIENT_ID}"
|
|
||||||
ZITADEL_CLIENT_SECRET: "${WEB_CLIENT_SECRET}"
|
|
||||||
AUTH_REDIRECT_URI: "https://${WEB_DOMAIN}/auth/callback"
|
|
||||||
AUTH_POST_LOGOUT_URI: "https://${WEB_DOMAIN}/"
|
|
||||||
SESSION_SECRET: "${SESSION_SECRET}"
|
|
||||||
API_BASE_URL: "http://api:5111"
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
nginx:
|
|
||||||
image: nginx:1.27-alpine
|
|
||||||
container_name: musify-nginx
|
|
||||||
depends_on:
|
|
||||||
- api
|
|
||||||
- gateway
|
|
||||||
- zitadel
|
|
||||||
- web-player
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
volumes:
|
|
||||||
- ./nginx/conf.d:/etc/nginx/conf.d:ro
|
|
||||||
- ./nginx/certs:/etc/nginx/certs:ro
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
rabbitmq_data:
|
|
||||||
seaweedfs_data:
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
name: musify-dev
|
|
||||||
|
|
||||||
# Infrastructure stack for Musify development: everything the backend needs
|
|
||||||
# (Postgres, Zitadel, RabbitMQ, SeaweedFS + bucket, Jaeger/OTLP). The .NET apps
|
|
||||||
# run from the IDE and connect over the published ports. Config in deploy/.env
|
|
||||||
#
|
|
||||||
# docker compose -f deploy/docker-compose.yml up -d
|
|
||||||
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres:17-alpine
|
|
||||||
container_name: musify-postgres
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
|
||||||
ports:
|
|
||||||
- "${POSTGRES_HOST_PORT}:5432"
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
pgadmin:
|
|
||||||
image: dpage/pgadmin4:latest
|
|
||||||
container_name: musify-pgadmin
|
|
||||||
environment:
|
|
||||||
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
|
|
||||||
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
|
|
||||||
PGADMIN_CONFIG_SERVER_MODE: "False"
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
|
||||||
- "${PGADMIN_HOST_PORT}:80"
|
|
||||||
volumes:
|
|
||||||
- pgadmin_data:/var/lib/pgadmin
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
zitadel:
|
|
||||||
image: ghcr.io/zitadel/zitadel:latest
|
|
||||||
container_name: musify-zitadel
|
|
||||||
command: 'start-from-init --masterkeyFromEnv --tlsMode disabled'
|
|
||||||
environment:
|
|
||||||
ZITADEL_MASTERKEY: ${ZITADEL_MASTERKEY}
|
|
||||||
ZITADEL_EXTERNALSECURE: "false"
|
|
||||||
ZITADEL_EXTERNALDOMAIN: ${ZITADEL_EXTERNAL_DOMAIN}
|
|
||||||
ZITADEL_EXTERNALPORT: ${ZITADEL_EXTERNAL_PORT}
|
|
||||||
ZITADEL_TLS_ENABLED: "false"
|
|
||||||
# Reuses the same Postgres: the admin user creates the 'zitadel' database
|
|
||||||
# and the 'zitadel' user on first start.
|
|
||||||
ZITADEL_DATABASE_POSTGRES_HOST: postgres
|
|
||||||
ZITADEL_DATABASE_POSTGRES_PORT: 5432
|
|
||||||
ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
|
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel
|
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel
|
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: ${POSTGRES_USER}
|
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: ${POSTGRES_PASSWORD}
|
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
|
||||||
# Initial console admin. Login: <user>@zitadel.<domain>
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_USERNAME: ${ZITADEL_ADMIN_USERNAME}
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD: ${ZITADEL_ADMIN_PASSWORD}
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: "false"
|
|
||||||
# Service account + PAT for automatic provisioning. Zitadel writes the PAT
|
|
||||||
# to PATPATH on init; provision.ps1 uses it to create the project and app.
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME: zitadel-admin-sa
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME: Admin Service Account
|
|
||||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE: "2099-01-01T00:00:00Z"
|
|
||||||
ZITADEL_FIRSTINSTANCE_PATPATH: /output/admin-sa.pat
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
|
||||||
- "${ZITADEL_EXTERNAL_PORT}:8080"
|
|
||||||
volumes:
|
|
||||||
- ./zitadel/.output:/output
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
rabbitmq:
|
|
||||||
image: rabbitmq:4-management-alpine
|
|
||||||
container_name: musify-rabbitmq
|
|
||||||
environment:
|
|
||||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
|
|
||||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
|
|
||||||
ports:
|
|
||||||
- "5672:5672" # AMQP -> MassTransit (API + Worker)
|
|
||||||
- "15672:15672" # management UI
|
|
||||||
volumes:
|
|
||||||
- rabbitmq_data:/var/lib/rabbitmq
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
seaweedfs:
|
|
||||||
image: chrislusf/seaweedfs:latest
|
|
||||||
container_name: musify-seaweedfs
|
|
||||||
# All-in-one dev server: master + volume + filer + S3 gateway.
|
|
||||||
command: >
|
|
||||||
server
|
|
||||||
-ip.bind=0.0.0.0
|
|
||||||
-dir=/data
|
|
||||||
-master.volumeSizeLimitMB=1024
|
|
||||||
-volume.max=0
|
|
||||||
-filer
|
|
||||||
-s3
|
|
||||||
-s3.config=/etc/seaweedfs/s3.conf
|
|
||||||
ports:
|
|
||||||
- "9333:9333" # master (admin/UI)
|
|
||||||
- "8888:8888" # filer HTTP -> StreamingGateway
|
|
||||||
- "8333:8333" # S3 API -> WebApi/Worker (InfrastructureStorage:Address)
|
|
||||||
volumes:
|
|
||||||
- seaweedfs_data:/data
|
|
||||||
- ./seaweedfs/s3.conf:/etc/seaweedfs/s3.conf:ro
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:9333/cluster/healthz"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
# One-shot: creates the app bucket in SeaweedFS (it is not auto-created).
|
|
||||||
seaweedfs-init:
|
|
||||||
image: amazon/aws-cli:latest
|
|
||||||
container_name: musify-seaweedfs-init
|
|
||||||
depends_on:
|
|
||||||
seaweedfs:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY}
|
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
|
||||||
entrypoint: ["/bin/sh", "-c"]
|
|
||||||
command:
|
|
||||||
- |
|
|
||||||
aws --endpoint-url http://seaweedfs:8333 s3 mb s3://${S3_BUCKET} 2>/dev/null || true
|
|
||||||
aws --endpoint-url http://seaweedfs:8333 s3 ls
|
|
||||||
restart: "no"
|
|
||||||
|
|
||||||
# OTLP collector + trace UI (MassTransit/EF/HTTP spans). OtlpEndpoint = :4317
|
|
||||||
jaeger:
|
|
||||||
image: jaegertracing/all-in-one:latest
|
|
||||||
container_name: musify-jaeger
|
|
||||||
environment:
|
|
||||||
COLLECTOR_OTLP_ENABLED: "true"
|
|
||||||
ports:
|
|
||||||
- "16686:16686" # UI
|
|
||||||
- "4317:4317" # OTLP gRPC
|
|
||||||
- "4318:4318" # OTLP HTTP
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
pgadmin_data:
|
|
||||||
rabbitmq_data:
|
|
||||||
seaweedfs_data:
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"identities": [
|
|
||||||
{
|
|
||||||
"name": "admin_user",
|
|
||||||
"credentials": [
|
|
||||||
{
|
|
||||||
"accessKey": "admin_access_key",
|
|
||||||
"secretKey": "admin_secret_key"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"actions": ["Admin", "Read", "Write"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "read_only_user",
|
|
||||||
"credentials": [
|
|
||||||
{
|
|
||||||
"accessKey": "readonly_access_key",
|
|
||||||
"secretKey": "readonly_secret_key"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"actions": ["Read"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# Secrets Zitadel writes on init (do not version)
|
|
||||||
.output/
|
|
||||||
Reference in New Issue
Block a user