Skip to main content

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

v1v2
HappyView + Tap + AIP (3 services)Single HappyView binary
Postgres onlySQLite (default) or Postgres
AIP handles OAuthBuilt-in atproto OAuth with DPoP
Tap handles indexing + backfillBuilt-in Jetstream streaming + backfill
Offset-based paginationCursor-based pagination
Admin bootstrapping via configFirst authenticated user becomes admin

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:

VariableReason
AIP_URLOAuth is now built in
AIP_PUBLIC_URLNo longer needed
TAP_URLIndexing is now built in
TAP_ADMIN_PASSWORDNo longer needed
TAP_DATABASE_URLTap no longer exists
TAP_RELAY_URLUse RELAY_URL on HappyView directly
TAP_PLC_URLUse PLC_URL on HappyView directly
TAP_COLLECTION_FILTERSCollection filtering now uses lexicon config
TAP_SIGNAL_COLLECTIONSCollection filtering now uses lexicon config

2. Update environment variables

New required variables:

VariableDescription
PUBLIC_URLPublic-facing URL (e.g. https://happyview.example.com). Used for OAuth callbacks
SESSION_SECRETSecret key for signing session cookies (at least 64 chars). Must be set in production

New optional variables:

VariableDefaultDescription
DATABASE_BACKENDauto-detectedForce sqlite or postgres
JETSTREAM_URLwss://jetstream1.us-east.bsky.networkReplaces Tap's Jetstream connection
STATIC_DIR./web/outDashboard static assets directory
TOKEN_ENCRYPTION_KEY---Base64-encoded 32-byte key for encrypting stored OAuth tokens. Strongly recommended in production
DEFAULT_RATE_LIMIT_CAPACITY100Default token bucket capacity for new API clients
DEFAULT_RATE_LIMIT_REFILL_RATE2.0Default 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

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

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 page

Clients 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

v2 adds several new Lua APIs that you can optionally adopt:

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

The dashboard has moved from the root path to /dashboard:

v1v2
//dashboard
/lexicons/dashboard/lexicons
/records/dashboard/records
/settings/dashboard/settings

Update any bookmarks or internal links.

7. User permissions

v2 introduces granular user permissions. After upgrading:

  1. The first user to authenticate becomes the super user (full access).
  2. Additional users are created with no permissions by default.
  3. Assign permissions or use a template (Viewer, Operator, Manager, Full Access).

See the Permissions guide for details.

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

  • Remove Tap and AIP services
  • Remove old environment variables (AIP_URL, TAP_URL, TAP_ADMIN_PASSWORD, etc.)
  • Add PUBLIC_URL and SESSION_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 /dashboard prefix
  • Set up user permissions after first login