# atmo.quest An event-companion app on [ATProto](https://atproto.com). Scan a QR, write a private note, leave a conference with a real follow-up list. Records live in your PDS, not ours. ## Quick start ```bash cp .env.example .env go tool task live ``` Then open the URL printed in the logs (defaults to ****). The `task live` recipe runs templ-watch, the web-asset bundler, and an air-driven live-reloading Go server. `task` is registered as a Go tool dependency, so no separate install is needed. If you'd rather have a bare `task` on your PATH: `brew install go-task`. For a one-shot run without live reload: `go run -tags dev ./cmd/web`. On first boot the app creates `data/atmoquest.db` (SQLite) and `data/admin_signing.key` (ed25519, badge signing). No further setup needed. > Use `127.0.0.1`, not `localhost` — atproto OAuth requires the redirect URI to match the configured `PUBLIC_URL` exactly. If port 9090 is taken on your machine, edit `.env` and update both `PORT` and `PUBLIC_URL` to match. ## Useful routes | Route | Notes | |---|---| | `/` | Guest landing or authed dashboard, depending on session | | `/signin` | Start OAuth via your Bluesky handle | | `/profile`, `/profile/edit` | View / edit your atmo.quest profile | | `/c/{did}` | Connect page (target of QR scans) | | `/e/{token}` | Event check-in page | | `/admin` | Admin console — requires `users.is_admin = 1` | | `/healthz` | Liveness probe | To grant yourself admin after signing in once, either: ```bash # Local dev: edit the SQLite file directly. sqlite3 data/atmoquest.db "UPDATE users SET is_admin=1 WHERE did='did:plc:…';" ``` Or set `BOOTSTRAP_ADMIN_DIDS` (space-separated) and restart the app. The listed DIDs are promoted to admin on every boot — idempotent and safe to leave set. This is the supported path in deployed environments where `sqlite3` isn't reachable (the prod image is `scratch`-based): ```bash BOOTSTRAP_ADMIN_DIDS="did:plc:xxx did:plc:yyy" ``` Either way, the DID must have signed in at least once first (so the `users` row exists). ## Layout ``` cmd/web/ entrypoint config/ env + config singleton router/ top-level route wiring features// handlers.go, routes.go, pages/*.templ internal// storage & domain logic (event, checkin, profile, …) web/resources/ static assets (css, js, fonts, datastar) ``` ## Env vars | Var | Default | Required | |---|---|---| | `PORT` | `8080` | no | | `PUBLIC_URL` | `http://127.0.0.1:$PORT` | no (dev) / **yes** (prod) | | `SESSION_SECRET` | dev default | **yes** in prod | | `DATABASE_URL` | `file:data/atmoquest.db?…` | no | | `ADMIN_SIGNING_KEY` | auto-generated in dev | **yes** in prod | | `BOOTSTRAP_ADMIN_DIDS` | unset | no | | `LOG_LEVEL` | `INFO` | no | `-tags dev` selects the dev config (random session key, auto-generated admin key). `-tags prod` (or no tag, in CI) requires the real secrets above. ## Tests ```bash go test -tags dev ./... ``` ## Production build ```bash go tool task build # writes bin/main, embeds prod tag ./bin/main ``` Or Docker: ```bash docker build -t atmoquest:latest . docker run --rm -p 8080:8080 \ -v atmoquest_data:/data \ -e PUBLIC_URL=https://your.domain \ -e SESSION_SECRET=$(openssl rand -hex 32) \ -e ADMIN_SIGNING_KEY=$(openssl rand -base64 32) \ -e BUCKET_NAME=your-bucket \ -e AWS_ENDPOINT_URL_S3=https://your-s3-endpoint \ -e AWS_ACCESS_KEY_ID=… \ -e AWS_SECRET_ACCESS_KEY=… \ -e AWS_REGION=auto \ atmoquest:latest ``` The image bundles [Litestream](https://litestream.io) as its supervisor process: it watches `/data/atmoquest.db`'s WAL and streams changes to S3-compatible object storage. Replica config lives in [`litestream.yml`](litestream.yml) and reads bucket + credentials from the `BUCKET_NAME` / `AWS_*` env vars above. Litestream is required — without those vars set the container won't start. Mount a volume at `/data` so the database and the OAuth signing key (`/data/oauth_key.pem`) survive restarts. ## Contributing PRs and feature requests welcome.