Migration guide — auth-mode=none → enabled
When you upgrade an existing single-tenant deployment to --auth-mode=enabled, three things have to be true before the flip:
- The database has the auth tables (migrations 008–014 applied).
- A master key is generated and the server can read it.
- An admin user exists and has been bound to the
adminrole.
The recommended sequence does all three first, then flips the flag — so you can roll back by un-flipping the flag without losing any data.
Compatibility matrix
| Capability | --auth-mode=none (today) | --auth-mode=enabled (after migration) |
|---|---|---|
| Public API | yes | no — 401 for anonymous |
| Login screen / user accounts | n/a | required |
| RBAC enforcement on mutations | bypassed | enforced |
| Project-scoped list filtering | disabled | filtered to active project |
| Audit actor | always "system" | real user/token identity |
| Existing API tokens for CI | n/a | created via ns token create, used as Bearer |
| SSO | n/a | OIDC + SAML available |
| Rollback | always available | restart with --auth-mode=none |
Step-by-step
-
Apply migrations 008–014.
On the next start, nanosync auto-runs
goose up. If you want to apply manually first to a maintenance window:ns admin migrate up # Or: goose -dir internal/db/migrations/postgres postgres "$DATABASE_URL" upVerify the seeded tenant:
SELECT slug FROM orgs WHERE id = '00000000-0000-0000-0000-000000000001'; -- ('nanosync') SELECT count(*) FROM projects WHERE id = '00000000-0000-0000-0000-000000000003'; -- (1)Migration 013 backfilled every existing pipeline and connection with
project_id = '00000000-0000-0000-0000-000000000003'(the default project). Confirm:SELECT count(*) FROM pipelines WHERE project_id IS NULL; -- (0)Migration 014 flips
project_idto NOT NULL onpipelines,connections, andpipeline_runson Postgres. SQLite enforces non-null at the application layer (it can’t ALTER COLUMN portably). -
Generate the master key.
openssl rand -hex 32 > /etc/nanosync/master.key chmod 0600 /etc/nanosync/master.key chown nanosync:nanosync /etc/nanosync/master.keyBack this up to your secrets vault. Losing it means losing every encrypted secret (SSO client secrets, SAML private keys). The key is NOT recoverable from the database — only from your backup.
-
Update the config.
auth: mode: enabled master_key_file: /etc/nanosync/master.key session_ttl: 12hDon’t restart yet. The next step needs the daemon running in its current mode.
-
Bootstrap the first admin while still on
--auth-mode=none.This is the trick that makes the migration zero-downtime: you bootstrap before you flip the flag, while the server still answers as SystemRoot. The
POST /v1/admin/bootstraproute is always available regardless of mode, and it creates the admin user + binds the admin role.ns admin bootstrap \ --email=admin@acme.com \ --display-name='Acme Admin' # Prompts for password interactivelyVerify by logging in (still on
--auth-mode=none, so this also succeeds):ns auth login --email=admin@acme.com ns auth whoami -
Restart with
--auth-mode=enabled.systemctl restart nanosyncConfirm anonymous requests are now refused:
curl -sS -i http://localhost:8080/v1/pipelines # HTTP/1.1 401 Unauthorized # {"error":"unauthenticated"}And confirm the admin still works:
ns auth login --email=admin@acme.com # cookie now required by the server ns pipeline list -
Issue API tokens for any non-interactive callers.
Existing CI scripts and automation hitting the API now need to send a Bearer token:
ns token create --name=ci-deploys --ttl=8760h # Output: # ✓ token created — keep this secret, it will not be shown again # token: ns_a1b2c3d4_VeryLongRandomSecretGoesHereYouOnlySeeItOnce… # id: 01h…Update your CI to send
Authorization: Bearer ns_…on every API call. -
Bind roles to additional users.
Now that admin works, invite the rest of your team and bind them to roles:
ns role bind editor --user=alice@acme.com \ --scope=workspace --scope-id=<workspace-uuid> ns role bind operator --user=oncall@acme.com \ --scope=workspace --scope-id=<workspace-uuid>See RBAC roles & bindings for the full role catalog and custom recipes.
-
(Optional) Wire SSO.
For larger teams, OIDC removes the password management burden. See SSO setup.
Rollback
If anything goes wrong, restart with --auth-mode=none:
systemctl stop nanosync
sed -i 's/^ mode: enabled/ mode: none/' /etc/nanosync/config.yaml
systemctl start nanosync
Data persists across the flag flip — users, role bindings, projects, audit events all stay. When you fix the issue and re-flip to enabled, everything is exactly as you left it.
The flag does NOT delete any data. There is no --reset-auth flag. Recovery is by restart, not by destructive operations.
What changes for existing automation
| Caller | Before | After |
|---|---|---|
ns CLI on the same host | unauthenticated | ns auth login once + sessions auto-renew |
ns CLI on a CI runner | unauthenticated | NS_TOKEN=ns_… env var (mint via ns token create) |
curl https://nanosync/v1/... | direct | curl -H "Authorization: Bearer ns_…" https://…/v1/... |
| Web UI | direct | login page, then cookie-bearing session |
| Terraform provider (community) | direct | env var NANOSYNC_TOKEN=ns_… |
| GitHub Actions / GitLab CI | unauthenticated | secret NS_TOKEN set in pipeline secrets, exported into env |
Sessions are 12h by default (sliding) — interactive users rarely re-login. Tokens have configurable TTL via --ttl=... on ns token create.
Downtime expectations
- Migrations 008–014: zero downtime. They’re additive (new tables, new nullable columns) and don’t lock existing rows materially. Migration 014’s
NOT NULLflip on Postgres takes the briefest of locks — backfill already happened in 013. - Master key generation: zero downtime.
- Bootstrap: zero downtime — the route runs while the server is up.
- Flag flip restart: standard restart downtime (a few seconds with systemd, less with Kubernetes rolling restart).
Common gotchas
| Gotcha | Symptom | Fix |
|---|---|---|
| Forgot to bootstrap before flipping the flag | Locked out — 401 on every call, including from the CLI | Restart with --auth-mode=none, run bootstrap, restart again with enabled |
| Reverse proxy stripping cookies | Logins “succeed” but next request is 401 | Allow nanosync_session cookie; verify SameSite=Lax works for your domain |
| Multi-instance setup with shared DB | Sessions issued by node A rejected by node B | Sessions are server-side — they’ll work across nodes, but verify the master key is identical on every node (encrypts session secrets) |
| Existing CI starts failing | 401 from every CI run | Mint tokens with ns token create, set env var in CI secrets |
data_dir not writable by service user | Audit sink fails to open the file | chown nanosync:nanosync /var/lib/nanosync |
What’s next
- RBAC roles & bindings — your real users and their bindings.
- SSO setup — drop local passwords once SSO is wired.
- Audit logging — ship audit events to your SIEM for compliance.