Skip to content

Local development

Use the Go version declared in go.mod, currently 1.27.1, plus Docker Engine with Compose, ffmpeg, Bash, and just. The lint tools are listed under checks.

End-to-end tests use real Postgres and Navidrome containers, the real filesystem, and ffmpeg. Telegram and provider APIs are HTTP test doubles; no real bot token or Zvuk subscription is needed for the test suite.

From the repository root:

Terminal window
cp .env.example .env
cp config.example.toml config.toml
mkdir -p test_data/shared test_data/users

Fill startup credentials in .env. Generate SECRET_KEY with openssl rand -base64 32. For host-run development, keep the sample DB_DSN pointing to localhost:5432.

Edit your own administrator ID and these existing TOML settings:

[library]
music_dir = "/absolute/path/to/beatstash/test_data"
navidrome_music_dir = "/music"
[navidrome]
url = "http://localhost:4533"
public_url = ""
user = "admin"

Use an absolute host path for music_dir. The runtime does not expand ~ in that setting. The root Compose file exposes Postgres and Navidrome on the host for development; keep those ports confined to your development machine.

Start dependencies and create the first Navidrome administrator at http://localhost:4533:

Terminal window
docker compose up -d postgres navidrome

Use its login and password in config.toml and .env. Then:

Terminal window
just up

just loads .env; the Go application does not. just up starts Postgres and Navidrome, then runs go run cmd/beatstash/main.go on the host. It does not start the local Telegram API or a containerized bot.

For small files, leave telegram.bot_api_url empty and use the cloud API. For local API development, follow Telegram setup and configure host access plus identical local file paths; the production-style shared volume assumes the bot also runs in Docker. Use a separate development token rather than polling the production token simultaneously.

Terminal window
just lint
just test

just lint runs Go lint and formatting checks, go vet, compilation, module checks, workflow linting, spelling, ShellCheck, installer syntax, and secret scanning. It leaves files unchanged; just fmt fixes Go formatting. just docs checks and builds this documentation.

just lint looks for tools on your PATH, in Go’s bin folder, and in ~/.local/bin, and lists any that are missing before it starts.

ToolVersionInstall
golangci-lintv2Into $(go env GOPATH)/bin
actionlint1.7.12go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12
Gitleaks8.30.1go install github.com/zricethezav/gitleaks/v8@v8.30.1
ShellCheck0.11.0Your package manager, or uv tool install shellcheck-py==0.11.0.1
zizmor1.30.1uv tool install zizmor==1.30.1, or see its installation guide
typos1.50.3Binary from release v1.50.3 into ~/.local/bin
Node24For the docs: npm --prefix docs ci

Secret scanning covers all local Git history plus tracked and new files. Ignored local credentials and generated files are skipped, but a tracked file is scanned even if it matches .gitignore. Findings fail the check, with secret values redacted in the log. The only exception is the fixed test encryption key in the test harness.

just test runs release-policy tests and unit tests first, then runs the installer scenarios and end-to-end business scenarios concurrently. Docker must be running and your user must be able to use it. CI also collects coverage from the end-to-end tests.

Local test recipes use gotestsum for compact unit-test summaries, live scenario results and full failure details. The pinned version runs through go run, which downloads and caches it automatically on the first run.

The installer scenarios in e2e/installer run the real installer against Docker with real Navidrome and Caddy containers and stand-ins for the bot and Telegram. Each scenario uses its own Compose project, volumes, Caddy container and dynamically assigned host ports. just test and just test-setup use Go’s default test parallelism; CI limits it to four scenarios at a time. Tests reuse downloaded images and limit Caddy discovery to their own containers, so other installations and Caddy servers can keep running. Version checks use saved deployment files without starting a stack. To try the installer by hand, build it with a version, such as go build -ldflags "-X main.version=0.0.1" ./cmd/beatstash-setup.

The small audio samples are committed to the repository, and tests generate additional audio as needed. You do not need to regenerate them before running tests. ffmpeg is still required.

For unfiltered test output or one test:

Terminal window
go test -count=1 ./e2e/
go test -count=1 ./e2e/ -run TestAttachedLibraryAccess

Test containers are isolated from the root development Compose stack. Container downloads may make the first test run slower.