No description
  • Python 79.5%
  • TypeScript 18.9%
  • Shell 0.8%
  • CSS 0.8%
Find a file
stanta b2f022c6db
All checks were successful
Docker Image / build (push) Successful in 15m33s
CI / verify (push) Successful in 54m34s
Publish downloads with readable permissions
2026-08-11 22:24:28 +00:00
.forgejo/workflows Fix Playwright dependency installation 2026-08-07 09:51:25 +00:00
.github/workflows Report the complete CI matrix 2026-08-06 23:21:26 +00:00
backend Publish downloads with readable permissions 2026-08-11 22:24:28 +00:00
deploy/searxng Configure SearXNG JSON search 2026-06-23 16:34:32 +02:00
docs Record identity breadth rollout 2026-08-06 20:02:28 +00:00
frontend Add Saved artist resolution controls 2026-08-07 19:09:11 +00:00
mobile Add Saved artist resolution controls 2026-08-07 19:09:11 +00:00
packages/api-client Expose Saved artist resolution client 2026-08-07 19:04:10 +00:00
scripts Mount configured production downloads 2026-08-11 22:24:24 +00:00
.dockerignore Ignore generated Graphify output 2026-08-05 22:44:47 +00:00
.env.example Mount configured production downloads 2026-08-11 22:24:24 +00:00
.gitignore Ignore generated Graphify output 2026-08-05 22:44:47 +00:00
.npmrc Isolate mobile dependencies and update audio 2026-08-07 05:23:47 +00:00
.pre-commit-config.yaml Add project automation and handoff docs 2026-06-21 01:01:08 +02:00
docker-compose.prod.yml Mount configured production downloads 2026-08-11 22:24:24 +00:00
docker-compose.yml Add durable download execution barriers 2026-08-06 00:23:22 +00:00
HANDOFF.md Remove unsupported runtime integrations 2026-08-05 19:24:07 +00:00
package-lock.json Isolate mobile dependencies and update audio 2026-08-07 05:23:47 +00:00
package.json Isolate mobile dependencies and update audio 2026-08-07 05:23:47 +00:00
PLAN.md Update docs to reflect the real Qobuz integration 2026-07-17 15:07:54 +02:00
README.md Mount configured production downloads 2026-08-11 22:24:24 +00:00
TODO.md Remove unsupported runtime integrations 2026-08-05 19:24:07 +00:00

AuralStash

AuralStash is a self-hosted music discovery and acquisition controller. The current scaffold includes a FastAPI API, platform-aware discovery and download services, a compact Vite React operations console, an Expo Router mobile app, and a shared generated TypeScript API client.

The MVP keeps all provider actions and filesystem paths on the server. Web and mobile clients talk to the same /api/v1 API.

See TODO.md for the current project status, completed work, and next tasks.

Local Development

Install the managed Python 3.12 runtime and sync the locked backend environment. This workspace currently uses Python 3.12.13. These commands work in fish:

uv python install 3.12
env UV_PROJECT_ENVIRONMENT=../.venv uv --directory backend sync --locked --group dev --python 3.12

Backend tests:

npm run backend:test

Full local verification:

npm run check

Web e2e smoke, including Discover, Library, Saved, Settings, Preferences, Admin, and voice transcription:

npm run check:e2e

Backend API:

npm run backend:dev

This command uses isolated in-memory storage and binds the API to loopback. Direct backend startup requires a non-default AURALSTASH_CREDENTIAL_ENCRYPTION_KEY. Memory storage also requires AURALSTASH_ALLOW_UNSAFE_MEMORY_STORAGE=true.

Web app:

npm install
npm --workspace frontend run dev

Web e2e:

npm run web:e2e

Mobile app:

npm --workspace mobile run start

Docker:

cp .env.example .env
docker compose up --build

Before startup, edit the ignored .env file. Set AURALSTASH_CREDENTIAL_ENCRYPTION_KEY to a stable random value. Development PostgreSQL and Redis ports bind only to host loopback.

Rehearse a development database backup and restore before the next schema migration. The script selects docker-compose.yml and derives the Compose project from the repository directory name. Keep that project's PostgreSQL, API, and worker running. Set the exact source revision that you expect. Load the same environment that started the stack. Then run this command from Bash or Fish:

env \
  AURALSTASH_REHEARSAL_EXPECTED_REVISION=0010_discovery_sync \
  ./scripts/rehearse-db-backup-restore.sh

From /opt/auralstash-dev, the derived project name is auralstash-dev. The script verifies each selected container's Compose project, service, and source file labels. If PostgreSQL has known legacy Compose provenance, set the exact label value explicitly:

env \
  AURALSTASH_REHEARSAL_EXPECTED_REVISION=0010_discovery_sync \
  AURALSTASH_REHEARSAL_EXPECTED_POSTGRES_CONFIG_FILES='/mnt/data/coding/auralstash/docker-compose.prod.yml,/tmp/auralstash-dev-override.yml' \
  ./scripts/rehearse-db-backup-restore.sh

The script stops the development API and worker, so expect development downtime. If the dispatcher is running, the script also stops it during the backup. It restarts the selected services after a normal exit or the first handled INT or TERM. A host loss or SIGKILL can prevent restart. In that case, manually start the API, worker, and the dispatcher if it ran before the rehearsal. Use the same Compose file and derived project.

The script stores the archive, proof, and restricted logs under tmp/backup-rehearsals/. It retains the restored database for review. Treat the archive as sensitive and review proof.txt before cleanup. The script does not remove these artifacts or drop the restored database. The restored-database smoke uses the local image selected by the Compose file with registry pulls disabled. It proves that image can run its migrations and health endpoint against the restored copy. Current-source migration tests and the later candidate-image deployment cover newer migrations. The deterministic fake-Docker tests validate host-side orchestration only. They do not execute the nested migration and health smoke.

The frontend container runs as AURALSTASH_DOCKER_UID:AURALSTASH_DOCKER_GID to avoid root-owned files in bind-mounted node_modules; the defaults are 1000:1000.

Forgejo can build the Docker images with .forgejo/workflows/docker-image.yml. Configure FORGEJO_REGISTRY_TOKEN with package/container registry write access before expecting pushes; set FORGEJO_REGISTRY_USER only when the token username differs from the workflow actor. The image workflow runs only for trusted pushes and manual runs because it needs the runner Docker socket. The workflow builds each image at most once for a full commit hash and reuses that immutable image on a rerun. It tests the exact attested digest, creates a CycloneDX SBOM, and scans for high and critical vulnerabilities. It updates the latest or version tag only after those checks pass.

The production frontend image serves the built Vite app with nginx and proxies /api/ to the API container. Create a separate ignored environment file for production. Set the database password, credential encryption key, media host path, and both immutable sha256 image digests before startup:

cp .env.example .env.production
docker compose --env-file .env.production -f docker-compose.prod.yml up -d

The API and worker run as 10001:10001. Set AURALSTASH_MUSIC_HOST_PATH to the existing music directory. Set AURALSTASH_MUSIC_CONTAINER_PATH to the path stored in existing Library records. Compose mounts the music directory read-only and refuses to create a missing source path. AURALSTASH_MEDIA_HOST_PATH must contain the default writable downloads directory. To store downloads elsewhere, set AURALSTASH_DOWNLOAD_HOST_PATH to that host directory. Set AURALSTASH_DOWNLOAD_CONTAINER_PATH to its container path. Compose mounts only that download directory as writable. The directory must allow file creation and deletion by UID 10001. Startup stops before migrations or worker launch when this access check fails.

Production Compose uses read-only filesystems, drops Linux capabilities, and enables dependency, worker, dispatcher, frontend, and Redis healthchecks. It also enables secure session cookies, a conservative per-process API rate limit, and a 20 MiB voice upload cap. Set AURALSTASH_SESSION_COOKIE_SECURE=false only when testing the production stack over plain HTTP. Adjust AURALSTASH_RATE_LIMIT_REQUESTS, AURALSTASH_RATE_LIMIT_WINDOW_SECONDS, and AURALSTASH_VOICE_MAX_UPLOAD_BYTES for your reverse proxy and expected client behavior. The worker uses one process by default. Set AURALSTASH_WORKER_CONCURRENCY only after measuring server load.

Production Compose gives the frontend proxy a fixed internal address. The API trusts only that address for forwarded client data. If you change AURALSTASH_FRONTEND_PROXY_IP, set AURALSTASH_TRUSTED_PROXY_CIDRS to the same address with a /32 suffix. Keep the address inside AURALSTASH_INTERNAL_SUBNET.

The Forgejo runner job container must have the Docker CLI and access to a Docker daemon. With act_runner, that usually means using a label image that includes the Docker CLI and mounting the host socket, for example /var/run/docker.sock:/var/run/docker.sock, in the runner configuration.

Database-backed auth records, server settings, search history, recommendation feedback, platform accounts, saved recommendations, download jobs, local library records, LLM usage, and audit events are optional in this scaffold. Set AURALSTASH_STORAGE_BACKEND=database. Configure AURALSTASH_DATABASE_URL and a stable AURALSTASH_CREDENTIAL_ENCRYPTION_KEY, then run:

env UV_PROJECT_ENVIRONMENT=../.venv uv --directory backend run --locked python -m alembic upgrade head

Create the first administrator from the local host after the migration:

env UV_PROJECT_ENVIRONMENT=../.venv uv --directory backend run --locked python -m app.tools.bootstrap_admin --email admin@example.com

The command reads the password twice without displaying it. Bootstrap closes permanently after the administrator and audit event commit.

Queue execution defaults to AURALSTASH_QUEUE_BACKEND=inline. The legacy /api/v1/queue endpoint rejects Qobuz work and directs clients to /api/v1/downloads, which runs real Qobuz downloads in the API process for local development. Docker Compose sets AURALSTASH_QUEUE_BACKEND=celery with database storage so the worker updates durable job records through Redis/Celery; download jobs then run in the worker container. AURALSTASH_MEDIA_HOST_PATH binds the same operator media directory into the API and worker containers.

Provider and platform credentials are scoped to the authenticated AuralStash user and encrypted before they are stored in provider_accounts.encrypted_credentials. Set AURALSTASH_CREDENTIAL_ENCRYPTION_KEY to a stable secret in every runtime environment. Startup rejects an empty key and the documented public development value. To rotate keys, set the new key first and old keys after it, then re-encrypt existing rows:

env AURALSTASH_CREDENTIAL_ENCRYPTION_KEY="new-key,old-key" UV_PROJECT_ENVIRONMENT=../.venv uv --directory backend run --locked python -m app.tools.rotate_credentials --dry-run
env AURALSTASH_CREDENTIAL_ENCRYPTION_KEY="new-key,old-key" UV_PROJECT_ENVIRONMENT=../.venv uv --directory backend run --locked python -m app.tools.rotate_credentials

Internal and external users share the same users.id boundary. Future LDAP/OIDC adapters should provision users through AuthService.provision_external_user, so settings and linked platform/provider accounts remain scoped to the same user id.

After local bootstrap, admins can create local users, edit roles, disable/enable accounts, reset passwords, and revoke web sessions or device tokens from the web console. Disabled users are rejected during web login, device login, cookie auth, and bearer-token auth.

LLM provider API keys and provider passwords belong in the dedicated credentials fields, never in free-form config. Public admin config, audit, and usage responses recursively redact secret-like field names as a defense in depth, but deployment operators should still treat database backups as sensitive.

MVP Surface

  • /api/v1/health
  • /api/v1/auth/login/web
  • /api/v1/auth/login/device
  • /api/v1/auth/me
  • /api/v1/auth/logout/web
  • /api/v1/discovery
  • /api/v1/discovery/history
  • /api/v1/preferences
  • /api/v1/voice/transcribe
  • /api/v1/library/scans
  • /api/v1/library/summary
  • /api/v1/library/search
  • /api/v1/recommendations
  • /api/v1/recommendations/feedback
  • /api/v1/queue
  • /api/v1/jobs
  • /api/v1/downloads
  • /api/v1/settings
  • /api/v1/providers
  • /api/v1/platforms
  • /api/v1/admin/*

Qobuz is the first platform adapter, providing account search and real downloads (single tracks and albums, selectable quality, mutagen metadata/cover-art tagging). It authenticates with email and password, or with a Qobuz user token for accounts that sign in via Google/SSO. /api/v1/downloads is the real acquisition path; the legacy /api/v1/queue provider path no longer supports Qobuz.