@promptowl/contextnest-community 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CONFIGURATION.md CHANGED
@@ -1,181 +1,182 @@
1
- # Configuration
2
-
3
- All server configuration is driven by environment variables. Set them in your shell, in a `.env` file, in a Docker image, or via your process manager — however you deploy the rest of your stack.
4
-
5
- ## Auth modes
6
-
7
- The server supports two authentication modes. You pick one per instance with `AUTH_MODE`.
8
-
9
- ### `AUTH_MODE=key` (default)
10
-
11
- Every request requires an `Authorization: Bearer cnst_...` header. Use this for any multi-user or internet-facing deployment.
12
-
13
- - Users register via `POST /auth/register` (email + password) or log in with PromptOwl via `POST /auth/device` → `POST /auth/promptowl`.
14
- - API keys are per-user (`cnst_<64-hex>`) and can be scoped to a single nest for service-account use.
15
- - Rate-limited login / register / device-auth endpoints (sliding window, per IP + per email).
16
- - First PromptOwl-authenticated user becomes the server admin (atomic claim). Admin can invite teammates at `POST /auth/invite`.
17
-
18
- Clients include the token on every request:
19
-
20
- ```bash
21
- curl -H 'Authorization: Bearer cnst_<your-token>' http://your-server/nests
22
- ```
23
-
24
- ### `AUTH_MODE=open`
25
-
26
- No authentication required. Every request is attributed to the anonymous admin user (`00000000-...`). Everyone effectively owns every anon-created nest.
27
-
28
- **Use this only when:**
29
- - You're running locally for yourself (`bind 127.0.0.1`), or
30
- - You're on a trusted LAN and don't care about access controls, or
31
- - You're behind an upstream reverse proxy that already authenticates.
32
-
33
- **Don't use this when:**
34
- - The port is reachable from the public internet
35
- - Multiple people share the deployment and need isolated data
36
- - You need audit trails (every write shows up under "anonymous admin")
37
-
38
- The server prints a loud warning at startup when `AUTH_MODE=open` is active.
39
-
40
- ---
41
-
42
- ## Full env var reference
43
-
44
- | Var | Default | Purpose |
45
- |---|---|---|
46
- | `PORT` | `3838` | HTTP port the server listens on. |
47
- | `DATA_ROOT` | `./data` (relative to cwd) | Root directory for SQLite DB + nest filesystem vaults. Set to an absolute path in production so it's independent of where you `cd`'d from. |
48
- | `DATABASE_PATH` | `$DATA_ROOT/community.db` | Override the SQLite file location explicitly. (SQLite backend only.) |
49
- | `NEST_STORAGE_ROOT` | `$DATA_ROOT/nests` | Root directory for nest vault files (markdown + version history). Override to store nests separately from the DB — e.g. point it at a Cloud Storage (GCS) volume mount on Cloud Run (`/mnt/nests`) while the SQLite DB stays on `DATA_ROOT` off the mount. **Moving existing nests:** the app does not relocate files — copy `$DATA_ROOT/nests/*` to the new root (e.g. `gsutil rsync`) before switching, or existing nests will appear empty (DB rows present, files missing). On a GCS mount, also set `DRIFT_SCAN_INTERVAL_MS=0` to avoid constant FUSE scans, and run a single instance (GCS FUSE has no file locking — concurrent writers lose data). |
50
- | `DB_DRIVER` | `sqlite` | Database backend: `sqlite` (default) or `postgres`. When unset, the server infers `postgres` if `DATABASE_URL` or `CLOUD_SQL_CONNECTION_NAME` is set, else `sqlite`. Existing deployments need no change — they stay on SQLite. See [Database backends](#database-backends). |
51
- | `DATABASE_URL` | `""` | PostgreSQL connection string for a TCP connection, e.g. `postgres://user:pass@host:5432/dbname`. Setting it selects the Postgres backend (unless `DB_DRIVER=sqlite`). |
52
- | `CLOUD_SQL_CONNECTION_NAME` | `""` | Cloud SQL instance connection name (`project:region:instance`). When set, the server connects over the Cloud SQL Auth Proxy unix socket at `/cloudsql/<name>` — the recommended Cloud Run setup. Combine with `DB_USER`, `DB_PASSWORD`, `DB_NAME`. |
53
- | `DB_HOST` / `DB_PORT` / `DB_USER` / `DB_PASSWORD` / `DB_NAME` | `localhost` / `5432` / `""` / `""` / `""` | Discrete Postgres connection params, used when neither `CLOUD_SQL_CONNECTION_NAME` nor `DATABASE_URL` is set. |
54
- | `DB_POOL_MAX` | `10` | Max Postgres pool connections. Keep modest — Cloud SQL tiers cap total connections. |
55
- | `DB_SSL` | `false` | Set `true` to require TLS on a Postgres TCP connection (not needed over the Cloud SQL unix socket, which is already secure). |
56
- | `DB_SSL_CA` | `""` | Path to a CA certificate (PEM) for verify-ca/verify-full TLS when `DB_SSL=true`. |
57
- | `AUTH_MODE` | `key` | `key` or `open`. See above. |
58
- | `PROMPTOWL_API_URL` | `https://app.promptowl.ai` | PromptOwl's API origin — used for device auth, license validation, telemetry. Override for air-gapped or test setups. |
59
- | `PROMPTOWL_KEY` | `""` | Your PromptOwl Community License key (`pk_...`). Unlicensed instances still run and serve reads, but every write returns `503` until a valid key is installed. Can also be set via the browser License Setup Page, which persists it to `ENV_FILE_PATH`. |
60
- | `PROMPTOWL_SIGN_IN_GATE` | `open` | Restrict "Sign in with PromptOwl". `open` = anyone may; `admin-only` = only the license owner (admin) may, everyone else uses email/password (admin opens the login page with `?admin=1`); `disabled` = nobody may. Enforced server-side at `POST /auth/promptowl` and surfaced on the health endpoint. Unknown values fall back to `open`. |
61
- | `OFFICIAL_COMMUNITY_SSO_SECRET` | `""` | **Official deployment only — leave unset on self-hosted.** Shared HMAC secret enabling the one-click "Open Community" SSO auto-login from PromptOwl. Must exactly match the same-named var on PromptOwl. When unset, `GET /auth/sso` returns `404` and the feature is disabled; self-hosted users keep using the manual device-code flow. |
62
- | `PUBLIC_BASE_URL` | `""` | This server's canonical external URL (e.g. `https://community.promptowl.ai`). Checked against the SSO ticket's `aud` claim so a ticket minted for this server can't be replayed against another. Only relevant when `OFFICIAL_COMMUNITY_SSO_SECRET` is set; when unset, the audience check is skipped. |
63
- | `ENV_FILE_PATH` | `$DATA_ROOT/.env` | Path to the `.env` file the license install flow writes `PROMPTOWL_KEY` into (alongside existing vars), and which the server also reads at boot. Defaults **under `DATA_ROOT`** so the browser License Setup Page persists durably in containers — `$cwd` is `/app` in the official image (root-owned, discarded on container recreate), which silently lost the key. Override only if your writable, persisted `.env` lives elsewhere. In containers, providing `PROMPTOWL_KEY` directly via the environment also works and is read at boot. |
64
- | `TELEMETRY_ENABLED` | `"true"` (set to `"false"` to disable) | Batched, anonymized usage events sent to PromptOwl. Off disables the loop entirely. |
65
- | `TELEMETRY_INTERVAL_MS` | `3600000` (1 hour) | How often buffered telemetry is flushed to PromptOwl. |
66
- | `CORS_ORIGINS` | `*` in open mode; `http://localhost:5173,http://localhost:3838` in key mode | Comma-separated allowlist. Set to `*` to allow any origin (**only** safe in open mode — in key mode with Bearer tokens this enables CSRF). |
67
- | `MAX_BODY_BYTES` | `10485760` (10 MB) | Reject requests whose `Content-Length` exceeds this. Prevents giant-payload DoS. |
68
- | `LOGO_URL` | _(unset)_ | Custom logo shown in the UI header + login screen. Must start with `https://`, `http://`, or `data:image/` — other schemes (`file://`, relative, `javascript:`) are rejected with a warning and the bundled icon is used. |
69
-
70
- ---
71
-
72
- ## Database backends
73
-
74
- The governance/auth metadata (users, sessions, nests registry, stewards, reviews,
75
- versions index, comments, telemetry) is stored in a relational database. Two
76
- backends are supported:
77
-
78
- - **SQLite (default).** A single file at `DATABASE_PATH`. Zero-config, ideal for
79
- single-box and local deployments. Nothing changes for existing installs.
80
- - **PostgreSQL / Cloud SQL.** For deployments where local disk isn't durable
81
- (e.g. Google Cloud Run, where the container filesystem is ephemeral). Selected
82
- by setting `DB_DRIVER=postgres`, `DATABASE_URL`, or `CLOUD_SQL_CONNECTION_NAME`.
83
-
84
- On first connect the server creates its schema automatically (idempotent) — no
85
- manual migration step. There is no built-in SQLite→Postgres data migration; a new
86
- Postgres backend starts empty.
87
-
88
- > ⚠️ **Cloud Run durability — read this.** Cloud SQL persists only the *metadata
89
- > database*. The actual **document content and version history** live on the
90
- > filesystem under `$DATA_ROOT/nests/`, which on Cloud Run is **ephemeral** and is
91
- > lost when an instance is recycled. Using Cloud SQL alone does **not** make a
92
- > Cloud Run deployment fully durable. You must also put `$DATA_ROOT` on persistent,
93
- > shared storage e.g. mount a GCS bucket via Cloud Storage FUSE, or use a
94
- > persistent volumeotherwise documents will disappear on redeploy/scale-in.
95
-
96
- ### Cloud Run + Cloud SQL (PostgreSQL)
97
-
98
- Attach the Cloud SQL instance to the service (`--add-cloudsql-instances`) so the
99
- Auth Proxy unix socket appears at `/cloudsql/<connection-name>`, then:
100
-
101
- ```bash
102
- DB_DRIVER=postgres \
103
- CLOUD_SQL_CONNECTION_NAME=my-project:us-central1:contextnest \
104
- DB_USER=contextnest \
105
- DB_PASSWORD=*** \
106
- DB_NAME=contextnest \
107
- DATA_ROOT=/mnt/vault \ # mount GCS/persistent storage here — see warning above
108
- PROMPTOWL_KEY=pk_... \
109
- node dist/index.js
110
- ```
111
-
112
- Or with a plain TCP connection string (e.g. the Cloud SQL Proxy running on
113
- localhost, or a private IP):
114
-
115
- ```bash
116
- DATABASE_URL=postgres://contextnest:***@127.0.0.1:5432/contextnest \
117
- DB_DRIVER=postgres \
118
- DATA_ROOT=/mnt/vault \
119
- npm start
120
- ```
121
-
122
- ## Typical deployments
123
-
124
- ### Local dev / single user
125
-
126
- ```bash
127
- AUTH_MODE=open DATA_ROOT=./my-data npm run dev
128
- ```
129
-
130
- Or the default dev mode if `npm run dev` already sets `AUTH_MODE=open` in your scripts.
131
-
132
- ### Team / multi-user behind a reverse proxy
133
-
134
- ```bash
135
- AUTH_MODE=key \
136
- CORS_ORIGINS="https://team.example.com,https://admin.example.com" \
137
- DATA_ROOT=/var/lib/contextnest \
138
- PROMPTOWL_KEY=pk_... \
139
- npm start
140
- ```
141
-
142
- Terminate TLS at the proxy, forward `X-Forwarded-For` and `X-Real-IP` headers (the rate limiter reads them), and bind the server to `127.0.0.1` so only the proxy can reach it.
143
-
144
- ### Hosted / commercial SaaS
145
-
146
- Same as team, plus:
147
- - Run N instances behind a load balancer. SQLite + a shared filesystem is not a great long-term story for the metadata DB when scaling beyond one box — switch to the **PostgreSQL / Cloud SQL** backend (see [Database backends](#database-backends)) so all instances share one consistent database. The on-disk nest vault under `$DATA_ROOT/nests/` still needs durable shared storage.
148
- - Set `TELEMETRY_ENABLED=true` and a valid `PROMPTOWL_KEY` so usage rolls up to PromptOwl.
149
- - Rotate keys regularly via `DELETE /auth/keys/:id` + `POST /auth/keys`.
150
-
151
- ---
152
-
153
- ## access.yaml (optional, open + key mode)
154
-
155
- Drop an `access.yaml` at `$DATA_ROOT/access.yaml` to add a server-level ABAC layer — useful even in key mode for super-admins and group-based defaults.
156
-
157
- ```yaml
158
- mode: restricted
159
- allowed_users:
160
- - "*.acme.com" # email wildcard — anyone @acme.com
161
- - "partner@vendor.com" # exact match
162
- super_admins:
163
- - "ceo@acme.com" # admin on every nest: visibility, collaborators, stewards (not owner-only delete/transfer)
164
- groups:
165
- engineering:
166
- default_permission: write
167
- members:
168
- - "*.eng.acme.com"
169
- viewers:
170
- default_permission: read
171
- members:
172
- - "*.contractor.acme.com"
173
- ```
174
-
175
- See `STEWARDSHIP.md` for how super-admins and groups interact with per-nest stewardship.
176
-
177
- ---
178
-
179
- ## Reload
180
-
181
- Changing env vars requires a restart — the server reads them at boot. `tsx watch` in dev will pick up code changes automatically but not env changes.
1
+ # Configuration
2
+
3
+ All server configuration is driven by environment variables. Set them in your shell, in a `.env` file, in a Docker image, or via your process manager — however you deploy the rest of your stack.
4
+
5
+ ## Auth modes
6
+
7
+ The server supports two authentication modes. You pick one per instance with `AUTH_MODE`.
8
+
9
+ ### `AUTH_MODE=key` (default)
10
+
11
+ Every request requires an `Authorization: Bearer cnst_...` header. Use this for any multi-user or internet-facing deployment.
12
+
13
+ - Users register via `POST /auth/register` (email + password) or log in with PromptOwl via `POST /auth/device` → `POST /auth/promptowl`.
14
+ - API keys are per-user (`cnst_<64-hex>`) and can be scoped to a single nest for service-account use.
15
+ - Rate-limited login / register / device-auth endpoints (sliding window, per IP + per email).
16
+ - First PromptOwl-authenticated user becomes the server admin (atomic claim). Admin can invite teammates at `POST /auth/invite`.
17
+
18
+ Clients include the token on every request:
19
+
20
+ ```bash
21
+ curl -H 'Authorization: Bearer cnst_<your-token>' http://your-server/nests
22
+ ```
23
+
24
+ ### `AUTH_MODE=open`
25
+
26
+ No authentication required. Every request is attributed to the anonymous admin user (`00000000-...`). Everyone effectively owns every anon-created nest.
27
+
28
+ **Use this only when:**
29
+ - You're running locally for yourself (`bind 127.0.0.1`), or
30
+ - You're on a trusted LAN and don't care about access controls, or
31
+ - You're behind an upstream reverse proxy that already authenticates.
32
+
33
+ **Don't use this when:**
34
+ - The port is reachable from the public internet
35
+ - Multiple people share the deployment and need isolated data
36
+ - You need audit trails (every write shows up under "anonymous admin")
37
+
38
+ The server prints a loud warning at startup when `AUTH_MODE=open` is active.
39
+
40
+ ---
41
+
42
+ ## Full env var reference
43
+
44
+ | Var | Default | Purpose |
45
+ |---|---|---|
46
+ | `PORT` | `3838` | HTTP port the server listens on. |
47
+ | `DATA_ROOT` | `./data` (relative to cwd) | Root directory for SQLite DB + nest filesystem vaults. Set to an absolute path in production so it's independent of where you `cd`'d from. |
48
+ | `DATABASE_PATH` | `$DATA_ROOT/community.db` | Override the SQLite file location explicitly. (SQLite backend only.) |
49
+ | `NEST_STORAGE_ROOT` | `$DATA_ROOT/nests` | Root directory for nest vault files (markdown + version history). Override to store nests separately from the DB — e.g. point it at a Cloud Storage (GCS) volume mount on Cloud Run (`/mnt/nests`) while the SQLite DB stays on `DATA_ROOT` off the mount. **Moving existing nests:** the app does not relocate files — copy `$DATA_ROOT/nests/*` to the new root (e.g. `gsutil rsync`) before switching, or existing nests will appear empty (DB rows present, files missing). On a GCS mount, also set `DRIFT_SCAN_INTERVAL_MS=0` to avoid constant FUSE scans, and run a single instance (GCS FUSE has no file locking — concurrent writers lose data). |
50
+ | `DB_DRIVER` | `sqlite` | Database backend: `sqlite` (default) or `postgres`. When unset, the server infers `postgres` if `DATABASE_URL` or `CLOUD_SQL_CONNECTION_NAME` is set, else `sqlite`. Existing deployments need no change — they stay on SQLite. See [Database backends](#database-backends). |
51
+ | `DATABASE_URL` | `""` | PostgreSQL connection string for a TCP connection, e.g. `postgres://user:pass@host:5432/dbname`. Setting it selects the Postgres backend (unless `DB_DRIVER=sqlite`). |
52
+ | `CLOUD_SQL_CONNECTION_NAME` | `""` | Cloud SQL instance connection name (`project:region:instance`). When set, the server connects over the Cloud SQL Auth Proxy unix socket at `/cloudsql/<name>` — the recommended Cloud Run setup. Combine with `DB_USER`, `DB_PASSWORD`, `DB_NAME`. |
53
+ | `DB_HOST` / `DB_PORT` / `DB_USER` / `DB_PASSWORD` / `DB_NAME` | `localhost` / `5432` / `""` / `""` / `""` | Discrete Postgres connection params, used when neither `CLOUD_SQL_CONNECTION_NAME` nor `DATABASE_URL` is set. |
54
+ | `DB_POOL_MAX` | `10` | Max Postgres pool connections. Keep modest — Cloud SQL tiers cap total connections. |
55
+ | `DB_SSL` | `false` | Set `true` to require TLS on a Postgres TCP connection (not needed over the Cloud SQL unix socket, which is already secure). |
56
+ | `DB_SSL_CA` | `""` | Path to a CA certificate (PEM) for verify-ca/verify-full TLS when `DB_SSL=true`. |
57
+ | `AUTH_MODE` | `key` | `key` or `open`. See above. |
58
+ | `PROMPTOWL_API_URL` | `https://app.promptowl.ai` | PromptOwl's API origin — used for device auth, license validation, telemetry. Override for air-gapped or test setups. |
59
+ | `PROMPTOWL_KEY` | `""` | Your PromptOwl Community License key (`pk_...`). Unlicensed instances still run and serve reads, but every write returns `503` until a valid key is installed. Can also be set via the browser License Setup Page, which persists it to `ENV_FILE_PATH`. |
60
+ | `PROMPTOWL_SIGN_IN_GATE` | `open` | Restrict "Sign in with PromptOwl". `open` = anyone may; `admin-only` = only the license owner (admin) may, everyone else uses email/password (admin opens the login page with `?admin=1`); `disabled` = nobody may. Enforced server-side at `POST /auth/promptowl` and surfaced on the health endpoint. Unknown values fall back to `open`. |
61
+ | `OFFICIAL_COMMUNITY_SSO_SECRET` | `""` | **Official deployment only — leave unset on self-hosted.** Shared HMAC secret enabling the one-click "Open Community" SSO auto-login from PromptOwl. Must exactly match the same-named var on PromptOwl. When unset, `GET /auth/sso` returns `404` and the feature is disabled; self-hosted users keep using the manual device-code flow. |
62
+ | `PUBLIC_BASE_URL` | `""` | This server's canonical external URL (e.g. `https://community.promptowl.ai`). Checked against the SSO ticket's `aud` claim so a ticket minted for this server can't be replayed against another. Only relevant when `OFFICIAL_COMMUNITY_SSO_SECRET` is set; when unset, the audience check is skipped. |
63
+ | `ENV_FILE_PATH` | `$DATA_ROOT/.env` | Path to the `.env` file the license install flow writes `PROMPTOWL_KEY` into (alongside existing vars), and which the server also reads at boot. Defaults **under `DATA_ROOT`** so the browser License Setup Page persists durably in containers — `$cwd` is `/app` in the official image (root-owned, discarded on container recreate), which silently lost the key. Override only if your writable, persisted `.env` lives elsewhere. In containers, providing `PROMPTOWL_KEY` directly via the environment also works and is read at boot. |
64
+ | `TELEMETRY_ENABLED` | `"true"` (set to `"false"` to disable) | Batched, anonymized usage events sent to PromptOwl. Off disables the loop entirely. |
65
+ | `TELEMETRY_INTERVAL_MS` | `3600000` (1 hour) | How often buffered telemetry is flushed to PromptOwl. |
66
+ | `CORS_ORIGINS` | `*` in open mode; `http://localhost:5173,http://localhost:3838` in key mode | Comma-separated allowlist. Set to `*` to allow any origin (**only** safe in open mode — in key mode with Bearer tokens this enables CSRF). |
67
+ | `MAX_BODY_BYTES` | `10485760` (10 MB) | Reject requests whose `Content-Length` exceeds this. Prevents giant-payload DoS. |
68
+ | `LOGO_URL` | _(unset)_ | Custom logo shown in the UI header + login screen. Must start with `https://`, `http://`, or `data:image/` — other schemes (`file://`, relative, `javascript:`) are rejected with a warning and the bundled icon is used. |
69
+ | `SLACK_WEBHOOK_URL` | _(unset — connector off)_ | Slack incoming-webhook URL for governance-event notifications (review submitted/approved/rejected, collaborator added). `https://` only — the URL embeds a secret. Also editable from Settings in the UI. |
70
+
71
+ ---
72
+
73
+ ## Database backends
74
+
75
+ The governance/auth metadata (users, sessions, nests registry, stewards, reviews,
76
+ versions index, comments, telemetry) is stored in a relational database. Two
77
+ backends are supported:
78
+
79
+ - **SQLite (default).** A single file at `DATABASE_PATH`. Zero-config, ideal for
80
+ single-box and local deployments. Nothing changes for existing installs.
81
+ - **PostgreSQL / Cloud SQL.** For deployments where local disk isn't durable
82
+ (e.g. Google Cloud Run, where the container filesystem is ephemeral). Selected
83
+ by setting `DB_DRIVER=postgres`, `DATABASE_URL`, or `CLOUD_SQL_CONNECTION_NAME`.
84
+
85
+ On first connect the server creates its schema automatically (idempotent) no
86
+ manual migration step. There is no built-in SQLite→Postgres data migration; a new
87
+ Postgres backend starts empty.
88
+
89
+ > ⚠️ **Cloud Run durability read this.** Cloud SQL persists only the *metadata
90
+ > database*. The actual **document content and version history** live on the
91
+ > filesystem under `$DATA_ROOT/nests/`, which on Cloud Run is **ephemeral** and is
92
+ > lost when an instance is recycled. Using Cloud SQL alone does **not** make a
93
+ > Cloud Run deployment fully durable. You must also put `$DATA_ROOT` on persistent,
94
+ > shared storagee.g. mount a GCS bucket via Cloud Storage FUSE, or use a
95
+ > persistent volume — otherwise documents will disappear on redeploy/scale-in.
96
+
97
+ ### Cloud Run + Cloud SQL (PostgreSQL)
98
+
99
+ Attach the Cloud SQL instance to the service (`--add-cloudsql-instances`) so the
100
+ Auth Proxy unix socket appears at `/cloudsql/<connection-name>`, then:
101
+
102
+ ```bash
103
+ DB_DRIVER=postgres \
104
+ CLOUD_SQL_CONNECTION_NAME=my-project:us-central1:contextnest \
105
+ DB_USER=contextnest \
106
+ DB_PASSWORD=*** \
107
+ DB_NAME=contextnest \
108
+ DATA_ROOT=/mnt/vault \ # mount GCS/persistent storage here — see warning above
109
+ PROMPTOWL_KEY=pk_... \
110
+ node dist/index.js
111
+ ```
112
+
113
+ Or with a plain TCP connection string (e.g. the Cloud SQL Proxy running on
114
+ localhost, or a private IP):
115
+
116
+ ```bash
117
+ DATABASE_URL=postgres://contextnest:***@127.0.0.1:5432/contextnest \
118
+ DB_DRIVER=postgres \
119
+ DATA_ROOT=/mnt/vault \
120
+ npm start
121
+ ```
122
+
123
+ ## Typical deployments
124
+
125
+ ### Local dev / single user
126
+
127
+ ```bash
128
+ AUTH_MODE=open DATA_ROOT=./my-data npm run dev
129
+ ```
130
+
131
+ Or the default dev mode if `npm run dev` already sets `AUTH_MODE=open` in your scripts.
132
+
133
+ ### Team / multi-user behind a reverse proxy
134
+
135
+ ```bash
136
+ AUTH_MODE=key \
137
+ CORS_ORIGINS="https://team.example.com,https://admin.example.com" \
138
+ DATA_ROOT=/var/lib/contextnest \
139
+ PROMPTOWL_KEY=pk_... \
140
+ npm start
141
+ ```
142
+
143
+ Terminate TLS at the proxy, forward `X-Forwarded-For` and `X-Real-IP` headers (the rate limiter reads them), and bind the server to `127.0.0.1` so only the proxy can reach it.
144
+
145
+ ### Hosted / commercial SaaS
146
+
147
+ Same as team, plus:
148
+ - Run N instances behind a load balancer. SQLite + a shared filesystem is not a great long-term story for the metadata DB when scaling beyond one box — switch to the **PostgreSQL / Cloud SQL** backend (see [Database backends](#database-backends)) so all instances share one consistent database. The on-disk nest vault under `$DATA_ROOT/nests/` still needs durable shared storage.
149
+ - Set `TELEMETRY_ENABLED=true` and a valid `PROMPTOWL_KEY` so usage rolls up to PromptOwl.
150
+ - Rotate keys regularly via `DELETE /auth/keys/:id` + `POST /auth/keys`.
151
+
152
+ ---
153
+
154
+ ## access.yaml (optional, open + key mode)
155
+
156
+ Drop an `access.yaml` at `$DATA_ROOT/access.yaml` to add a server-level ABAC layer — useful even in key mode for super-admins and group-based defaults.
157
+
158
+ ```yaml
159
+ mode: restricted
160
+ allowed_users:
161
+ - "*.acme.com" # email wildcard — anyone @acme.com
162
+ - "partner@vendor.com" # exact match
163
+ super_admins:
164
+ - "ceo@acme.com" # admin on every nest: visibility, collaborators, stewards (not owner-only delete/transfer)
165
+ groups:
166
+ engineering:
167
+ default_permission: write
168
+ members:
169
+ - "*.eng.acme.com"
170
+ viewers:
171
+ default_permission: read
172
+ members:
173
+ - "*.contractor.acme.com"
174
+ ```
175
+
176
+ See `STEWARDSHIP.md` for how super-admins and groups interact with per-nest stewardship.
177
+
178
+ ---
179
+
180
+ ## Reload
181
+
182
+ Changing env vars requires a restart — the server reads them at boot. `tsx watch` in dev will pick up code changes automatically but not env changes.