home-hosted 0.7.3 → 0.7.4
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 +5 -3
- package/README.md +22 -35
- package/dist/cli.js +21 -13
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/uis/stock/dist/ui.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -277,10 +277,12 @@ either is a last resort, and never an accidental one.
|
|
|
277
277
|
boot through `.state/<id>.json` before any port preflight (`nannyIsAlive` = live pid **and** a fresh
|
|
278
278
|
heartbeat, the `HHOSTED_SERVER_ID` marker or the argv), tails that file for live logs, and reads
|
|
279
279
|
`lastExit` once so a crash nobody watched is reported. The spawn spec is consumed by the read and
|
|
280
|
-
swept at boot — it carries expanded env.
|
|
280
|
+
swept at boot — it carries expanded env. Three lifetimes are pinned: the nanny **exits with its child**
|
|
281
281
|
(an inherited pipe would keep it alive, and the panel would report a healthy entry whose server is a
|
|
282
|
-
detached stranger),
|
|
283
|
-
`killGroup: false` cannot be forwarded — which is why a self-restarting program belongs on `reclaim
|
|
282
|
+
detached stranger), a stop reaches the child by pid from the state file, because `SIGKILL` and
|
|
283
|
+
`killGroup: false` cannot be forwarded — which is why a self-restarting program belongs on `reclaim` —
|
|
284
|
+
and its `SIGTERM`/`SIGINT` traps are armed *before* the child exists and before that state file
|
|
285
|
+
appears, so a stop that beats the spawn can never orphan a child nobody has a record of.
|
|
284
286
|
The nanny never restarts anything: retries and health stay the supervisor's. `logs.persist: false`
|
|
285
287
|
keeps that file out of the Logs page but never stops it being written — it is the transport.
|
|
286
288
|
- **A port is only ever freed by re-listing its listeners.** `POST /api/servers/:id/free-port` never
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ them, watches them, restarts what dies, and shows you one page of what is going
|
|
|
25
25
|
|
|
26
26
|
<div align="center">
|
|
27
27
|
|
|
28
|
-

|
|
29
29
|
|
|
30
30
|
<sub>The stock panel, the [NOC-console](./uis/noc-console) that ships alongside it (for TUI and
|
|
31
31
|
shortcuts wizards), and UI directions you could build yourself —
|
|
@@ -163,13 +163,13 @@ Worked example, with per-server data inside the project:
|
|
|
163
163
|
<sub>Call them as `pnpm run up` — `pnpm up` is pnpm's own update, not your script.</sub>
|
|
164
164
|
|
|
165
165
|
> [!TIP]
|
|
166
|
-
>
|
|
167
|
-
>
|
|
166
|
+
> Recommended to give a project its own panel port (`control.port`, e.g. `4399`), to avoid port conflict
|
|
167
|
+
> with other projects.
|
|
168
168
|
|
|
169
169
|
</details>
|
|
170
170
|
|
|
171
171
|
<details>
|
|
172
|
-
<summary><b>🧰 Run it under systemd or Docker
|
|
172
|
+
<summary><b>🧰 Run it under systemd or Docker</b></summary>
|
|
173
173
|
|
|
174
174
|
```bash
|
|
175
175
|
home-hosted up --foreground # stays in the foreground, logs to stderr
|
|
@@ -179,12 +179,16 @@ home-hosted up --foreground # stays in the foreground, logs to stderr
|
|
|
179
179
|
[Unit]
|
|
180
180
|
Description=home-hosted
|
|
181
181
|
[Service]
|
|
182
|
+
User=$YOUR_USER
|
|
182
183
|
ExecStart=/usr/local/bin/home-hosted up --foreground
|
|
183
184
|
Environment=HHOSTED_HOME=/srv/home-hosted
|
|
184
185
|
Restart=always
|
|
185
186
|
[Install]
|
|
186
187
|
WantedBy=multi-user.target
|
|
187
188
|
```
|
|
189
|
+
> [!CAUTION]
|
|
190
|
+
> Note: change `$YOUR_USER` to your actual user account, or remove for `root`.
|
|
191
|
+
> You could also change `/srv/home-hosted` to change where the data stores.
|
|
188
192
|
|
|
189
193
|
</details>
|
|
190
194
|
|
|
@@ -231,8 +235,7 @@ default workspace.
|
|
|
231
235
|
| `GET /healthz` | no session needed — the one an external monitor wants (its per-server detail needs a credential) | ❌ |
|
|
232
236
|
| `GET /api/metrics` | Prometheus text (needs a token or session, like every `/api` route) | ❌ |
|
|
233
237
|
|
|
234
|
-
✅ takes `?workspace=<id>`; ❌ is panel-wide. Omitting
|
|
235
|
-
another one.
|
|
238
|
+
✅ takes `?workspace=<id>`; ❌ is panel-wide. Omitting `?workspace` means `default` workspace is used.
|
|
236
239
|
|
|
237
240
|
`GET /openapi/spec.json` describes all of it, `/openapi/ui` is the browsable version, and every error
|
|
238
241
|
comes back as one envelope (`{ message, code, detail }`) with a stable `code` a tool can branch on.
|
|
@@ -273,7 +276,7 @@ can be told what to be: *"Help me build a UI for home-hosted: nostalgic game the
|
|
|
273
276
|
| 🔔 **Notifications** | Telegram on crash, unhealthy, forced restart, recovery, host thresholds and DNS changes — [setup here](./docs/NOTIFICATIONS.md). |
|
|
274
277
|
| 🌐 **Dynamic DNS** | Keep hostnames pointed at your public IP — Cloudflare, Namecheap, Spaceship, Porkbun, GoDaddy, Gandi and more. [DDNS.md](./docs/DDNS.md) |
|
|
275
278
|
| 🔀 **Reverse proxy** | One engine in front of everything, with automatic HTTPS: `git.example.com` → a Gitea entry, `media.example.com` → a service on another box, `panel.example.com` → the panel. Caddy, installed and supervised by the panel. [REVERSE_PROXY.md](./docs/REVERSE_PROXY.md) |
|
|
276
|
-
| 💾 **Backups** |
|
|
279
|
+
| 💾 **Backups** | Deep and customizable: pick global settings, global secrets, TLS, Workspaces, then the pieces inside it (workspace settings, servers, secrets, data directories) — plain `.zip`, or AES-256 with a password, customized restore is supported too! |
|
|
277
280
|
| 🎨 **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) |
|
|
278
281
|
| 🔐 **Security** | Cookie sessions, API tokens, scrypt hashes, per-IP lockout, optional TLS, and a refusal to expose itself without a password. |
|
|
279
282
|
| 🧩 **No special treatment** | A server is `command` + `args` + `env` + `cwd`; nothing is built in for any particular app. |
|
|
@@ -283,27 +286,17 @@ can be told what to be: *"Help me build a UI for home-hosted: nostalgic game the
|
|
|
283
286
|
|
|
284
287
|
## 🗂 Workspaces
|
|
285
288
|
|
|
286
|
-
A workspace is the ownership boundary: its own servers, secrets, logs,
|
|
287
|
-
|
|
288
|
-
what it supervises first). The first boot creates a `default` workspace; a pre-workspaces instance is
|
|
289
|
-
relocated into one automatically.
|
|
290
|
-
|
|
291
|
-
> [!IMPORTANT]
|
|
292
|
-
> **Upgrading from before workspaces (0.6 → 0.7)?** That release is breaking: state moves under
|
|
293
|
-
> `$HHOSTED_HOME/.hh`, settings split global vs. per-workspace, and backups become two-level. The
|
|
294
|
-
> relocation runs on the next command — `home-hosted migrate` reports it and stamps every file. See
|
|
295
|
-
> [Upgrading](#-cli).
|
|
289
|
+
A workspace is the ownership boundary: its own servers, secrets, logs, states and settings.
|
|
290
|
+
The first boot creates a `default` workspace.
|
|
296
291
|
|
|
297
292
|
| page | url | scope |
|
|
298
293
|
| --- | --- | --- |
|
|
299
294
|
| **Overview** | `/w/<workspace>` | the selected workspace: its servers, counts and live status |
|
|
300
295
|
| **Servers** · **Logs** · **Workspace settings** | `/w/<workspace>/servers` · `/w/<workspace>/logs` · `/w/<workspace>/settings` | the selected workspace |
|
|
296
|
+
| **One server** | `/w/<workspace>/servers/<id>` | that entry: config, live logs, resources |
|
|
301
297
|
| **Global Overview** | `/global/overview` | host vitals plus every server from every workspace |
|
|
302
298
|
| **Global settings** | `/global/settings` | Listener, Authentication, Host vitals, Backups, TLS, Interface, Paths |
|
|
303
|
-
|
|
304
|
-
Every page is bookmarked by its own URL: the workspace id is part of it, so a link opens the same
|
|
305
|
-
workspace even in a browser that never selected it. `/` opens **Global Overview**. The sidebar lists
|
|
306
|
-
the panel-wide pages first, then the selected workspace's.
|
|
299
|
+
| **Reverse Proxy** | `/proxy` | panel-wide: one engine, every workspace's servers ([REVERSE_PROXY.md](./docs/REVERSE_PROXY.md)) |
|
|
307
300
|
|
|
308
301
|
<details>
|
|
309
302
|
<summary><b>🗂 What lives where</b></summary>
|
|
@@ -334,6 +327,7 @@ An entry is a few lines. Add one with **➕ Add server**, or write it into the s
|
|
|
334
327
|
```
|
|
335
328
|
|
|
336
329
|
`dataEnvs` declares a data directory once: it is exported to the process *and* picked up by Backups.
|
|
330
|
+
|
|
337
331
|
**Every field, every placeholder, the port-conflict policies (including adopting a server that
|
|
338
332
|
restarts itself), and how hand-edits are validated: [SERVERS.md](./docs/SERVERS.md).**
|
|
339
333
|
|
|
@@ -352,9 +346,9 @@ restarts itself), and how hand-edits are validated: [SERVERS.md](./docs/SERVERS.
|
|
|
352
346
|
| `home-hosted set-password` | set the panel password without opening a browser |
|
|
353
347
|
| `home-hosted set-token` | set the API token scripts and agents use (`--generate`, `--clear`) |
|
|
354
348
|
| `home-hosted migrate` | relocate a pre-workspaces state directory and stamp every config for this release (`--dry-run`, `--yes`) |
|
|
355
|
-
| `home-hosted init` | scaffold a project
|
|
349
|
+
| `home-hosted init` | scaffold a self-contained project (`HHOSTED_HOME` sets to the repo) |
|
|
356
350
|
| `home-hosted ui-switch` | install a UI from a release asset, a zip file or a URL (interactive) |
|
|
357
|
-
| `home-hosted ui-update` | bring an installed UI up to date
|
|
351
|
+
| `home-hosted ui-update` | bring an installed UI up to date automatically (official UI) or pick a release (`--old`, `--check`) |
|
|
358
352
|
| `home-hosted ui-revert` | go back to the stock panel UI after uploading your own |
|
|
359
353
|
|
|
360
354
|
<details>
|
|
@@ -390,16 +384,6 @@ for a launcher that pins one; a workspace picked in the UI keeps its own. `start
|
|
|
390
384
|
<details>
|
|
391
385
|
<summary><b>🧭 Upgrading, and why the panel sometimes refuses to start</b></summary>
|
|
392
386
|
|
|
393
|
-
> **Breaking in 0.7 — workspaces.** State moved under `$HHOSTED_HOME/.hh`: global files at its top
|
|
394
|
-
> level, one directory per workspace. Settings split into panel-wide **Global settings** and
|
|
395
|
-
> per-workspace **Workspace settings**, backups became two-level, and `--config` now points at the
|
|
396
|
-
> default workspace's servers file. The old `servers.config.json` becomes
|
|
397
|
-
> `.hh/default/servers.config.json`.
|
|
398
|
-
|
|
399
|
-
Relocation is automatic: the CLI relocates a pre-`.hh` `$HHOSTED_HOME` before any command runs, so an
|
|
400
|
-
existing instance keeps working after the upgrade. `home-hosted migrate` reports what it moved and
|
|
401
|
-
stamps every file for this release:
|
|
402
|
-
|
|
403
387
|
```bash
|
|
404
388
|
home-hosted migrate --dry-run # print the steps, write nothing
|
|
405
389
|
home-hosted migrate # ask, then write — keeps a .bak beside each rewritten file
|
|
@@ -582,7 +566,10 @@ run.json the running panel (pid, url, token, mode 0600)
|
|
|
582
566
|
<details>
|
|
583
567
|
<summary><b>Which ports does it use?</b></summary>
|
|
584
568
|
|
|
585
|
-
|
|
569
|
+
The control panel, `3999` by default. Supervised servers use the ports you give them. The reverse
|
|
570
|
+
proxy is the one panel-wide listener, and only when you switch it on: `80`/`443` for automatic HTTPS,
|
|
571
|
+
or `4480`/`4443` unprivileged with your router forwarding to them
|
|
572
|
+
([REVERSE_PROXY.md](./docs/REVERSE_PROXY.md#ports)).
|
|
586
573
|
|
|
587
574
|
</details>
|
|
588
575
|
|
|
@@ -634,7 +621,7 @@ plus types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
|
|
|
634
621
|
<details>
|
|
635
622
|
<summary><b>🔗 Interesting resources</b></summary>
|
|
636
623
|
|
|
637
|
-
- [dsh-home-hosted](https://github.com/NamesMT/dsh-home-hosted) — home-hosted servers management with boot autostart from inside [DeepSeek Harness](https://github.com/deepseek-ai/
|
|
624
|
+
- [dsh-home-hosted](https://github.com/NamesMT/dsh-home-hosted) — home-hosted servers management with boot autostart from inside [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
|
|
638
625
|
|
|
639
626
|
<sub><i>+ PR to add yours</i></sub>
|
|
640
627
|
|
package/dist/cli.js
CHANGED
|
@@ -17657,34 +17657,42 @@ async function runNanny(spec, statePath) {
|
|
|
17657
17657
|
text
|
|
17658
17658
|
});
|
|
17659
17659
|
};
|
|
17660
|
+
let stopping = false;
|
|
17661
|
+
let pending = null;
|
|
17662
|
+
let spawned = null;
|
|
17663
|
+
const stopChild = (child, signal) => {
|
|
17664
|
+
emit("system", `${signal} — stopping this entry (grace ${spec.stop.graceMs}ms)`);
|
|
17665
|
+
terminate(child, {
|
|
17666
|
+
signal: spec.stop.signal,
|
|
17667
|
+
killGroup: false,
|
|
17668
|
+
graceMs: spec.stop.graceMs
|
|
17669
|
+
});
|
|
17670
|
+
};
|
|
17671
|
+
const onSignal = (signal) => {
|
|
17672
|
+
if (stopping) return;
|
|
17673
|
+
stopping = true;
|
|
17674
|
+
if (spawned === null) pending = signal;
|
|
17675
|
+
else stopChild(spawned, signal);
|
|
17676
|
+
};
|
|
17677
|
+
process.on("SIGTERM", () => onSignal("SIGTERM"));
|
|
17678
|
+
process.on("SIGINT", () => onSignal("SIGINT"));
|
|
17660
17679
|
const child = spawnManaged({
|
|
17661
17680
|
command: spec.command,
|
|
17662
17681
|
args: spec.args,
|
|
17663
17682
|
cwd: spec.cwd,
|
|
17664
17683
|
env: spec.env
|
|
17665
17684
|
}, { detached: false });
|
|
17685
|
+
spawned = child;
|
|
17666
17686
|
childPid = child.pid ?? null;
|
|
17667
17687
|
persistState();
|
|
17668
17688
|
emit("system", `persistent: nanny pid ${process.pid} runs this entry`);
|
|
17689
|
+
if (pending !== null) stopChild(child, pending);
|
|
17669
17690
|
const stdout = new LineSplitter((stream, text) => emit(stream, text));
|
|
17670
17691
|
const stderr = new LineSplitter((stream, text) => emit(stream, text));
|
|
17671
17692
|
child.stdout?.on("data", (chunk) => stdout.push("stdout", chunk));
|
|
17672
17693
|
child.stderr?.on("data", (chunk) => stderr.push("stderr", chunk));
|
|
17673
17694
|
const heartbeat = setInterval(persistState, NANNY_HEARTBEAT_MS);
|
|
17674
17695
|
heartbeat.unref();
|
|
17675
|
-
let stopping = false;
|
|
17676
|
-
const onSignal = (signal) => {
|
|
17677
|
-
if (stopping) return;
|
|
17678
|
-
stopping = true;
|
|
17679
|
-
emit("system", `${signal} — stopping this entry (grace ${spec.stop.graceMs}ms)`);
|
|
17680
|
-
terminate(child, {
|
|
17681
|
-
signal: spec.stop.signal,
|
|
17682
|
-
killGroup: false,
|
|
17683
|
-
graceMs: spec.stop.graceMs
|
|
17684
|
-
});
|
|
17685
|
-
};
|
|
17686
|
-
process.on("SIGTERM", () => onSignal("SIGTERM"));
|
|
17687
|
-
process.on("SIGINT", () => onSignal("SIGINT"));
|
|
17688
17696
|
await new Promise((resolve) => {
|
|
17689
17697
|
let settled = false;
|
|
17690
17698
|
const finish = (code, signal, runtimeMs, note) => {
|