home-hosted 0.6.6 → 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 +37 -20
- package/README.md +28 -12
- package/dist/cli.js +3010 -898
- package/dist/cli.js.map +1 -1
- package/docs/DDNS.md +84 -0
- package/docs/NOTIFICATIONS.md +3 -0
- package/docs/UI_CREATION.md +1 -1
- package/package.json +2 -1
- package/uis/stock/dist/assets/index-B_4sGjNj.css +2 -0
- package/uis/stock/dist/assets/{index-B8A85L38.js → index-BuB3VX_S.js} +7 -7
- package/uis/stock/dist/index.html +2 -2
- package/uis/stock/dist/assets/index-DMo6krK0.css +0 -2
package/AGENTS.md
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
`home-hosted` is a Node 24 / TypeScript harness for self-hosted servers: `up` starts a Hono/srvx
|
|
4
4
|
panel (default `127.0.0.1:3999`) that supervises the entries in `$HHOSTED_HOME/servers.config.json`
|
|
5
5
|
and serves a UI. User docs: `README.md`, `docs/SERVERS.md` (entries and port conflicts),
|
|
6
|
-
`docs/NOTIFICATIONS.md`; UI authors: `docs/UI_CREATION.md`.
|
|
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
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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.
|
|
13
15
|
|
|
14
16
|
## Commands
|
|
15
17
|
|
|
@@ -91,14 +93,15 @@ a minor without one.
|
|
|
91
93
|
`processCarriesServerId` for the environment marker and `processTreePids` for tree ownership),
|
|
92
94
|
`identity` (which port holder is this entry's own successor: marker first, then resolved image +
|
|
93
95
|
argv), `nanny` (a persistent entry's spec/state files and the liveness rules for one),
|
|
94
|
-
`log-tail` (an offset reader that survives rotation and truncation),
|
|
96
|
+
`log-tail` (an offset reader that survives rotation and truncation), `ddns/` (one `DdnsProvider` per
|
|
97
|
+
registrar over one HTTP call each, plus `ip` and `zone` helpers), health-check, host, telegram,
|
|
95
98
|
archive.
|
|
96
99
|
- `src/services/` — stateful orchestration: supervisor, control-server, config-watch, state,
|
|
97
|
-
auth + exposure, dependencies, history, log-buffer/log-files, notifications, host-monitor,
|
|
98
|
-
tls, ui, plus `init` (the scaffold behind `home-hosted init`: a manifest, a `.gitignore`,
|
|
99
|
-
prompts stay in the CLI), `nanny` (the process a persistent entry runs under) and
|
|
100
|
-
tailer that feeds its lines to the supervisor). It names no server — the scaffold
|
|
101
|
-
neutral as the supervisor.
|
|
100
|
+
auth + exposure, dependencies, history, log-buffer/log-files, notifications, host-monitor, ddns,
|
|
101
|
+
backups, tls, ui, plus `init` (the scaffold behind `home-hosted init`: a manifest, a `.gitignore`,
|
|
102
|
+
and the prompts stay in the CLI), `nanny` (the process a persistent entry runs under) and
|
|
103
|
+
`log-relay` (the tailer that feeds its lines to the supervisor). It names no server — the scaffold
|
|
104
|
+
must stay as neutral as the supervisor.
|
|
102
105
|
- `src/middleware/auth.ts` — the `/api/*` guard, and `requestIdentity()`, the one place a request's
|
|
103
106
|
credentials are read: the `hh_session` cookie or `Authorization: Bearer <api token>`. A token is
|
|
104
107
|
a first-class credential (same authority as a signed-in browser) and is verified from the secrets
|
|
@@ -127,10 +130,10 @@ a minor without one.
|
|
|
127
130
|
- `scripts/` — `build-uis.mjs` (build one UI, optionally zip it), `typecheck-uis.mjs`,
|
|
128
131
|
`capture-media.mjs` (the README's media), `check-release-version.mjs` and `release-notes.mjs`
|
|
129
132
|
(used by the release workflow), `dev.mjs` (`pnpm dev`).
|
|
130
|
-
- `docs/` — the topic docs (`SERVERS.md`, `NOTIFICATIONS.md`, `UI_CREATION.md`),
|
|
131
|
-
(hand-written UI examples in four directions) and `media/*`: their screenshots plus
|
|
132
|
-
`tour.gif`, all regenerated by `pnpm run media`. Only the
|
|
133
|
-
media is a build artifact.
|
|
133
|
+
- `docs/` — the topic docs (`SERVERS.md`, `NOTIFICATIONS.md`, `DDNS.md`, `UI_CREATION.md`),
|
|
134
|
+
`mockups/*.html` (hand-written UI examples in four directions) and `media/*`: their screenshots plus
|
|
135
|
+
the README's `tour.gif`, all regenerated by `pnpm run media`. Only the four `.md` files are
|
|
136
|
+
published; the media is a build artifact.
|
|
134
137
|
|
|
135
138
|
## Conventions
|
|
136
139
|
|
|
@@ -147,7 +150,9 @@ a minor without one.
|
|
|
147
150
|
envelope `{ message, code, detail }`. Never hand-roll `c.json({ error })`.
|
|
148
151
|
- Document routes with `describeRoute` + `jsonBody(schema)`; `jsonBody` needs a real schema.
|
|
149
152
|
- Patch schemas carry no defaults; nested groups (`restart`/`health`/`stop`/`auth`/`tls`/`telegram`/
|
|
150
|
-
`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.
|
|
151
156
|
- Two-sided bounds read inclusively (`'1 <= number.integer <= 512'`). `test/shared/contracts.test.ts`
|
|
152
157
|
pins every boundary and the patch/schema parity — update it with any schema change.
|
|
153
158
|
- A new field in a **response** DTO is optional (`'x?'`) and its clients read it defensively. An
|
|
@@ -163,6 +168,12 @@ a minor without one.
|
|
|
163
168
|
- A destructive action that is one click away confirms in a **popover** (`KillPortButton.vue`),
|
|
164
169
|
never by arming the same button for a second press: an impatient double click on an arming
|
|
165
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.
|
|
166
177
|
|
|
167
178
|
## Compatibility
|
|
168
179
|
|
|
@@ -192,6 +203,9 @@ either is a last resort, and never an accidental one.
|
|
|
192
203
|
- **Unknown keys are dropped from the resolved config, kept on disk, and listed in a startup
|
|
193
204
|
warning.** Dropping one is the normal way a newer config looks here, so it must never fail the
|
|
194
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.
|
|
195
209
|
- **Migrations ship inside the package** (`src/config/migrations.ts`), are ordered, idempotent and
|
|
196
210
|
described in one line each. `home-hosted migrate` prints the plan, keeps `servers.config.json.bak`,
|
|
197
211
|
refuses to write a config it cannot read, and needs consent: `--yes`, `HHOSTED_MIGRATE=allow`, or a
|
|
@@ -346,11 +360,14 @@ either is a last resort, and never an accidental one.
|
|
|
346
360
|
- **Server field**: `serverSchema` + its patch in contracts, merge keys in `config/store.ts` when
|
|
347
361
|
nested, the form in `uis/stock/src/components/settings/`, and the contract tests.
|
|
348
362
|
- **UI**: a new `uis/<name>/` with a `vite.config.ts` from the shared factory;
|
|
349
|
-
`node scripts/build-uis.mjs <name> --zip`. The contract is `docs/UI_CREATION.md`.
|
|
350
|
-
-
|
|
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.
|
|
365
|
+
- **Capability**: a stateless `src/providers/*` returning plain data; a DDNS provider is one file plus
|
|
366
|
+
a registry line (`src/providers/ddns/index.ts`), and the settings form, credentials and validation
|
|
367
|
+
follow from its metadata — see `docs/DDNS.md`.
|
|
351
368
|
|
|
352
369
|
## Publishing
|
|
353
370
|
|
|
354
|
-
`pnpm pack` runs `prepack` (a full build) and ships `bin/`, `dist/`, `uis/stock/dist`, `README.md`, `docs/
|
|
355
|
-
`docs/NOTIFICATIONS.md`, `docs/UI_CREATION.md`, `AGENTS.md` and `LICENSE`. The bin falls back to tsx so `pnpm link` works before a
|
|
371
|
+
`pnpm pack` runs `prepack` (a full build) and ships `bin/`, `dist/`, `uis/stock/dist`, `README.md`, `docs/DDNS.md`,
|
|
372
|
+
`docs/SERVERS.md`, `docs/NOTIFICATIONS.md`, `docs/UI_CREATION.md`, `AGENTS.md` and `LICENSE`. The bin falls back to tsx so `pnpm link` works before a
|
|
356
373
|
build; `vue`/`vue-router` are devDependencies because the UIs are prebuilt.
|
package/README.md
CHANGED
|
@@ -255,7 +255,8 @@ can be told what to be: *"Help me build a UI for home-hosted: nostalgic game the
|
|
|
255
255
|
| 📈 **Resources** | CPU and RSS of the whole process tree, with an optional memory ceiling that triggers a restart. |
|
|
256
256
|
| 🌡️ **Host vitals** | Load, memory, swap, disk and CPU temperature, with thresholds that notify once and again on recovery. |
|
|
257
257
|
| 🤖 **Token API** | Scripts and agents drive it with `Authorization: Bearer` — no browser, no session. [↑](#-agents-scripts-and-tools) |
|
|
258
|
-
| 🔔 **Notifications** | Telegram on crash, unhealthy, forced restart, recovery
|
|
258
|
+
| 🔔 **Notifications** | Telegram on crash, unhealthy, forced restart, recovery, host thresholds and DNS changes — [setup here](./docs/NOTIFICATIONS.md). |
|
|
259
|
+
| 🌐 **Dynamic DNS** | Keep hostnames pointed at your public IP — Cloudflare, Namecheap, Spaceship, Porkbun, GoDaddy, Gandi and more. [DDNS.md](./docs/DDNS.md) |
|
|
259
260
|
| 💾 **Backups** | One click for config, secrets, TLS and your declared data directories — plain `.zip`, or AES-256 with a password, restored per path. |
|
|
260
261
|
| 🎨 **BYOU — Bring Your Own UI** | Upload a static build, `ui-update` to follow its releases, `ui-revert` to go back. [UI_CREATION.md](./docs/UI_CREATION.md) |
|
|
261
262
|
| 🔐 **Security** | Cookie sessions, API tokens, scrypt hashes, per-IP lockout, optional TLS, and a refusal to expose itself without a password. |
|
|
@@ -293,8 +294,8 @@ restarts itself), and how hand-edits are validated: [SERVERS.md](./docs/SERVERS.
|
|
|
293
294
|
| `home-hosted down` | stop it cleanly — supervised processes included, persistent entries left running |
|
|
294
295
|
| `home-hosted restart` | `down`, then `up` |
|
|
295
296
|
| `home-hosted status` | pid, URL, health, uptime, state and log paths (`--json` for scripts) |
|
|
296
|
-
| `home-hosted start <id>` | start one server
|
|
297
|
-
| `home-hosted stop <id>` | stop one server
|
|
297
|
+
| `home-hosted start <id>` | start one server — and anything it `dependsOn` |
|
|
298
|
+
| `home-hosted stop <id>` | stop one server, nothing else |
|
|
298
299
|
| `home-hosted set-password` | set the panel password without opening a browser |
|
|
299
300
|
| `home-hosted set-token` | set the API token scripts and agents use (`--generate`, `--clear`) |
|
|
300
301
|
| `home-hosted migrate` | bring `servers.config.json` up to this release's schema (`--dry-run`, `--yes`) |
|
|
@@ -374,8 +375,10 @@ Everything binds `127.0.0.1` until you say otherwise.
|
|
|
374
375
|
never taken from the message, and anything the panel supervises is refused, not killed. A server
|
|
375
376
|
that [restarts itself](./docs/SERVERS.md#when-a-program-restarts-itself) can be followed, or replaced
|
|
376
377
|
with a supervised copy.
|
|
377
|
-
- **Secrets never enter the config**: the password hash, the API token hash
|
|
378
|
-
token live in `$HHOSTED_HOME/.control-secrets.json` with mode `0600`; the
|
|
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).
|
|
379
382
|
- **Behind a proxy** turn on `trustProxy` and let `cookieSecure: auto` add `Secure` on https, or
|
|
380
383
|
upload a PEM pair and let home-hosted terminate TLS itself.
|
|
381
384
|
|
|
@@ -384,18 +387,29 @@ Everything binds `127.0.0.1` until you say otherwise.
|
|
|
384
387
|
## 🔔 Notifications
|
|
385
388
|
|
|
386
389
|
Telegram, when something happens while you are not looking: a server that gave up restarting, a
|
|
387
|
-
failing health check, a forced restart, a recovery,
|
|
388
|
-
temperature). Opt-in, rate-limited per server *and* reason, and the bot token
|
|
389
|
-
file. **Two minutes of setup: [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md).**
|
|
390
|
+
failing health check, a forced restart, a recovery, a host threshold (disk, memory, swap, load,
|
|
391
|
+
temperature), or a dynamic DNS change. Opt-in, rate-limited per server *and* reason, and the bot token
|
|
392
|
+
stays in the secrets file. **Two minutes of setup: [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md).**
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## 🌐 Dynamic DNS
|
|
397
|
+
|
|
398
|
+
**Settings → Dynamic DNS** keeps a list of hostnames pointed at this machine's public IP — add an
|
|
399
|
+
account, paste its credentials, add hostnames. The panel checks the address on an interval and calls a
|
|
400
|
+
provider only when it actually changed, and the last confirmed address survives a restart. Provider
|
|
401
|
+
tokens stay in the secrets file. **Providers and the config shape: [DDNS.md](./docs/DDNS.md).**
|
|
390
402
|
|
|
391
403
|
---
|
|
392
404
|
|
|
393
405
|
## 💾 Backups
|
|
394
406
|
|
|
395
407
|
**Settings → Backups** archives the config, secrets, TLS pair and every data directory your entries
|
|
396
|
-
declare — an ordinary `.zip`, or WinZip AES-256 with a password, restored per path.
|
|
397
|
-
and
|
|
398
|
-
|
|
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.
|
|
399
413
|
|
|
400
414
|
<details>
|
|
401
415
|
<summary><b>🚚 One archive is a whole setup</b></summary>
|
|
@@ -406,7 +420,9 @@ archive and restore. Definitions come back, data lands where *this* machine's co
|
|
|
406
420
|
|
|
407
421
|
It works because an archive carries its own `servers.config.json` and paths are matched by the
|
|
408
422
|
**declaration** (`omniroute:DATA_DIR`), not by an absolute path from the source machine. A restore never
|
|
409
|
-
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.
|
|
410
426
|
|
|
411
427
|
</details>
|
|
412
428
|
|