home-hosted 0.5.0 → 0.6.1

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
@@ -28,6 +28,8 @@ pnpm exec tsx src/cli.ts init # scaffold a project (interactive; --yes for
28
28
  pnpm run media # regenerate docs/media (mockups, both served UIs, tour.gif)
29
29
  ```
30
30
 
31
+ The published bin is `home-hosted`, with an `hh` alias: both names run the same CLI.
32
+
31
33
  Releases are dispatched from `.github/workflows/release.yml` with a version (and a `dry-run` switch
32
34
  that stops before pushing). It verifies the version against `package.json`, lints/types/tests,
33
35
  builds the CLI plus the stock UI and every UI zip, lets changelogen write the changelog and tag
@@ -67,13 +69,14 @@ exists, so the first release has to be published by hand.
67
69
  `migrations.ts` (the schema constant and the ordered step registry), `secrets.ts`, `seed.ts`.
68
70
  - `src/providers/` — stateless leaves: `process` (spawn, `terminate`, `terminatePid` for a process we
69
71
  adopted), `port` (probe, holder lookup, `terminatePids`), `proc` (the sampler, plus
70
- `processCarriesServerId` for ownership), health-check, host, telegram, archive.
72
+ `processCarriesServerId` for the environment marker), `identity` (which port holder is this entry's
73
+ own successor: marker first, then resolved image + argv), health-check, host, telegram, archive.
71
74
  - `src/services/` — stateful orchestration: supervisor, control-server, config-watch, state,
72
75
  auth + exposure, dependencies, history, log-buffer/log-files, notifications, host-monitor, backups,
73
76
  tls, ui, plus `init` (the scaffold behind `home-hosted init`: a manifest, a `.gitignore`, and the
74
77
  prompts stay in the CLI). It names no server — the scaffold must stay as neutral as the supervisor.
75
78
  - `src/middleware/auth.ts` — the `/api/*` guard, and `requestIdentity()`, the one place a request's
76
- credentials are read: the `hh2_session` cookie or `Authorization: Bearer <api token>`. A token is
79
+ credentials are read: the `hh_session` cookie or `Authorization: Bearer <api token>`. A token is
77
80
  a first-class credential (same authority as a signed-in browser) and is verified from the secrets
78
81
  file on every request, so `set-token` needs no restart.
79
82
  - `src/helpers/` — paths (`dataRoot` vs `projectDir`), daemon (run.json + a loopback probe that
@@ -82,6 +85,19 @@ exists, so the first release has to be published by hand.
82
85
  - `uis/<name>/` — each UI is a Vite app (Vue 3 + Tailwind v4) built through `uis/vite.shared.ts`;
83
86
  `stock` is the one shipped inside the package. Aliases: `@` → that UI's `src`, `@shared` →
84
87
  `src/shared`, `@server` → `src` (**types only** — never import runtime server code into a UI).
88
+ Its `public/ui.json` is the UI's identity: `name` and `version` (what Settings shows),
89
+ `repo`/`tag`/`asset` (which release carries it, for `ui-update`) and `unix` (when it was built).
90
+ `UiService` carries those fields into the installed `$HHOSTED_HOME/.ui/ui.json` unchanged and adds
91
+ `uploadedAt`/`files` of its own.
92
+ - **Bumping a UI ships a new asset, so its `ui.json` is part of the change.** Any commit that alters
93
+ a UI under `uis/<name>/` bumps that `ui.json`'s `version` and sets `unix` to the commit's own epoch
94
+ seconds: patch for a fix, minor for a feature, and **major only for a rewrite or a restyle**. The
95
+ number is what tells a person how big the change is — `ui-update` prints it and holds it against
96
+ the release it is offering. `tag` is what pairs an official UI with its panel, and it is **stamped
97
+ by `build-uis.mjs`**, not trusted from the source file: a UI zip is built before its release is cut,
98
+ so the committed value is always a release behind the asset it ends up inside. The release workflow
99
+ passes `HHOSTED_UI_TAG`; anywhere else the build stamps `v<package.json version>`. Do not hand-edit
100
+ `tag` to chase a release — a stale one makes `ui-update` re-install the same UI on every boot.
85
101
  - `bin/home-hosted.mjs` — the published bin: `dist/cli.js`, or `src/cli.ts` through tsx when the
86
102
  build is missing (a linked checkout).
87
103
  - `scripts/` — `build-uis.mjs` (build one UI, optionally zip it), `typecheck-uis.mjs`,
@@ -166,15 +182,26 @@ either is a last resort, and never an accidental one.
166
182
  - **Paths.** `dataRoot` is state; `projectDir` is the base for relative entry paths. `{id}{port}`
167
183
  `{host}{bind}{cwd}{projectDir}{dataRoot}{home}` and `${ENV}` expand in config; there is no
168
184
  package-relative state.
169
- - **Secrets never enter the config.** Password hash, API token hash, bot token and TLS key live in
170
- the 0600 secrets file; the config holds policy.
171
- - **A port holder that carries `HHOSTED_SERVER_ID` for this entry is our own successor, not a
172
- stranger.** A program that restarts itself leaves a detached process behind; with
173
- `follow` the panel adopts it as-is (pid, liveness, health, resources, stop — but not its output);
174
- with `reclaim` it stops that successor and starts a fully supervised child instead. Both are strictly
175
- better than blocking forever on a port that is already serving, and neither ever touches a stranger.
176
- Ownership is read from the environment — `/proc` on Linux, `ps -E` on macOS, impossible on Windows —
177
- and `stop.killPortHolders` stays the fallback.
185
+ - **Secrets never enter the config.** The password hash, API token hash and bot token live in the
186
+ 0600 secrets file, and the TLS pair in `.tls/`; the config holds policy.
187
+ - **A port holder that is this entry's own successor is not a stranger.** A program that restarts
188
+ itself leaves a detached process behind; with `follow` the panel adopts it as-is (pid, liveness,
189
+ health, resources, stop — but not its output); with `reclaim` it stops that successor and starts a
190
+ fully supervised child instead; with `kill` it stops whatever holds the port without asking whose it
191
+ is. All three beat blocking forever on a port that is already serving, and only `kill` ever touches a
192
+ process the panel could not identify — which is why it logs the pids it stopped.
193
+ Ownership is read from `HHOSTED_SERVER_ID` in the environment (`/proc` on Linux, `ps -E` on macOS),
194
+ falling back to the entry's own resolved image **plus its expanded argv** where that is unavailable
195
+ or unmatched — the fallback is what gives `follow`/`reclaim` any reach on Windows, which exposes no
196
+ per-process environment. That fallback is strict on purpose, because `reclaim` kills what it
197
+ identifies: arguments are compared **literally** (never by basename — `/srv/a/server.js` must not
198
+ match `/tmp/b/server.js`), an argument can never be satisfied by a word that is not in the argv, and
199
+ two matching holders mean the panel refuses to guess and blocks. It still cannot see through a
200
+ Windows `.cmd` shim (the holder is `node.exe`, whose argv never mentions the shim) or a macOS
201
+ argument containing a space (`ps` joins argv without quoting); those cases block, and `kill` is the
202
+ answer for them. `src/providers/identity.ts` owns the matching, and the argv it is given comes from
203
+ the supervisor's single `resolveSpawn()`, the same one the spawn itself used — keep it to one
204
+ resolver, because a second one that expands or filters args differently re-opens these cases.
178
205
  - **A port is only ever freed by re-listing its listeners.** `POST /api/servers/:id/free-port` never
179
206
  trusts a pid quoted in a message, and refuses any listener in `supervisedPids()` (the panel plus
180
207
  every entry's child) instead of killing it — a port held by a sibling is a config mistake.
package/README.md CHANGED
@@ -36,17 +36,12 @@ shortcuts wizards), and UI directions you could build yourself —
36
36
 
37
37
  ## 🤔 Why?
38
38
 
39
- Running services on a home machine usually means one of two extremes: `tmux` sessions you
40
- forget about, or a hand-written systemd unit per service (times six) — or a complex docker/k8s
41
- setup?
39
+ You run a handful of services at home. The usual choices are extremes — 🧟 `tmux` sessions you
40
+ forget about, 📜 a hand-written systemd unit per service (times six), or 🐳 a whole docker/k8s
41
+ stack??? - too extreme! — plus 😩 monitoring, rebooting and changing the host machine, yuck!
42
42
 
43
- | | |
44
- | --- | --- |
45
- | ❌ **"Is it still running?"** | You check with `ps`, then `curl`, then hope. |
46
- | ❌ **Silent deaths** | Something crashes at 3am and you notice days later. |
47
- | ❌ **One terminal per service** | Logs scroll away in tabs you closed. |
48
- | ❌ **Fragile restarts** | The box reboots and half the stack is gone. |
49
- | ✅ **home-hosted** | Declare it once, watch it forever, one command to stop it all. |
43
+ 🙂 home-hosted, well, joins in somewhere: a panel/supervisor that starts them, watches them, restarts what dies, and
44
+ puts the whole stack on one page, with deep backup support.
50
45
 
51
46
  ```text
52
47
  ┌──────────────────────────────────────┐
@@ -63,8 +58,23 @@ setup?
63
58
  health ✓ health ✓ restarts ↻
64
59
  ```
65
60
 
66
- It ships with **nothing**. No blessed paths, no "data directory" setting, no opinion about
67
- what you run — a server is a command, some arguments, and the environment you give it.
61
+ | | |
62
+ | --- | --- |
63
+ | ❌ **"Is it still running?"** | Health, CPU/mem, uptime and live logs per server — no `ps`, no `curl`, no hope |
64
+ | ❌ **Silent deaths** | Restarted automatically, and the panel or Telegram notifies |
65
+ | ❌ **Fragile reboots** | `autostart` brings the stack back; one `down` stops it all cleanly |
66
+ | ❌ **"Move it to the new box"** | One archive: config **and** data, restored on a blank host |
67
+ | ✅ **home-hosted** | Declare it once, watch it forever, one command to stop it all |
68
+
69
+ It ships with **nothing**: no blessed paths, no opinion about what you run — a server is a command,
70
+ some arguments, and the environment you give it:
71
+
72
+ ```json
73
+ { "id": "gateway", "command": "node", "args": ["server.js"], "port": 4000, "autostart": true }
74
+ ```
75
+
76
+ <sub>Manage them from the panel, `home-hosted status --json`, or `GET /api/state`; `GET /healthz` is
77
+ the same status line for your own monitor, no session needed.</sub>
68
78
 
69
79
  ---
70
80
 
@@ -92,12 +102,15 @@ npx home-hosted down # stops the panel *and* everything it started
92
102
 
93
103
  ```bash
94
104
  npm install -g home-hosted # or: pnpm add -g home-hosted
95
- home-hosted up
105
+ home-hosted up # `hh up` does the same
96
106
  ```
97
107
 
98
108
  Everything it owns — config, secrets, logs, TLS, backups — lives in `$HHOSTED_HOME`, default
99
109
  `~/.home-hosted`. Delete that and nothing of yours is left behind.
100
110
 
111
+ <sub>An installed home-hosted answers to **`hh`** too — `hh up`, `hh status`, `hh down`. Only the
112
+ installed form gets it; npx stays `npx home-hosted …`.</sub>
113
+
101
114
  </details>
102
115
 
103
116
  <details>
@@ -178,7 +191,7 @@ home-hosted set-token --generate
178
191
 
179
192
  curl -H "Authorization: Bearer hh_9uA2…" http://127.0.0.1:3999/api/state
180
193
  curl -H "Authorization: Bearer hh_9uA2…" -X POST http://127.0.0.1:3999/api/servers/9router/restart
181
- curl -N -H "Authorization: Bearer hh_9uA2…" 'http://127.0.0.1:3999/api/events?logs=1' # SSE
194
+ curl -N -H "Authorization: Bearer hh_9uA2…" 'http://127.0.0.1:3999/api/events?serverId=9router' # SSE
182
195
  home-hosted status --json # machine-readable: pid, url, health, paths
183
196
  ```
184
197
 
@@ -196,7 +209,8 @@ revokes it instantly.
196
209
  | `POST /api/servers/:id/{start,stop,restart}` | lifecycle |
197
210
  | `POST /api/servers/:id/free-port` | ask whatever holds that server's port to stop |
198
211
  | `PATCH /api/servers/:id`, `PATCH /api/settings` | edit configuration |
199
- | `GET /api/logs`, `/api/backups`, `/api/notifications` | logs, archives, Telegram |
212
+ | `GET /api/logs`, `/api/backups` | history and archives |
213
+ | `PUT`/`DELETE /api/notifications/token`, `POST /api/notifications/{test,detect-chats}` | the bot credential, a test send |
200
214
  | `GET /healthz` | no session needed — the one an external monitor wants (its per-server detail needs a credential) |
201
215
  | `GET /api/metrics` | Prometheus text (needs a token or session, like every `/api` route) |
202
216
 
@@ -237,7 +251,7 @@ can be told what to be: *"Help me build a UI for home-hosted: nostalgic game the
237
251
  | 🤖 **Token API** | Scripts and agents drive it with `Authorization: Bearer` — no browser, no session. [↑](#-agents-scripts-and-tools) |
238
252
  | 🔔 **Notifications** | Telegram on crash, unhealthy, forced restart, recovery and host thresholds — [setup here](./docs/NOTIFICATIONS.md). |
239
253
  | 💾 **Backups** | One click for config, secrets, TLS and your declared data directories — plain `.zip`, or AES-256 with a password, restored per path. |
240
- | 🎨 **BYOU — Bring Your Own UI** | Upload a static build, `home-hosted ui-revert` to go back. [UI_CREATION.md](./docs/UI_CREATION.md) |
254
+ | 🎨 **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) |
241
255
  | 🔐 **Security** | Cookie sessions, API tokens, scrypt hashes, per-IP lockout, optional TLS, and a refusal to expose itself without a password. |
242
256
  | 🧩 **No special treatment** | A server is `command` + `args` + `env` + `cwd`; nothing is built in for any particular app. |
243
257
  | 🖥 **Cross-platform** | Linux, macOS and Windows: `/proc`, `ps` or Win32_Process, process groups or `taskkill /T`, no shell dependencies. |
@@ -277,26 +291,34 @@ restarts itself), and how hand-edits are validated: [SERVERS.md](./docs/SERVERS.
277
291
  | `home-hosted set-token` | set the API token scripts and agents use (`--generate`, `--clear`) |
278
292
  | `home-hosted migrate` | bring `servers.config.json` up to this release's schema (`--dry-run`, `--yes`) |
279
293
  | `home-hosted init` | scaffold a project that keeps `state/` and its data in the repo |
280
- | `home-hosted ui-switch` | install a UI from a release asset, a zip file or a URL (`--list` to see them) |
294
+ | `home-hosted ui-switch` | install a UI from a release asset, a zip file or a URL (interactive) |
295
+ | `home-hosted ui-update` | bring an installed UI up to date, or pick a release (`--old`, `--check`) |
281
296
  | `home-hosted ui-revert` | go back to the stock panel UI after uploading your own |
282
297
 
283
298
  <details>
284
299
  <summary><b>⚙️ Flags</b></summary>
285
300
 
286
- `home-hosted <command> --help` prints the options that command takes.
301
+ `home-hosted <command> --help` prints what that command takes.
287
302
 
288
303
  ```text
289
- -c, --config <file> servers config (default: <state>/servers.config.json)
290
- -p, --port <port> control panel port (default: 3999)
291
- --host <bind> local | lan | an ipv4 address
292
- --open open the panel in a browser once it is up
293
- --no-autostart do not start the entries marked autostart
294
- --foreground run in this process instead of detaching
295
- --print-config print the effective config and exit
296
- --home <dir> state directory (or $HHOSTED_HOME)
297
- --project <dir> base for relative paths (or $HHOSTED_PROJECT)
304
+ up, restart -c/--config -p/--port --host --open --no-autostart --foreground --print-config
305
+
306
+ down (no flags)
307
+ status --json
308
+ init --dir --name --pm --no-install -y/--yes
309
+ set-password --clear
310
+ set-token --generate --clear
311
+ migrate --config --dry-run -y/--yes
312
+ ui-switch --repo --tag --asset --file --list --token -y/--yes
313
+ ui-update --check --tag --asset --old --repo --token -y/--yes
314
+ ui-revert (no flags)
315
+
316
+ every command --home <dir> --project <dir> (or $HHOSTED_HOME, $HHOSTED_PROJECT)
317
+ env vars HHOSTED_PASSWORD, HHOSTED_MIGRATE=allow, HHOSTED_TOKEN, GITHUB_TOKEN or GH_TOKEN
298
318
  ```
299
319
 
320
+ `up` and `restart` share the same flags: `restart` is `down`, then `up` with exactly what it was given.
321
+
300
322
  </details>
301
323
 
302
324
  <details>
@@ -343,8 +365,8 @@ Everything binds `127.0.0.1` until you say otherwise.
343
365
  never taken from the message, and anything the panel supervises is refused, not killed. A server
344
366
  that [restarts itself](./docs/SERVERS.md#when-a-program-restarts-itself) can be followed, or replaced
345
367
  with a supervised copy.
346
- - **Secrets never enter the config**: the password hash, the API token hash, the Telegram bot token
347
- and the TLS key live in `$HHOSTED_HOME/.control-secrets.json` with mode `0600`.
368
+ - **Secrets never enter the config**: the password hash, the API token hash and the Telegram bot
369
+ token live in `$HHOSTED_HOME/.control-secrets.json` with mode `0600`; the TLS pair in `.tls/`.
348
370
  - **Behind a proxy** turn on `trustProxy` and let `cookieSecure: auto` add `Secure` on https, or
349
371
  upload a PEM pair and let home-hosted terminate TLS itself.
350
372
 
@@ -391,6 +413,11 @@ Two ship in this repo: `uis/stock`, and `uis/noc-console` for TUI and shortcuts
391
413
  attaches both as `home-hosted-ui-<name>.zip`. Yours can be anything that compiles to static files —
392
414
  the server never cares what built it.
393
415
 
416
+ <sub>Install a UI from the CLI: `home-hosted ui-switch` — with no flags it fetches the official asset
417
+ built for this release. An **official** UI keeps itself paired with the panel: upgrade the panel and
418
+ the next `up` re-installs the matching asset. Someone else's UI declares its own `repo`/`asset` in
419
+ `ui.json`, and `home-hosted ui-update` offers its newer releases to pick from — `--old` for older ones.</sub>
420
+
394
421
  <details>
395
422
  <summary><b>🤖 Or have an agent build the UI you actually want</b></summary>
396
423
 
@@ -439,13 +466,13 @@ opened.
439
466
  `$HHOSTED_HOME`, default `~/.home-hosted`:
440
467
 
441
468
  ```text
442
- servers.config.json your servers, plus meta: which release and schema wrote it
443
- servers.config.schema.json regenerated on every start, for editor autocomplete
444
- .control-secrets.json password hash + API token hash + Telegram token (mode 0600)
445
- .logs/ rotated per-server logs + history
446
- .tls/ an uploaded PEM pair
447
- .backups/ zip archives
448
- run.json the running panel (pid, url, token, mode 0600)
469
+ servers.config.json your servers, plus meta: which release and schema wrote it
470
+ servers.config.schema.json regenerated on every start, for editor autocomplete
471
+ .control-secrets.json password hash + API token hash + Telegram token (mode 0600)
472
+ .logs/ rotated per-server logs + history
473
+ .tls/ an uploaded PEM pair
474
+ .backups/ zip archives
475
+ run.json the running panel (pid, url, token, mode 0600)
449
476
  ```
450
477
 
451
478
  `home-hosted status` prints the paths.
@@ -488,15 +515,15 @@ test/ the vitest suite
488
515
  types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
489
516
 
490
517
  <details>
491
- <summary><b>📚 The docs, and which one you want</b></summary>
518
+ <summary><b>📚 Which doc do I need?</b></summary>
492
519
 
493
- | file | for |
520
+ | if you want to… | read |
494
521
  | --- | --- |
495
- | [SERVERS.md](./docs/SERVERS.md) | declaring a server: every field, placeholders, port conflicts |
496
- | [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md) | Telegram alerts, end to end |
497
- | [UI_CREATION.md](./docs/UI_CREATION.md) | building a UI against the API |
498
- | [AGENTS.md](./AGENTS.md) | the architecture and the rules worth knowing before changing anything |
499
- | [/openapi/ui](http://127.0.0.1:3999/openapi/ui) | the live API, on your own panel |
522
+ | declare a server: every field, placeholders, port conflicts | [SERVERS.md](./docs/SERVERS.md) |
523
+ | get Telegram alerts working end to end | [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md) |
524
+ | build a UI against the API | [UI_CREATION.md](./docs/UI_CREATION.md) |
525
+ | change the internals: architecture and the rules | [AGENTS.md](./AGENTS.md) |
526
+ | poke the live API on your own panel | [/openapi/ui](http://127.0.0.1:3999/openapi/ui) |
500
527
 
501
528
  </details>
502
529