Montara Pass
Operations

Migrations

Changing the database schema with the embedded migration CLI.

SQL migrations live in apps/server/migrations as numbered up and down files. They are embedded in the binaries, so a deployed image carries its own schema.

Commands

pnpm migrate:up                                              # apply what is pending
pnpm --filter @montara-pass/server run migrate:up:seed       # up, then run the seeders
pnpm --filter @montara-pass/server run migrate:create add_x  # new up/down pair
pnpm --filter @montara-pass/server run migrate:version       # current version
pnpm --filter @montara-pass/server run migrate:force 2       # after a failed migration
pnpm migrate:down                                            # roll back everything
pnpm --filter @montara-pass/server run migrate:refresh       # down, then up

The CLI reads DB_DSN from the environment or apps/server/.env, or takes --db-dsn.

down and refresh delete all data

down rolls back every migration, users included. Both commands refuse a database that is not on localhost unless --allow-remote is passed.

On boot

With MIGRATE_ON_BOOT=true the API applies pending migrations before it starts serving. With SEED_ON_BOOT=true it also runs the seeders, which are idempotent: the categories, and the SUPER_USER account.

Existing migrations

VersionWhat it creates
000001categories
000002users, mirroring authula.users
000003the role column on users
000004the vault: user_keys, folders, items, item_members
000005the last_used_at column on item_members

Cloud Edition migrations

apps/server/migrations/ee holds the tables only the Cloud Edition has. They keep their own version in schema_migrations_ee, run after the main ones and roll back before them. A Community Edition build never applies them, so its database has none of these tables; a database that moves to the Cloud Edition gets them on its next up or boot.

up, down, refresh and version act on both tracks. force and create act on the main one unless --ee is passed:

go run ./cmd/migrate --ee create add_x   # new pair in migrations/ee
go run ./cmd/migrate --ee force 1        # after a failed EE migration
VersionWhat it creates
000001billing: plans, subscriptions, purchases

Only an account with the user role holds a subscription or a purchase. The database enforces it: both tables reference users (id, role) with the role fixed to user, so a super user cannot subscribe, and a subscriber cannot be given another role. Each table also takes only its kind of plan (recurring or one_time), and a user has at most one live subscription.

Tests against a real database

apps/server/cmd/api has an integration test that drives the API end to end. It is skipped unless TEST_DB_DSN points at a PostgreSQL 18 database it is allowed to wipe.

With TEST_REDIS_URL as well, that test keeps its rate limit counts and idempotency keys in Redis, and apps/server/internal/middlewares runs its Redis tests: two replicas sharing one budget, one answer and one lock. They are skipped without it. Point it at a database number nothing else uses, such as redis://localhost:6379/15.

On this page