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 +25 -11
- package/README.md +65 -42
- package/dist/cli.js +546 -128
- package/dist/cli.js.map +1 -1
- package/docs/SERVERS.md +46 -4
- package/package.json +3 -2
- package/uis/stock/dist/assets/{index-C7Rc5zP8.js → index-4_Q69j7h.js} +7 -7
- package/uis/stock/dist/index.html +1 -1
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
|
|
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 `
|
|
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.**
|
|
170
|
-
|
|
171
|
-
- **A port holder that
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
with `
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
40
|
-
forget about,
|
|
41
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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?
|
|
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
|
|
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 (
|
|
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
|
|
300
|
+
`home-hosted <command> --help` prints what that command takes.
|
|
287
301
|
|
|
288
302
|
```text
|
|
289
|
-
-c
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
|
347
|
-
|
|
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
|
|
443
|
-
servers.config.schema.json
|
|
444
|
-
.control-secrets.json
|
|
445
|
-
.logs/
|
|
446
|
-
.tls/
|
|
447
|
-
.backups/
|
|
448
|
-
run.json
|
|
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>📚
|
|
514
|
+
<summary><b>📚 Which doc do I need?</b></summary>
|
|
492
515
|
|
|
493
|
-
|
|
|
516
|
+
| if you want to… | read |
|
|
494
517
|
| --- | --- |
|
|
495
|
-
|
|
|
496
|
-
| [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md) |
|
|
497
|
-
| [UI_CREATION.md](./docs/UI_CREATION.md) |
|
|
498
|
-
|
|
|
499
|
-
| [/openapi/ui](http://127.0.0.1:3999/openapi/ui) |
|
|
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
|
|