diff --git a/.env.example b/.env.example index 712da41..4e8470a 100644 --- a/.env.example +++ b/.env.example @@ -35,6 +35,11 @@ REFRESH_TOKEN_TTL_SECONDS=2592000 # Public self-service sign-up (POST /auth/register). Set to false to make # accounts admin-only. Backend authority; pair with PUBLIC_ENABLE_REGISTRATION. ALLOW_REGISTRATION=true +# CORS: browser origins allowed to call the API. Defaults to "*" (safe here — +# auth is a bearer token, not cookies). The web UI can connect cross-origin +# (e.g. the direct :8000 port, a LAN IP), which needs this. Restrict in +# hardened setups: CORS_ALLOW_ORIGINS=http://localhost:8881,http://localhost:3000 +# CORS_ALLOW_ORIGINS=* # media / storage (paths inside the container) MEDIA_PATH=/data/media diff --git a/.gitignore b/.gitignore index a17d470..f8d3f86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .env *.zip plans/ +TODO/ modern-sk/ data/ .DS_Store diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8ace860 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,103 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this repo is + +`mcma-compose` is the **dev-orchestration superproject** for *mycoolmusicapp* — a self-hosted music +app. It contains no application code of its own: `docker-compose.yml`, `nginx.conf`, `Makefile`, and +`.env.example`. The actual code lives in two **git submodules**: + +- `mcma-backend/` — Python / FastAPI (async), arq background workers, Alembic migrations. +- `mcma-webui/` — TypeScript / React, built with Rsbuild. + +Both submodules are pulled from `ssh://git@git.ollyhearn.ru:49239` (see `.gitmodules`). **They may be +empty** — clone with `git clone --recurse-submodules`, or run `git submodule update --init` in an +existing checkout, before you can touch or build app code. + +## The stack + +`make up` builds and starts six services (see `docker-compose.yml`): + +| Service | Image / context | Role | +|----------|-------------------|------| +| `db` | postgres:16-alpine | PostgreSQL, data in `./data/pgdata` | +| `redis` | redis:7-alpine | arq broker + cache, data in `./data/redisdata` | +| `api` | `mcma-backend` | FastAPI via `uvicorn --reload` | +| `worker` | `mcma-backend` | `arq app.workers.arq_worker.WorkerSettings` background jobs | +| `webui` | `mcma-webui` | Rsbuild dev server with HMR | +| `nginx` | nginx:1.27-alpine | **single entrypoint**, reverse proxy | + +Key architectural points that span multiple files: + +- **nginx is the only public port.** It routes `/api/` and `/health` → `api:8000`, everything else + (including the `/rsbuild-hmr` websocket) → `webui:3000`. The browser never talks to webui directly; + HMR is proxied through nginx, which is why compose sets `RSBUILD_HMR_CLIENT_PORT` to `NGINX_PORT`. +- **The entrypoint is `http://localhost:8881`**, not `:80`. Compose maps `${NGINX_PORT:-8881}:80` + (README says `:80`, but the `.env.example` default is `8881`). `api:8000` is also exposed directly + for docs/debugging (`/docs`). +- **Source is bind-mounted** into `api`, `worker`, and `webui`, so edits in the submodules hot-reload + with no rebuild. Named-volume mounts (`/app/.venv`, `/app/node_modules`) shadow the host dir to + preserve the image's installed deps — if you change dependencies you must rebuild (`make build`). +- **`api` and `worker` share the same image** and both need `db` + `redis` healthy before starting. +- **`DATABASE_URL` / `REDIS_URL` are overridden inside compose** to use service names + (`db`, `redis`). The localhost values in `.env` are only the fallback for host-run processes. +- **Persistent state and media live under `./data/`** (gitignored): `pgdata`, `redisdata`, `media`, + `transcode-cache`, and `youtube` (drop a yt-dlp `cookies.txt` here — optional). + +## Commands + +Everything runs **inside the containers** via the `Makefile`, which wraps `docker compose exec`. +The stack must be up (`make up`) before `test`/`lint`/`migrate`/shell targets work. Run `make help` +for the full list. + +```bash +make up # create .env from .env.example, build images, start the whole stack +make build # rebuild images (with cache); needed after dependency changes +make rebuild # rebuild with --no-cache +make down # stop + remove containers +make clean # down -v — DESTROYS db/redis volumes +make logs-api # tail a single service's logs (also logs, logs-webui) +make sh-api # bash into api (also sh-webui, db-shell, redis-cli) +``` + +Migrations (Alembic, run in the `api` container): + +```bash +make migrate # alembic upgrade head +make makemigration m="add_tracks" # autogenerate a revision +make downgrade # roll back one +``` + +Quality — these shell out to the tools inside each container: + +```bash +make test # test-api (pytest) + test-webui (npm test) +make test-api # pytest — for a single test: make sh-api, then `pytest path::test` +make test-webui # npm test +make lint # ruff check . (api) + npm run lint (webui) +make fmt # ruff format . (api) + npm run format (webui) +``` + +There is **no dev docker-compose for prod**. Prod images are built straight from each submodule's +`dockerfiles/Dockerfile.prod`: + +```bash +make prod-build # builds mcma-backend:prod and mcma-webui:prod +``` + +## Environment + +`.env` is a single combined file at the repo root, created from `.env.example` by `make env` / +`make up` (never committed; it's gitignored). It is injected into `api`, `worker`, and `webui`. +Backend features degrade gracefully when optional keys are unset: + +- `ACOUSTID_API_KEY` + `MUSICBRAINZ_OWNER_EMAIL` — enable AcoustID/MusicBrainz metadata enrichment. +- `YOUTUBE_ENABLED` / `YOUTUBE_COOKIES_PATH` — YouTube Music source via yt-dlp. +- `ALLOW_REGISTRATION` (backend) must be kept in sync with `PUBLIC_ENABLE_REGISTRATION` (frontend). + +## Working across submodules + +When a change touches app behavior, the code is in `mcma-backend/` or `mcma-webui/` — commit there +first, then the superproject records the new submodule commit. Recent superproject history is mostly +`chore: bump mcma-backend/mcma-webui` commits that pin updated submodule SHAs. diff --git a/mcma-backend b/mcma-backend index 7800746..313af3a 160000 --- a/mcma-backend +++ b/mcma-backend @@ -1 +1 @@ -Subproject commit 78007461e1e403358dceef700a61eeae1d142435 +Subproject commit 313af3a0706a296c325e761aeff7616167ebd2db diff --git a/mcma-webui b/mcma-webui index 89cf66f..e45ef78 160000 --- a/mcma-webui +++ b/mcma-webui @@ -1 +1 @@ -Subproject commit 89cf66f28abdbdc88c9543f644aab257a39f1194 +Subproject commit e45ef785cfb56457dbd7a1dcc959d6ef7ff3675a