Files
Musify/deploy/.env.prod.example
T
Admin cf8ac706b6 Replace deploy scripts with self-contained compose bootstrap jobs
Removes every wrapper script under deploy/ (up.ps1, up.sh, scripts/*,
zitadel/provision.*) and folds their logic into three one-shot services
in compose.yml, following the pattern already used by seaweedfs-init:

  keys-init     generates the RS256 stream-ticket key pair
  migrate       applies the EF Core migrations (backend/Musify.Migrator.Dockerfile)
  zitadel-init  creates the Musify project + OIDC apps, writes the client
                ids back into the active env file

Deploying is now two plain `docker compose` commands, no scripts to run
or chmod:

  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

(swap compose.dev.yml for compose.prod.yml + --env-file deploy/.env.prod
for production). The first command brings up infra and lets the bootstrap
jobs run to completion; the second builds and starts the apps, which by
then read real OIDC values instead of empty ones — compose interpolates
${VAR} once per invocation, so this can't collapse into a single command
without the apps reading their own config file at container start instead
of via compose env (out of scope here).

Also:
- DatabaseDesignTimeFactory now also reads Database__ConnectionString
  from the environment, and DesignSettings.json is optional, so the new
  migrate container works without user-secrets or a config file excluded
  from the Docker build context by .dockerignore.
- nginx's depends_on no longer lists the apps-profile services: it does
  not need them at its own startup (every vhost resolves its upstream at
  request time via the embedded Docker resolver), and requiring them made
  `compose.prod.yml up -d` (without --profile apps) an invalid project.
- env_get/env_set in zitadel-init strip \r: the mounted env file can have
  CRLF line endings on Windows, which otherwise makes an empty-looking
  value non-empty to `[ -z ... ]`. .env.example/.env.prod.example are
  pinned to LF via .gitattributes for the same reason. Also fixed:
  rewriting the bind-mounted env file in place instead of renaming a temp
  file over it (rename onto a bind-mounted inode fails with "Resource
  busy").
- Removed compose.share.yml and zitadel/.output-share/ — a "temporary"
  personal overlay that had leaked a real Zitadel service-account PAT
  into git history (committed in 70968b3). That token should be rotated.
- Removed deploy/.tmp/ from .gitignore: nothing mounts or creates it
  anymore (it never actually did, either — no compose service referenced
  it, only the now-removed up.ps1 did).

Verified end-to-end with Docker: a clean `down -v` + fresh `up -d` +
`--profile apps up -d --build` on the dev stack brings up all four one-shot
jobs successfully and all four apps healthy (api/gateway return 200,
worker starts its consumers with no errors, web-player responds). Also
verified compose.prod.yml starts nginx cleanly with dummy certs and no
apps running, confirming the depends_on fix.
2026-09-09 15:08:34 +02:00

71 lines
2.8 KiB
Bash

# Production configuration for deploy/compose.yml + compose.prod.yml.
# Copy to deploy/.env.prod, 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
# Keep OIDC apps strict about https redirect URIs in production.
ZITADEL_DEV_MODE=false
# OIDC clients: the zitadel-init one-shot service (see compose.yml) fills
# these in automatically on `docker compose up -d`.
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
# Disk nginx gives to the cached YouTube artwork it serves for the web player.
THUMBNAIL_CACHE_SIZE=2g
# 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