- Python 79.5%
- TypeScript 18.9%
- Shell 0.8%
- CSS 0.8%
| .forgejo/workflows | ||
| .github/workflows | ||
| backend | ||
| deploy/searxng | ||
| docs | ||
| frontend | ||
| mobile | ||
| packages/api-client | ||
| scripts | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .npmrc | ||
| .pre-commit-config.yaml | ||
| docker-compose.prod.yml | ||
| docker-compose.yml | ||
| HANDOFF.md | ||
| package-lock.json | ||
| package.json | ||
| PLAN.md | ||
| README.md | ||
| TODO.md | ||
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.