You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+34-1Lines changed: 34 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,7 +7,7 @@ Every consumer talks to this over plain HTTP — no Go required. This is what a
7
7
## Prerequisites
8
8
9
9
- Go 1.22+ (check `go.mod` for exact version)
10
-
- A running Postgres instance (local, Docker, or hosted — e.g. Supabase, Neon, RDS)
10
+
- A running Postgres instance (local, Docker, or hosted — e.g. Supabase, Neon, RDS) — or nothing but a writable path, if you run on SQLite (see [The two backends](#the-two-backends))
11
11
12
12
## Getting started
13
13
@@ -20,6 +20,8 @@ go run .
20
20
21
21
Run the migrations in `migrations/` against your database first, in order (copies of CrydenSync's own migrations, kept here so this repo is self-contained for local dev and CI — same as `typebook` keeps its own copy). `002_oauth_identities` is required even if you don't use OAuth yet — `NewOAuthStore` is wired into the engine config unconditionally. `004` through `008` are the TOTP, WebAuthn, recovery-code, login-attempt and API-key tables; run them even if you leave `ENCRYPTION_KEY` unset, since `007` is what the engine's credential-stuffing detection reads once Tier 2 wires it up and `008` is what the API-key work will use.
22
22
23
+
That paragraph is the Postgres path only. On SQLite there is nothing to run by hand — `main.go` calls cryden's own `sqlite.Migrate` at startup. See [The two backends](#the-two-backends).
24
+
23
25
OAuth is optional. To enable a provider, set its client ID/secret plus `BASE_URL` (used to build the callback URL registered in that provider's console):
All four `APPLE_*` values are required; a partially configured Apple is simply unavailable, like any other unconfigured provider.
59
61
62
+
## The two backends
63
+
64
+
This API runs on Postgres or on SQLite, chosen by exactly one environment variable:
65
+
66
+
```
67
+
DATABASE_URL=postgres://... # everything
68
+
SQLITE_PATH=/var/lib/cryden/api.db # core auth only
69
+
```
70
+
71
+
Setting both is a startup error, and so is setting neither. That is deliberate rather than a convenience: `DATABASE_URL` is what the admin console needs and `SQLITE_PATH` is what the store wiring reads, so quietly preferring one would run a deployment on a backend its own configuration does not describe.
72
+
73
+
**SQLite runs core auth and nothing under `/v1/admin`.** Signup, login, refresh, sessions, password change, email change, OAuth, TOTP, passkeys, recovery codes, magic links and API keys all work. Every admin route — the whole console: the user surface, metadata, webhook and log history, the digest, support diagnosis, config tuning, flagged-event review, AI settings — answers:
74
+
75
+
```json
76
+
{"error": {"code": "not_implemented_on_sqlite", "message": "the admin console requires a Postgres backend; this deployment runs on SQLite"}}
77
+
```
78
+
79
+
with `501`, before the token is looked at. This is one decision applied in one place (`httpapi.AdminOnly`), not a list of routes to maintain: the admin console's tables are this repo's own and Postgres-only, and `RequireAdmin` itself depends on the `operators` table, so there is no partial console to offer and no way for a SQLite deployment to have an operator at all. A `403 not_operator` would have been the easy answer and the wrong one — it tells a legitimate operator they personally lack access when the truth is that this backend has no console.
80
+
81
+
The one AI surface that is *not* under `/v1/admin` is `POST /v1/ask-ai`, which serves the widget to a signed-in end user. It is still unavailable on SQLite, because the provider behind it lives in the Postgres-only `settings` table: it answers `404 not_configured`, the same shape it gives on a Postgres deployment that has not configured a provider. One rule, stated once: **the AI-assisted surface needs `DATABASE_URL`.**
82
+
83
+
These variables are accepted but inert on SQLite, and the server says so at startup rather than letting an operator wonder — `SETTINGS_ENCRYPTION_KEY`, `WEBHOOK_URL`, `CLOUD_LOGGING`, `DIGEST_INTERVAL_HOURS`. `ENCRYPTION_KEY` is *not* in that list: TOTP and passkeys are cryden's own tables and work on both backends.
84
+
85
+
No migration step exists on SQLite. `main.go` calls cryden's own `sqlite.Migrate` at startup, which embeds its migrations and records what it applied, so a second boot is a no-op. The copy of those files under `migrations/sqlite/` is reference material, not what runs — see the README in that directory.
86
+
87
+
The connection is opened with three pragmas, all of them load-bearing: `foreign_keys(1)` (off by default, so the schema's `ON DELETE` clauses would silently not run), `busy_timeout(5000)` (zero by default, so a concurrent writer gets an immediate `SQLITE_BUSY` instead of waiting), and `journal_mode(WAL)`. The server verifies the first two on every boot with cryden's own `CheckPragmas` and refuses to start if the DSN and the driver have drifted apart.
88
+
89
+
**Backing up a SQLite deployment means copying `api.db`, `api.db-wal` and `api.db-shm` together**, or checkpointing first. With WAL, recent writes — including, on a fresh deployment, the entire schema — live in the `-wal` file until a checkpoint folds them into the main file, and there is no graceful shutdown here yet to force one on exit. Copying `api.db` alone can silently produce an empty database.
90
+
60
91
## Second factors
61
92
62
93
TOTP and passkeys are optional and all-or-nothing on `ENCRYPTION_KEY`: cryden refuses to construct an engine with a TOTP or WebAuthn store set and no encryption key (a TOTP secret must be recoverable in plaintext to check a code, so it is encrypted rather than hashed). With the key unset, both methods answer `404 totp_not_configured` / `404 passkeys_not_configured` per request, the same shape an unconfigured OAuth provider uses, rather than the server refusing to start.
Everything under `/v1/admin` requires an **operator** token: a valid access token whose `role` claim is `admin`. Operator status is this repo's own concept, not cryden's — it lives in its own `operators` table (`migrations/003_operators.*.sql`), and the claim is attached to the token at issue time by the `AccessTokenClaims` provider in `main.go`. An ordinary user's token carries no `role` claim at all, so "revoked operator", "never was one" and "no such user" are indistinguishable to a caller, deliberately.
211
242
243
+
On a SQLite deployment this entire section is unavailable and every route in it answers `501 not_implemented_on_sqlite` — the console's tables are Postgres-only. See [The two backends](#the-two-backends).
244
+
212
245
The first operator is created with `cmd/grant-operator`, from a machine with direct database access — deliberately not an HTTP bootstrap route, which would be needless attack surface reachable over the network:
returncfg, fmt.Errorf("one of DATABASE_URL or SQLITE_PATH is required — see README's note on the two backends")
350
+
}
351
+
ifcfg.DatabaseURL!=""&&cfg.SQLitePath!="" {
352
+
returncfg, fmt.Errorf("DATABASE_URL and SQLITE_PATH are mutually exclusive — set one; the admin console's tables are Postgres-only, so a SQLite deployment serves core auth and nothing under /v1/admin")
0 commit comments