home-hosted 0.6.2 → 0.6.3
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 +51 -9
- package/README.md +16 -1
- package/dist/cli.js +932 -59
- package/dist/cli.js.map +1 -1
- package/docs/SERVERS.md +47 -0
- package/package.json +1 -1
- package/uis/stock/dist/assets/{index-sj5eYUeD.js → index-CCPLyw-2.js} +6 -6
- package/uis/stock/dist/index.html +1 -1
package/AGENTS.md
CHANGED
|
@@ -7,7 +7,8 @@ and serves a UI. User docs: `README.md`, `docs/SERVERS.md` (entries and port con
|
|
|
7
7
|
|
|
8
8
|
State lives only in `$HHOSTED_HOME` (default `~/.home-hosted`): `servers.config.json`,
|
|
9
9
|
`.control-secrets.json` (0600: password hash, API token hash, Telegram bot token), `.logs/`, `.tls/`,
|
|
10
|
-
`.backups/`, `.ui/`,
|
|
10
|
+
`.backups/`, `.ui/`, `.state/` (a persistent entry's nanny state, plus its 0600 spawn spec until the
|
|
11
|
+
nanny reads it), and `run.json` — the live daemon's pid/url/token, 0600. The package ships **no
|
|
11
12
|
servers**: never commit a config, a seed entry, or a path that names one.
|
|
12
13
|
|
|
13
14
|
## Commands
|
|
@@ -31,21 +32,39 @@ pnpm run media # regenerate docs/media (mockups, both served
|
|
|
31
32
|
The published bin is `home-hosted`, with an `hh` alias: both names run the same CLI.
|
|
32
33
|
|
|
33
34
|
Releases are dispatched from `.github/workflows/release.yml` with a version (and a `dry-run` switch
|
|
34
|
-
that stops before pushing). It verifies the version
|
|
35
|
-
|
|
35
|
+
that stops before pushing). It verifies the version, lints/types/tests, builds the CLI plus the stock
|
|
36
|
+
UI and every UI zip, lets changelogen write the changelog, bump `package.json`, commit and tag
|
|
36
37
|
`v<version>`, creates the GitHub release with the UI bundles attached, and publishes to npm through
|
|
37
38
|
trusted publishing (OIDC, no token). npm only offers a trusted publisher for a package that already
|
|
38
39
|
exists, so the first release has to be published by hand.
|
|
39
40
|
|
|
41
|
+
### Which version to dispatch
|
|
42
|
+
|
|
43
|
+
Below 1.0 the **minor is the breaking channel**: a fix or a non-breaking feature is a **patch**
|
|
44
|
+
(`0.6.2` → `0.6.3`), while a minor (`0.6.3` → `0.7.0`) means someone has to read the release notes and
|
|
45
|
+
act. A `feat:` commit does not decide this — ask what the user has to do about it.
|
|
46
|
+
|
|
47
|
+
Never edit `package.json` by hand; changelogen bumps, commits and tags inside the workflow:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
gh workflow run release.yml -f version=0.6.3 # -f dry-run=true to rehearse
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`scripts/check-release-version.mjs` refuses a patch while a breaking commit is pending, and warns on
|
|
54
|
+
a minor without one.
|
|
55
|
+
|
|
40
56
|
## Architecture (and why)
|
|
41
57
|
|
|
42
58
|
- `src/cli.ts` — the CLI's thin root. Two things happen before [citty](https://github.com/unjs/citty)
|
|
43
59
|
is asked anything: `--home`/`--project` are peeled off and applied (`src/cli/args.ts`), and the
|
|
44
60
|
curated dispatch — `help`/`version`, `unknown command`, and the `-p 4000` shorthand for `up` — is
|
|
45
61
|
decided, because `#src/helpers/paths.ts` resolves at import time. Its static imports stay node
|
|
46
|
-
builtins, citty and
|
|
62
|
+
builtins, citty and the two path-free local modules (`src/cli/args.ts`, `src/helpers/runtime.ts`);
|
|
63
|
+
every command is a lazy
|
|
47
64
|
`() => import('#src/cli/<command>')` in citty's `subCommands`, so a command module *may* use static
|
|
48
|
-
`#src` imports (that is the whole point of the pre-pass).
|
|
65
|
+
`#src` imports (that is the whole point of the pre-pass). The hidden `__nanny` is dispatched by an
|
|
66
|
+
early branch, not `subCommands`, so it stays out of the curated help and the unknown-command
|
|
67
|
+
message — the panel spawns it, nobody types it. citty's `runMain` is deliberately not
|
|
49
68
|
used: it prints its own usage and `console.error`s before `process.exit(1)`, replacing `fail()`'s
|
|
50
69
|
one error shape; the root calls `runCommand` and catches. `src/cli/<command>.ts` is one command per
|
|
51
70
|
file — citty owns dispatch and argument parsing, with `--no-autostart`/`--no-install` declared as
|
|
@@ -69,12 +88,17 @@ exists, so the first release has to be published by hand.
|
|
|
69
88
|
`migrations.ts` (the schema constant and the ordered step registry), `secrets.ts`, `seed.ts`.
|
|
70
89
|
- `src/providers/` — stateless leaves: `process` (spawn, `terminate`, `terminatePid` for a process we
|
|
71
90
|
adopted), `port` (probe, holder lookup, `terminatePids`), `proc` (the sampler, plus
|
|
72
|
-
`processCarriesServerId` for the environment marker
|
|
73
|
-
own successor: marker first, then resolved image +
|
|
91
|
+
`processCarriesServerId` for the environment marker and `processTreePids` for tree ownership),
|
|
92
|
+
`identity` (which port holder is this entry's own successor: marker first, then resolved image +
|
|
93
|
+
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), health-check, host, telegram,
|
|
95
|
+
archive.
|
|
74
96
|
- `src/services/` — stateful orchestration: supervisor, control-server, config-watch, state,
|
|
75
97
|
auth + exposure, dependencies, history, log-buffer/log-files, notifications, host-monitor, backups,
|
|
76
98
|
tls, ui, plus `init` (the scaffold behind `home-hosted init`: a manifest, a `.gitignore`, and the
|
|
77
|
-
prompts stay in the CLI)
|
|
99
|
+
prompts stay in the CLI), `nanny` (the process a persistent entry runs under) and `log-relay` (the
|
|
100
|
+
tailer that feeds its lines to the supervisor). It names no server — the scaffold must stay as
|
|
101
|
+
neutral as the supervisor.
|
|
78
102
|
- `src/middleware/auth.ts` — the `/api/*` guard, and `requestIdentity()`, the one place a request's
|
|
79
103
|
credentials are read: the `hh_session` cookie or `Authorization: Bearer <api token>`. A token is
|
|
80
104
|
a first-class credential (same authority as a signed-in browser) and is verified from the secrets
|
|
@@ -202,6 +226,22 @@ either is a last resort, and never an accidental one.
|
|
|
202
226
|
answer for them. `src/providers/identity.ts` owns the matching, and the argv it is given comes from
|
|
203
227
|
the supervisor's single `resolveSpawn()`, the same one the spawn itself used — keep it to one
|
|
204
228
|
resolver, because a second one that expands or filters args differently re-opens these cases.
|
|
229
|
+
Ownership covers the entry's whole **tree**, not the pid it recorded (`processTreePids`): a nanny
|
|
230
|
+
puts the real server one generation down, and so does any wrapper entry. The panel's own tree is
|
|
231
|
+
deliberately excluded — "ours" has to mean a process a server owns.
|
|
232
|
+
- **A persistent entry is run by its own nanny, not by the panel.** `persistent: true` makes the panel
|
|
233
|
+
spawn the hidden `__nanny` (`src/services/nanny.ts`), which owns the child's pipes, writes its JSONL
|
|
234
|
+
and mirrors its exit — the only arrangement that outlives the panel *and* keeps logging. The panel
|
|
235
|
+
skips such an entry in `stopAll()`/`dispose()` (`down` reports what it left running), reattaches on
|
|
236
|
+
boot through `.state/<id>.json` before any port preflight (`nannyIsAlive` = live pid **and** a fresh
|
|
237
|
+
heartbeat, the `HHOSTED_SERVER_ID` marker or the argv), tails that file for live logs, and reads
|
|
238
|
+
`lastExit` once so a crash nobody watched is reported. The spawn spec is consumed by the read and
|
|
239
|
+
swept at boot — it carries expanded env. Two lifetimes are pinned: the nanny **exits with its child**
|
|
240
|
+
(an inherited pipe would keep it alive, and the panel would report a healthy entry whose server is a
|
|
241
|
+
detached stranger), and a stop reaches the child by pid from the state file, because `SIGKILL` and
|
|
242
|
+
`killGroup: false` cannot be forwarded — which is why a self-restarting program belongs on `reclaim`.
|
|
243
|
+
The nanny never restarts anything: retries and health stay the supervisor's. `logs.persist: false`
|
|
244
|
+
keeps that file out of the Logs page but never stops it being written — it is the transport.
|
|
205
245
|
- **A port is only ever freed by re-listing its listeners.** `POST /api/servers/:id/free-port` never
|
|
206
246
|
trusts a pid quoted in a message, and refuses any listener in `supervisedPids()` (the panel plus
|
|
207
247
|
every entry's child) instead of killing it — a port held by a sibling is a config mistake.
|
|
@@ -268,7 +308,9 @@ either is a last resort, and never an accidental one.
|
|
|
268
308
|
the live output view empty until a remount — a test that reads the array itself will not catch it.
|
|
269
309
|
- An adopted process is not a `ChildProcess`, so nothing reports its exit: the tick polls liveness and
|
|
270
310
|
hands the entry back to the normal `afterExit` path. Its output is not captured either — it was
|
|
271
|
-
redirected by whoever spawned it.
|
|
311
|
+
redirected by whoever spawned it. The exception is a persistent entry, whose output the panel
|
|
312
|
+
*does* capture: its nanny writes the JSONL, and `log-relay` tails it into the same buffer and SSE
|
|
313
|
+
frames a pipe would feed.
|
|
272
314
|
- `stop.killPortHolders` frees a port only from a *listener* that is not our own process tree. Broad
|
|
273
315
|
`lsof -ti:<port>` sweeps and pid-as-text parses have killed supervisors in the field; don't add one.
|
|
274
316
|
`free-port` reuses the same lookup and adds the supervisor's own pid set on top.
|
package/README.md
CHANGED
|
@@ -290,7 +290,7 @@ restarts itself), and how hand-edits are validated: [SERVERS.md](./docs/SERVERS.
|
|
|
290
290
|
| command | |
|
|
291
291
|
| --- | --- |
|
|
292
292
|
| `home-hosted up` | start the panel detached, and keep it alive in the background |
|
|
293
|
-
| `home-hosted down` | stop it cleanly — supervised processes included |
|
|
293
|
+
| `home-hosted down` | stop it cleanly — supervised processes included, persistent entries left running |
|
|
294
294
|
| `home-hosted restart` | `down`, then `up` |
|
|
295
295
|
| `home-hosted status` | pid, URL, health, uptime, state and log paths (`--json` for scripts) |
|
|
296
296
|
| `home-hosted set-password` | set the panel password without opening a browser |
|
|
@@ -454,6 +454,10 @@ panel itself (`--foreground`) and for system services; use home-hosted for the r
|
|
|
454
454
|
`home-hosted down` stops them — that is the point of the command. `SIGTERM`/`SIGINT` are handled
|
|
455
455
|
the same way: every supervised process tree is stopped before the panel exits.
|
|
456
456
|
|
|
457
|
+
An entry marked `persistent` is the exception, and `down` names it instead of stopping it: it runs
|
|
458
|
+
under its own nanny process, keeps logging, and is reattached by the next panel
|
|
459
|
+
([SERVERS.md](./docs/SERVERS.md#persistent-entries)).
|
|
460
|
+
|
|
457
461
|
</details>
|
|
458
462
|
|
|
459
463
|
<details>
|
|
@@ -535,6 +539,17 @@ types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
|
|
|
535
539
|
|
|
536
540
|
---
|
|
537
541
|
|
|
542
|
+
<details>
|
|
543
|
+
<summary><b>🔗 Interesting resources</b></summary>
|
|
544
|
+
|
|
545
|
+
- [dsh-home-hosted](https://github.com/NamesMT/dsh-home-hosted) — home-hosted servers management with boot autostart from [DeepSeek Harness](https://github.com/deepseek-ai/dsh)
|
|
546
|
+
|
|
547
|
+
<sub><i>+ PR to add yours</i></sub>
|
|
548
|
+
|
|
549
|
+
</details>
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
538
553
|
<div align="center">
|
|
539
554
|
|
|
540
555
|
**MIT**
|