Self-hosted debt ledger. Rust + Axum + Postgres, vanilla PWA frontend. Replaces a Coda.io "Skuld Mamma" doc with a mobile-friendly web app: a running balance, a transaction list, an always-visible add form, and a step-chart graph view with range presets and hover.
- Create a Postgres resource in Coolify. Note its internal connection URL.
- Create an Application from this git repo, build pack = Dockerfile.
- Google OAuth client (Google Cloud Console → Credentials → OAuth client, type Web):
- Authorized redirect URI:
https://<your-domain>/auth/callback
- Authorized redirect URI:
- Set environment variables on the Application:
var value DATABASE_URLthe Coolify Postgres internal URL GOOGLE_CLIENT_IDfrom Google GOOGLE_CLIENT_SECRETfrom Google OAUTH_REDIRECT_URLhttps://<your-domain>/auth/callbackBOOTSTRAP_ADMIN_EMAILyour Google email (first-boot admin) SESSION_SECRET32+ random bytes COOKIE_SECUREtrue(leave default; setfalseonly for local HTTP) - Set the app port to 3000 (matches the image's
EXPOSEand thePORTenv); assign your domain (Coolify provisions HTTPS via its Traefik proxy). - Deploy. On first boot the app runs migrations and bootstraps your admin account. The transactions table starts empty; add transactions in-app.
- Sign in with Google. Invite others from the account menu (admin only).
Only Google accounts whose email is on the allowlist (the users table) can sign in. The first admin comes from BOOTSTRAP_ADMIN_EMAIL; thereafter manage the allowlist in-app via POST /api/users / DELETE /api/users/:id.
docker run -d --name skuld-pg -e POSTGRES_PASSWORD=pw -e POSTGRES_DB=skuld -p 5432:5432 postgres:17
cp .env.example .env # fill values; DATABASE_URL=postgres://postgres:pw@localhost:5432/skuld
# SESSION_SECRET must be 32+ bytes; set COOKIE_SECURE=false for plain-HTTP localhost
cargo runOpen http://localhost:8080. (Google login needs real OAuth credentials + the redirect URI registered; the rest of the app runs without them.) The table starts empty; add transactions in-app.
Tests run against a disposable, always-clean Postgres (tmpfs-backed, port 5433). One command resets it and runs the suite:
bash scripts/test.sh # or: pwsh scripts/test.ps1Each test gets its own isolated database via #[sqlx::test], so runs never share state.
One Rust binary serves everything:
GET /health— liveness (Coolify health checks)/auth/login·/auth/callback·/auth/logout— Google OIDC (authorization code + PKCE), allowlist-gated, signed HMAC session cookie/api/transactions(CRUD),/api/export,/api/users(admin allowlist) — session-gated JSON API- everything else — static SPA from
web/(dark-mode PWA, installable, offline shell)
belopp is NUMERIC(12,2); datum is DATE. Positive amounts increase the debt, negative decrease it; the balance is SUM(belopp).