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 +38 -11
- package/README.md +70 -43
- package/dist/cli.js +1396 -470
- package/dist/cli.js.map +1 -1
- package/docs/SERVERS.md +46 -4
- package/docs/UI_CREATION.md +35 -4
- package/package.json +3 -2
- package/uis/stock/dist/assets/{index-C7Rc5zP8.js → index-sj5eYUeD.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
|
|
@@ -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.**
|
|
170
|
-
|
|
171
|
-
- **A port holder that
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
with `
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -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, `
|
|
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 (
|
|
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
|
|
301
|
+
`home-hosted <command> --help` prints what that command takes.
|
|
287
302
|
|
|
288
303
|
```text
|
|
289
|
-
-c
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
|
347
|
-
|
|
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
|
|
443
|
-
servers.config.schema.json
|
|
444
|
-
.control-secrets.json
|
|
445
|
-
.logs/
|
|
446
|
-
.tls/
|
|
447
|
-
.backups/
|
|
448
|
-
run.json
|
|
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>📚
|
|
518
|
+
<summary><b>📚 Which doc do I need?</b></summary>
|
|
492
519
|
|
|
493
|
-
|
|
|
520
|
+
| if you want to… | read |
|
|
494
521
|
| --- | --- |
|
|
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) |
|
|
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
|
|