home-hosted 0.6.7 → 0.6.8

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/AGENTS.md CHANGED
@@ -6,11 +6,12 @@ and serves a UI. User docs: `README.md`, `docs/SERVERS.md` (entries and port con
6
6
  `docs/NOTIFICATIONS.md`, `docs/DDNS.md`; UI authors: `docs/UI_CREATION.md`.
7
7
 
8
8
  State lives only in `$HHOSTED_HOME` (default `~/.home-hosted`): `servers.config.json`,
9
- `.control-secrets.json` (0600: password hash, API token hash, Telegram bot token, DDNS credentials),
10
- `.logs/`, `.tls/`, `.backups/`, `.ui/`, `.state/` (a persistent entry's nanny state, plus its 0600
11
- spawn spec until the nanny reads it, plus `ddns.json` — the last address each target confirmed), and
12
- `run.json` — the live daemon's pid/url/token, 0600. The package ships **no servers**: never commit a
13
- config, a seed entry, or a path that names one.
9
+ `.control-secrets.json` (0600: password hash, API token hash, Telegram bot token, and DDNS
10
+ credentials sealed with AES-256-GCM under `HHOSTED_DDNS_SECRET`), `.logs/`, `.tls/`, `.backups/`,
11
+ `.ui/`, `.state/` (a persistent entry's nanny state, plus its 0600 spawn spec until the nanny reads
12
+ it, plus `ddns.json` — the last address each target confirmed), and `run.json` — the live daemon's
13
+ pid/url/token, 0600. The package ships **no servers**: never commit a config, a seed entry, or a path
14
+ that names one.
14
15
 
15
16
  ## Commands
16
17
 
@@ -149,7 +150,9 @@ a minor without one.
149
150
  envelope `{ message, code, detail }`. Never hand-roll `c.json({ error })`.
150
151
  - Document routes with `describeRoute` + `jsonBody(schema)`; `jsonBody` needs a real schema.
151
152
  - Patch schemas carry no defaults; nested groups (`restart`/`health`/`stop`/`auth`/`tls`/`telegram`/
152
- `http`) merge key-by-key, and an explicit `null` clears a key.
153
+ `http`) merge key-by-key, and an explicit `null` clears a key. `ddns` is the exception: its
154
+ `accounts`/`domains` lists are **replaced** (a merge cannot express removing a hostname), and only
155
+ `ipv4`/`ipv6` merge.
153
156
  - Two-sided bounds read inclusively (`'1 <= number.integer <= 512'`). `test/shared/contracts.test.ts`
154
157
  pins every boundary and the patch/schema parity — update it with any schema change.
155
158
  - A new field in a **response** DTO is optional (`'x?'`) and its clients read it defensively. An
@@ -165,6 +168,12 @@ a minor without one.
165
168
  - A destructive action that is one click away confirms in a **popover** (`KillPortButton.vue`),
166
169
  never by arming the same button for a second press: an impatient double click on an arming
167
170
  button fires it. Keep the safe choice first in the popover's tab order.
171
+ - **Docs ship with the change.** A user-visible change updates the docs that describe it
172
+ (`README.md`, `docs/*.md`) in the same commit — do not forget them; a stale doc is a bug like
173
+ stale code.
174
+ - **UIs move together.** `uis/stock` is not the only client: a change to it — or to a shared
175
+ contract it reads — lands in every other UI under `uis/`, and each altered UI bumps its
176
+ `ui.json` per the rule above.
168
177
 
169
178
  ## Compatibility
170
179
 
@@ -194,6 +203,9 @@ either is a last resort, and never an accidental one.
194
203
  - **Unknown keys are dropped from the resolved config, kept on disk, and listed in a startup
195
204
  warning.** Dropping one is the normal way a newer config looks here, so it must never fail the
196
205
  group it sits in (that used to reset `control` — port, bind, auth policy — to schema defaults).
206
+ A *write* to a group validates the same tolerant way (`parseTolerant` via the store), so a key this
207
+ release does not know never blocks a save — removing a key from a schema must not brick the page
208
+ that writes it.
197
209
  - **Migrations ship inside the package** (`src/config/migrations.ts`), are ordered, idempotent and
198
210
  described in one line each. `home-hosted migrate` prints the plan, keeps `servers.config.json.bak`,
199
211
  refuses to write a config it cannot read, and needs consent: `--yes`, `HHOSTED_MIGRATE=allow`, or a
@@ -348,7 +360,8 @@ either is a last resort, and never an accidental one.
348
360
  - **Server field**: `serverSchema` + its patch in contracts, merge keys in `config/store.ts` when
349
361
  nested, the form in `uis/stock/src/components/settings/`, and the contract tests.
350
362
  - **UI**: a new `uis/<name>/` with a `vite.config.ts` from the shared factory;
351
- `node scripts/build-uis.mjs <name> --zip`. The contract is `docs/UI_CREATION.md`.
363
+ `node scripts/build-uis.mjs <name> --zip`. The contract is `docs/UI_CREATION.md`. A change to one
364
+ UI (`stock`, `noc-console`) is carried to the others in the same commit.
352
365
  - **Capability**: a stateless `src/providers/*` returning plain data; a DDNS provider is one file plus
353
366
  a registry line (`src/providers/ddns/index.ts`), and the settings form, credentials and validation
354
367
  follow from its metadata — see `docs/DDNS.md`.
package/README.md CHANGED
@@ -375,8 +375,10 @@ Everything binds `127.0.0.1` until you say otherwise.
375
375
  never taken from the message, and anything the panel supervises is refused, not killed. A server
376
376
  that [restarts itself](./docs/SERVERS.md#when-a-program-restarts-itself) can be followed, or replaced
377
377
  with a supervised copy.
378
- - **Secrets never enter the config**: the password hash, the API token hash and the Telegram bot
379
- token live in `$HHOSTED_HOME/.control-secrets.json` with mode `0600`; the TLS pair in `.tls/`.
378
+ - **Secrets never enter the config**: the password hash, the API token hash, the Telegram bot
379
+ token and the DDNS credentials live in `$HHOSTED_HOME/.control-secrets.json` with mode `0600`; the
380
+ TLS pair in `.tls/`. DDNS credentials are sealed with AES-256-GCM under `HHOSTED_DDNS_SECRET`
381
+ (default `hh` — set your own).
380
382
  - **Behind a proxy** turn on `trustProxy` and let `cookieSecure: auto` add `Secure` on https, or
381
383
  upload a PEM pair and let home-hosted terminate TLS itself.
382
384
 
@@ -403,9 +405,11 @@ tokens stay in the secrets file. **Providers and the config shape: [DDNS.md](./d
403
405
  ## 💾 Backups
404
406
 
405
407
  **Settings → Backups** archives the config, secrets, TLS pair and every data directory your entries
406
- declare — an ordinary `.zip`, or WinZip AES-256 with a password, restored per path. Known build output
407
- and dependency directories (`node_modules`, `dist`, `.next`, framework caches) are skipped per entry;
408
- `backupIgnoreGenerated: false` captures them anyway.
408
+ declare — an ordinary `.zip`, or WinZip AES-256 with a password, restored per path. *Create backup…*
409
+ lists what the archive will hold and lets you drop any item; *Restore…* shows the same list from an
410
+ archive before anything is written. Known build output and dependency directories (`node_modules`,
411
+ `dist`, `.next`, framework caches) are skipped per entry; `backupIgnoreGenerated: false` captures them
412
+ anyway.
409
413
 
410
414
  <details>
411
415
  <summary><b>🚚 One archive is a whole setup</b></summary>
@@ -416,7 +420,9 @@ archive and restore. Definitions come back, data lands where *this* machine's co
416
420
 
417
421
  It works because an archive carries its own `servers.config.json` and paths are matched by the
418
422
  **declaration** (`omniroute:DATA_DIR`), not by an absolute path from the source machine. A restore never
419
- writes where no config declares.
423
+ writes where no config declares. A declaration using `{projectDir}`, `{dataRoot}` or `{home}` follows
424
+ the restoring panel (`{projectDir}` is `HHOSTED_PROJECT` or the panel's cwd, not `HHOSTED_HOME`); a
425
+ literal absolute path is restored to that same path.
420
426
 
421
427
  </details>
422
428