- storage-agent: POST /internal/files copies a staged file into a source
(temp file, fsync, SHA-256 check, hard link so nothing is overwritten,
" (2)" suffix on conflict, owner PIPU_FILE_UID/GID) and
POST /internal/files/test. NFS sources that accept uploads are mounted
rw in an agent-only /library-rw and bind-mounted read-only for others;
local sources are written through /library-host-rw.
- meta: write templates ({track:02}, {disc:1-}, [optional]) with name
sanitising and length limits, suggestion from the source's regexes
and a reversibility check against 50 catalogue tracks.
- API and admin UI: accepts_uploads / write_template on sources,
write-template suggest and check endpoints, write test result.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- covers are taken from the embedded picture of the first track or a
cover/folder/front image in the album folder, converted by ffmpeg to
WebP 64/300/1200 and named by content hash, so albums sharing a cover
share the files; a full scan looks again for albums without one
- a 3-colour palette (dominant, second, most saturated accent) from a
deterministic k-means feeds placeholders, page tints and the wave
- /covers/{id} is public but HMAC-signed and immutable; nginx serves
the file via X-Accel-Redirect, X_ACCEL=false serves it from api
- the web client picks the size by display size and pixel density
- install.sh and update.sh generate STREAM_SIGNING_KEY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- artists, albums and tracks indexes are rebuilt from PostgreSQL next
to the live ones and swapped in, so search keeps working; a rebuild
runs on start, after every scan and when a source is removed
- transliteration fields (Кино ↔ kino) and a retry in the other
keyboard layout (ghbdtn → привет) reported as corrected_query
- /search/suggest (3 artists, 3 albums, 5 tracks), /search by type
with cursor pages, /admin/search/reindex; Meilisearch is part of
/readyz
- found ids are hydrated from the catalog, so a lagging index never
returns deleted entries
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- users, refresh_tokens and audit_log schema (migration 00002)
- argon2id passwords with rehash on login, password policy, lockout
with exponential back-off and a per-IP rate limit in Redis
- Ed25519 JWT access tokens with kid-based key rotation; rotating
refresh tokens with reuse detection (cookie for web, body for Android)
- bootstrap admin on first start; must_change_credentials and
must_change_password gate every other endpoint
- /auth/login, /auth/refresh, /auth/logout(-all), /me, /me/password,
/me/bootstrap-credentials, /me/sessions; server-info reports
bootstrap_required
- role policy from docs/auth-and-roles.md with table tests
- problem+json errors with field errors; ru/en translations of the codes
- install.sh and update.sh generate JWT_PRIVATE_KEY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the alpine nginx image localhost resolves to ::1 first while nginx
listens on IPv4 only, so the readiness wait never succeeded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upload import is not implemented yet, so nothing needs write access to
LIBRARY_HOST. Write access will come back together with the import.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scripts were committed without the executable bit, so ./install.sh failed
with "command not found" on a fresh clone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/api/Dockerfile, storage-agent, storage-agent) (push) Canceled after 0s
Add an install step for mounting the music disk via fstab and wire it into
the stack: LIBRARY_HOST in .env points at the host folder, compose mounts it
as /library-host (read-only for api, nginx and analyzer; writable only for
storage-agent), nginx serves it through X-Accel, and install.sh validates the
path and records it in .env.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/api/Dockerfile, storage-agent, storage-agent) (push) Canceled after 0s
Security and robustness:
- storage-agent rejects an empty INTERNAL_TOKEN (config notEmpty + middleware)
- api takes the client IP only from X-Real-IP (chi RealIP trusted True-Client-IP)
- /readyz reports ok/fail and logs the cause; checks run in parallel with a timeout
- storage-agent shuts down gracefully; shared httpx.NewServer/Serve with IdleTimeout
- goose migrations take a Postgres advisory lock
- recommender readyz: per-check timeout, redis socket timeouts
Clients:
- Android release allows cleartext HTTP for LAN servers (expo-build-properties)
- checkServer times out after 10s; connect button is disabled while checking
Deploy and CI:
- install.sh hands cache/uploads/updates/models to the non-root service users
- update.sh refuses downgrades unless PIPU_ALLOW_DOWNGRADE=1
- recommender/analyzer get only the env they need; required secrets fail fast
- nginx security headers and CSP; strip True-Client-IP
- dev compose binds ports to 127.0.0.1; postgres/redis images pinned
- version.mjs fails on a malformed explicit tag instead of releasing 0.0.0-dev
- contract check catches untracked generated files
- final releases fail without a release keystore; changelog skips rc tags
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/api/Dockerfile, storage-agent, storage-agent) (push) Successful in 52s
- deploy/compose.yaml: every app service has `build:` (repo root context),
local tags pipu-music-<service>:<version>
- deploy/update.sh: git fetch → checkout tag (latest release by default,
or vX.Y.Z / main) → docker compose build --pull → up -d → prune → /readyz;
re-executes from a temp copy because checkout may replace the script
- install.sh / .env.example: no REGISTRY, PIPU_VERSION set by update.sh,
.env owned by the invoking user
- release.yml: drop image publishing; release ships APK + sha256 only
- ci.yml: new `docker` job builds all images without push
- docs: install guide uses git clone, deployment/ci-cd updated, ADR-013
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docs/install-guide.md: step-by-step server setup (DNS, Docker, install.sh,
.env, launch, checks), web and Android client setup, own reverse proxy
variant, updates, backups and troubleshooting
- deploy/compose.tls.yaml + Caddyfile: optional Caddy with automatic
Let's Encrypt in front of nginx (enabled via COMPOSE_FILE in .env)
- nginx: honour X-Forwarded-Proto/For from private networks
- .env.example: DOMAIN, ACME_EMAIL, COMPOSE_FILE, bootstrap admin vars
- install.sh: create caddy data dirs; release.yml: ship TLS files as assets
- deployment.md, docs index and README link to the guide
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>