home-hosted 0.5.0 → 0.6.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/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
@@ -166,15 +169,26 @@ either is a last resort, and never an accidental one.
166
169
  - **Paths.** `dataRoot` is state; `projectDir` is the base for relative entry paths. `{id}{port}`
167
170
  `{host}{bind}{cwd}{projectDir}{dataRoot}{home}` and `${ENV}` expand in config; there is no
168
171
  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.
172
+ - **Secrets never enter the config.** The password hash, API token hash and bot token live in the
173
+ 0600 secrets file, and the TLS pair in `.tls/`; the config holds policy.
174
+ - **A port holder that is this entry's own successor is not a stranger.** A program that restarts
175
+ itself leaves a detached process behind; with `follow` the panel adopts it as-is (pid, liveness,
176
+ health, resources, stop — but not its output); with `reclaim` it stops that successor and starts a
177
+ fully supervised child instead; with `kill` it stops whatever holds the port without asking whose it
178
+ is. All three beat blocking forever on a port that is already serving, and only `kill` ever touches a
179
+ process the panel could not identify — which is why it logs the pids it stopped.
180
+ Ownership is read from `HHOSTED_SERVER_ID` in the environment (`/proc` on Linux, `ps -E` on macOS),
181
+ falling back to the entry's own resolved image **plus its expanded argv** where that is unavailable
182
+ or unmatched — the fallback is what gives `follow`/`reclaim` any reach on Windows, which exposes no
183
+ per-process environment. That fallback is strict on purpose, because `reclaim` kills what it
184
+ identifies: arguments are compared **literally** (never by basename — `/srv/a/server.js` must not
185
+ match `/tmp/b/server.js`), an argument can never be satisfied by a word that is not in the argv, and
186
+ two matching holders mean the panel refuses to guess and blocks. It still cannot see through a
187
+ Windows `.cmd` shim (the holder is `node.exe`, whose argv never mentions the shim) or a macOS
188
+ argument containing a space (`ps` joins argv without quoting); those cases block, and `kill` is the
189
+ answer for them. `src/providers/identity.ts` owns the matching, and the argv it is given comes from
190
+ the supervisor's single `resolveSpawn()`, the same one the spawn itself used — keep it to one
191
+ resolver, because a second one that expands or filters args differently re-opens these cases.
178
192
  - **A port is only ever freed by re-listing its listeners.** `POST /api/servers/:id/free-port` never
179
193
  trusts a pid quoted in a message, and refuses any listener in `supervisedPids()` (the panel plus
180
194
  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
 
@@ -277,26 +291,32 @@ 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) |
281
295
  | `home-hosted ui-revert` | go back to the stock panel UI after uploading your own |
282
296
 
283
297
  <details>
284
298
  <summary><b>⚙️ Flags</b></summary>
285
299
 
286
- `home-hosted <command> --help` prints the options that command takes.
300
+ `home-hosted <command> --help` prints what that command takes.
287
301
 
288
302
  ```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)
303
+ up, restart -c/--config -p/--port --host --open --no-autostart --foreground --print-config
304
+
305
+ down (no flags)
306
+ status --json
307
+ init --dir --name --pm --no-install -y/--yes
308
+ set-password --clear
309
+ set-token --generate --clear
310
+ migrate --config --dry-run -y/--yes
311
+ ui-switch --repo --tag --asset --file --list --token -y/--yes
312
+ ui-revert (no flags)
313
+
314
+ every command --home <dir> --project <dir> (or $HHOSTED_HOME, $HHOSTED_PROJECT)
315
+ env vars HHOSTED_PASSWORD, HHOSTED_MIGRATE=allow, HHOSTED_TOKEN, GITHUB_TOKEN or GH_TOKEN
298
316
  ```
299
317
 
318
+ `up` and `restart` share the same flags: `restart` is `down`, then `up` with exactly what it was given.
319
+
300
320
  </details>
301
321
 
302
322
  <details>
@@ -343,8 +363,8 @@ Everything binds `127.0.0.1` until you say otherwise.
343
363
  never taken from the message, and anything the panel supervises is refused, not killed. A server
344
364
  that [restarts itself](./docs/SERVERS.md#when-a-program-restarts-itself) can be followed, or replaced
345
365
  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`.
366
+ - **Secrets never enter the config**: the password hash, the API token hash and the Telegram bot
367
+ token live in `$HHOSTED_HOME/.control-secrets.json` with mode `0600`; the TLS pair in `.tls/`.
348
368
  - **Behind a proxy** turn on `trustProxy` and let `cookieSecure: auto` add `Secure` on https, or
349
369
  upload a PEM pair and let home-hosted terminate TLS itself.
350
370
 
@@ -391,6 +411,9 @@ Two ship in this repo: `uis/stock`, and `uis/noc-console` for TUI and shortcuts
391
411
  attaches both as `home-hosted-ui-<name>.zip`. Yours can be anything that compiles to static files —
392
412
  the server never cares what built it.
393
413
 
414
+ <sub>Install a UI from the CLI: `home-hosted ui-switch` — with no flags it fetches the official asset
415
+ built for this release.</sub>
416
+
394
417
  <details>
395
418
  <summary><b>🤖 Or have an agent build the UI you actually want</b></summary>
396
419
 
@@ -439,13 +462,13 @@ opened.
439
462
  `$HHOSTED_HOME`, default `~/.home-hosted`:
440
463
 
441
464
  ```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)
465
+ servers.config.json your servers, plus meta: which release and schema wrote it
466
+ servers.config.schema.json regenerated on every start, for editor autocomplete
467
+ .control-secrets.json password hash + API token hash + Telegram token (mode 0600)
468
+ .logs/ rotated per-server logs + history
469
+ .tls/ an uploaded PEM pair
470
+ .backups/ zip archives
471
+ run.json the running panel (pid, url, token, mode 0600)
449
472
  ```
450
473
 
451
474
  `home-hosted status` prints the paths.
@@ -488,15 +511,15 @@ test/ the vitest suite
488
511
  types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
489
512
 
490
513
  <details>
491
- <summary><b>📚 The docs, and which one you want</b></summary>
514
+ <summary><b>📚 Which doc do I need?</b></summary>
492
515
 
493
- | file | for |
516
+ | if you want to… | read |
494
517
  | --- | --- |
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 |
518
+ | declare a server: every field, placeholders, port conflicts | [SERVERS.md](./docs/SERVERS.md) |
519
+ | get Telegram alerts working end to end | [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md) |
520
+ | build a UI against the API | [UI_CREATION.md](./docs/UI_CREATION.md) |
521
+ | change the internals: architecture and the rules | [AGENTS.md](./AGENTS.md) |
522
+ | poke the live API on your own panel | [/openapi/ui](http://127.0.0.1:3999/openapi/ui) |
500
523
 
501
524
  </details>
502
525