An event companion and onboarding experience for the ATmosphere (alpha) atmo.quest
Go 54%
JavaScript 25%
templ 13%
CSS 8%
TypeScript <1%
Nix <1%
Dockerfile <1%
<1%

README.md

atmo.quest #

An event-companion app on ATProto. 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 #

cp .env.example .env
go tool task live

Then open the URL printed in the logs (defaults to http://127.0.0.1:9090). 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:

# 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):

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/<name>/  handlers.go, routes.go, pages/*.templ
internal/<name>/  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 #

go test -tags dev ./...

Production build #

go tool task build      # writes bin/main, embeds prod tag
./bin/main

Or Docker:

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 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 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.