services: db: image: postgres:16-alpine restart: always environment: POSTGRES_USER: user POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: muzick ports: - "127.0.0.1:5432:5432" volumes: - ./data/postgres:/var/lib/postgresql/data - ./backend/src/db/schema.sql:/docker-entrypoint-initdb.d/schema.sql search: image: typesense/typesense:0.25.1 restart: always ports: - "127.0.0.1:8108:8108" volumes: - ./data/typesense:/data command: --data-dir /data --api-key=${TYPESENSE_API_KEY} backend: build: ./backend restart: unless-stopped ports: - "127.0.0.1:3000:3000" environment: DATABASE_URL: postgresql://user:${DB_PASSWORD}@db:5432/muzick REDIS_URL: redis://:${REDIS_PASSWORD}@infra-redis:6379 TYPESENSE_API_KEY: ${TYPESENSE_API_KEY} MUZICK_API_KEY: ${MUZICK_API_KEY} MUZICK_ADMIN_KEY: ${MUZICK_ADMIN_KEY} MUSIC_DIR: /music volumes: # READ-ONLY, deliberately. Nothing in the API request path may write to # the library. Hard deletion of disliked files happens only in the worker, # which is the sole service with an rw mount. - /mnt/hdd1/media/Music:/music:ro depends_on: - db - search networks: - default - infra-net frontend: build: ./frontend restart: unless-stopped ports: - "127.0.0.1:5174:80" environment: MUZICK_API_KEY: ${MUZICK_API_KEY} MUZICK_ADMIN_KEY: ${MUZICK_ADMIN_KEY} depends_on: - backend worker: build: context: ./workers # Enabled deliberately: the auto-seed discovery loop cannot acquire a # candidate without the downloader present. args: INSTALL_YTDLP: "true" restart: unless-stopped network_mode: host environment: DATABASE_URL: postgresql://user:${DB_PASSWORD}@127.0.0.1:5432/muzick REDIS_URL: redis://:${REDIS_PASSWORD}@127.0.0.1:6379 MUSICBRAINZ_CONTACT: ${MUSICBRAINZ_CONTACT} LASTFM_API_KEY: ${LASTFM_API_KEY} LASTFM_SHARED_SECRET: ${LASTFM_SHARED_SECRET} DISCOGS_TOKEN: ${DISCOGS_TOKEN} SOCKS_PROXY_URL: ${SOCKS_PROXY_URL} MUSIC_DIR: /music # System E acquisition, enabled. The allow-list is the only thing deciding # where audio may come from, and it binds search-resolved URLs too. # `www.youtube.com` is what yt-dlp reports as webpage_url for a ytsearch # hit. Narrow this list to shrink the blast radius; empty it to stop # acquisition without a rebuild. MUZICK_ACQUISITION_ENABLED: "true" MUZICK_ACQUISITION_YTDLP_PATH: /usr/bin/yt-dlp MUZICK_ACQUISITION_ALLOWED_HOSTS: www.youtube.com MUZICK_ACQUISITION_DIR: .recommendations # # Candidate generation crons (defaults shown, both daily). # NEW_RELEASE_CRON: "30 5 * * *" # RECOMMENDATION_CRON: "30 6 * * *" # Hard-deletion gates for the dislike lifecycle (invariant §C). All three # default to the safe value inside cleanup.service.ts; they are listed here # as documentation and are intentionally left unset. # # MASTER SWITCH — leave unset/false. While off, the cleanup sweep logs # exactly which files it WOULD delete and changes nothing at all. Set to # true by hand, only after reviewing a dry-run log: # MUZICK_ALLOW_HARD_DELETE: "false" # Blast-radius cap per sweep (default 5): # MUZICK_HARD_DELETE_MAX_PER_SWEEP: "5" # Days a track must sit in HIDDEN before its file is eligible (default 7): # MUZICK_HARD_DELETE_GRACE_DAYS: "7" volumes: # READ-WRITE, and the only rw mount of the library in the stack. The worker # is the sole process permitted to unlink a music file, and only via the # gated cleanup sweep. The backend keeps `:ro`. - /mnt/hdd1/media/Music:/music:rw networks: infra-net: external: true name: infra-net