Migrating from v1
v2 consolidates HappyView, Tap, and AIP into a single binary. Real-time indexing, backfill, and OAuth are now built in — there are no companion services to deploy.
This guide covers every breaking change and the steps to migrate.
Architecture changes
Section titled “Architecture changes”| v1 | v2 |
|---|---|
| HappyView + Tap + AIP (3 services) | Single HappyView binary |
| Postgres only | SQLite (default) or Postgres |
| AIP handles OAuth | Built-in atproto OAuth with DPoP |
| Tap handles indexing + backfill | Built-in Jetstream streaming + backfill |
| Offset-based pagination | Cursor-based pagination |
| Admin bootstrapping via config | First authenticated user becomes admin |
1. Remove Tap and AIP
Section titled “1. Remove Tap and AIP”Tap and AIP are no longer needed. Remove them from your docker-compose.yml (or equivalent) and delete any associated containers/volumes.
Environment variables to remove:
| Variable | Reason |
|---|---|
AIP_URL | OAuth is now built in |
AIP_PUBLIC_URL | No longer needed |
TAP_URL | Indexing is now built in |
TAP_ADMIN_PASSWORD | No longer needed |
TAP_DATABASE_URL | Tap no longer exists |
TAP_RELAY_URL | Use RELAY_URL on HappyView directly |
TAP_PLC_URL | Use PLC_URL on HappyView directly |
TAP_COLLECTION_FILTERS | Collection filtering now uses lexicon config |
TAP_SIGNAL_COLLECTIONS | Collection filtering now uses lexicon config |
2. Update environment variables
Section titled “2. Update environment variables”New required variables:
| Variable | Description |
|---|---|
PUBLIC_URL | Public-facing URL (e.g. https://happyview.example.com). Used for OAuth callbacks |
SESSION_SECRET | Secret key for signing session cookies (at least 64 chars). Must be set in production |
New optional variables:
| Variable | Default | Description |
|---|---|---|
DATABASE_BACKEND | auto-detected | Force sqlite or postgres |
JETSTREAM_URL | wss://jetstream1.us-east.bsky.network | Replaces Tap’s Jetstream connection |
STATIC_DIR | ./web/out | Dashboard static assets directory |
TOKEN_ENCRYPTION_KEY | --- | Base64-encoded 32-byte key for encrypting stored OAuth tokens. Strongly recommended in production |
DEFAULT_RATE_LIMIT_CAPACITY | 100 | Default token bucket capacity for new API clients |
DEFAULT_RATE_LIMIT_REFILL_RATE | 2.0 | Default refill rate (tokens/sec) for new API clients |
APP_NAME | --- | App name shown on OAuth screens |
LOGO_URI | --- | Logo URL for OAuth screens |
TOS_URI | --- | Terms of service URL |
POLICY_URI | --- | Privacy policy URL |
Unchanged variables: DATABASE_URL, HOST, PORT, RELAY_URL, PLC_URL, EVENT_LOG_RETENTION_DAYS, RUST_LOG.
See Configuration for the full reference.
3. Choose your database
Section titled “3. Choose your database”v2 defaults to SQLite. If you’re running Postgres in v1, you have two options:
Keep Postgres — no changes needed. Your DATABASE_URL stays the same and v2 auto-detects the backend from the connection string.
Migrate to SQLite — follow the Postgres to SQLite migration guide. SQLite is simpler to operate (no separate database server) and is the recommended default for most deployments.
4. Update Lua scripts
Section titled “4. Update Lua scripts”Cursor-based pagination (breaking change)
Section titled “Cursor-based pagination (breaking change)”db.query no longer supports offset. Replace offset-based pagination with cursors:
Before (v1):
local result = db.query({ collection = collection, limit = limit, offset = page * limit,})After (v2):
local result = db.query({ collection = collection, limit = limit, cursor = params.cursor,})
-- result.cursor is an opaque string; pass it back as ?cursor= for the next pageClients should pass the cursor value from the response as a query parameter to fetch the next page. Don’t parse or construct cursors — they’re opaque.
New APIs available
Section titled “New APIs available”v2 adds several new Lua APIs that you can optionally adopt:
atproto.resolve_service_endpoint— resolve a DID to its PDS endpointatproto.get_labels/atproto.get_labels_batch— fetch content labels from subscribed labelersos.time,os.date,os.difftime,os.clock— safeossubset
5. Update API key prefixes
Section titled “5. Update API key prefixes”v1 API keys used the hv_ prefix. v2 keeps existing hv_ keys working but new keys use the hv_ prefix as well. No migration needed.
v2 also adds API clients for third-party OAuth apps, which use the hvc_ prefix. These are separate from API keys — see the API Clients guide.
6. Update the dashboard URL
Section titled “6. Update the dashboard URL”The dashboard has moved from the root path to /dashboard:
| v1 | v2 |
|---|---|
/ | /dashboard |
/lexicons | /dashboard/lexicons |
/records | /dashboard/records |
/settings | /dashboard/settings |
Update any bookmarks or internal links.
7. User permissions
Section titled “7. User permissions”v2 introduces granular user permissions. After upgrading:
- The first user to authenticate becomes the super user (full access).
- Additional users are created with no permissions by default.
- Assign permissions or use a template (Viewer, Operator, Manager, Full Access).
See the Permissions guide for details.
8. Docker Compose (example)
Section titled “8. Docker Compose (example)”Before (v1):
services: postgres: image: postgres:17 # ...
tap: image: ghcr.io/bluesky-social/indigo/tap:latest environment: TAP_DATABASE_URL: postgres://... TAP_RELAY_URL: https://bsky.network TAP_ADMIN_PASSWORD: secret depends_on: [postgres]
happyview: image: ghcr.io/gamesgamesgamesgamesgames/happyview:latest environment: DATABASE_URL: postgres://... AIP_URL: http://aip:8080 TAP_URL: http://tap:2480 TAP_ADMIN_PASSWORD: secret depends_on: [postgres, tap]After (v2):
services: happyview: image: ghcr.io/gamesgamesgamesgamesgames/happyview:latest environment: DATABASE_URL: sqlite://data/happyview.db?mode=rwc PUBLIC_URL: https://happyview.example.com SESSION_SECRET: your-64-char-secret volumes: - data:/app/data
volumes: data:Or with Postgres:
services: postgres: image: postgres:17 # ...
happyview: image: ghcr.io/gamesgamesgamesgamesgames/happyview:latest environment: DATABASE_URL: postgres://happyview:happyview@postgres/happyview PUBLIC_URL: https://happyview.example.com SESSION_SECRET: your-64-char-secret depends_on: [postgres]Checklist
Section titled “Checklist”- Remove Tap and AIP services
- Remove old environment variables (
AIP_URL,TAP_URL,TAP_ADMIN_PASSWORD, etc.) - Add
PUBLIC_URLandSESSION_SECRET - Add
TOKEN_ENCRYPTION_KEY(recommended for production) - Decide on SQLite (default) or keep Postgres
- Update Lua scripts to use cursor-based pagination instead of offsets
- Update any bookmarks/links to use
/dashboardprefix - Set up user permissions after first login

