home-hosted 0.7.3 → 0.7.5

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
@@ -12,8 +12,9 @@ level: `settings.json` (listener, auth, TLS policy, host vitals, backups, revers
12
12
  `.backups/`, `.ui/`, `.logs/` (the panel console), `.proxy/` (the reverse-proxy engine, its generated
13
13
  configuration and its nanny state), and `run.json` — the live daemon's pid/url/token, 0600. Every workspace owns
14
14
  `.hh/<id>/`: `settings.json` (server defaults, log retention, notifications, DDNS), `servers.config.json`,
15
- `.secrets.json` (0600: Telegram bot token and DDNS credentials sealed with AES-256-GCM under
16
- `HHOSTED_DDNS_SECRET`), `.logs/`, and `.state/` (a persistent entry's nanny state plus its 0600 spawn
15
+ `.secrets.json` (0600: Telegram bot token, plus DDNS credentials sealed with AES-256-GCM under
16
+ `HHOSTED_DDNS_SECRET` — only DDNS is sealed, because it is the one thing replayed to a third party),
17
+ `.logs/`, and `.state/` (a persistent entry's nanny state plus its 0600 spawn
17
18
  spec until the nanny reads it, plus `ddns.json`). A pre-`.hh` instance is relocated automatically by
18
19
  `ensureLayout()` (`src/config/layout.ts`). The package ships **no servers**: never commit a config, a
19
20
  seed entry, or a path that names one.
@@ -165,9 +166,10 @@ a minor without one.
165
166
  - A single on/off setting is a `ToggleSwitch`; `CheckField` is only for picking items out of a set
166
167
  (the restore plan). A checkbox in a `FieldGroup` grid reads as misaligned next to the inputs.
167
168
  - A create body carries only what differs from what the entry would inherit — the schema's defaults
168
- with `Workspace Settings → Server defaults` on top (`inheritBaseline` + `diffFields` in both
169
- UIs). A value written into `servers.config.json` stops following those defaults, so anything the
170
- person did not decide stays out; the editor applies the same rule to an edit.
169
+ with `Workspace Settings → Server defaults` on top (each UI's own `inheritBaseline` plus the shared
170
+ `diffFields` in `src/shared/patch-diff.ts`). A value written into `servers.config.json` stops
171
+ following those defaults, so anything the person did not decide stays out; the editor applies the
172
+ same rule to an edit.
171
173
  - ArkType at every runtime boundary: routes use `validate('json'|'query'|'param', schema)` then
172
174
  `c.req.valid(...)`; ad-hoc payloads use `parseOrThrow`. Schemas reject undeclared keys.
173
175
  - Every failure is a `DetailedError` (`@namesmt/utils`), mapped by `src/helpers/error.ts` into one
@@ -277,10 +279,12 @@ either is a last resort, and never an accidental one.
277
279
  boot through `.state/<id>.json` before any port preflight (`nannyIsAlive` = live pid **and** a fresh
278
280
  heartbeat, the `HHOSTED_SERVER_ID` marker or the argv), tails that file for live logs, and reads
279
281
  `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. Two lifetimes are pinned: the nanny **exits with its child**
282
+ swept at boot — it carries expanded env. Three lifetimes are pinned: the nanny **exits with its child**
281
283
  (an inherited pipe would keep it alive, and the panel would report a healthy entry whose server is a
282
- detached stranger), and 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
+ detached stranger), a stop reaches the child by pid from the state file, because `SIGKILL` and
285
+ `killGroup: false` cannot be forwarded — which is why a self-restarting program belongs on `reclaim` —
286
+ and its `SIGTERM`/`SIGINT` traps are armed *before* the child exists and before that state file
287
+ appears, so a stop that beats the spawn can never orphan a child nobody has a record of.
284
288
  The nanny never restarts anything: retries and health stay the supervisor's. `logs.persist: false`
285
289
  keeps that file out of the Logs page but never stops it being written — it is the transport.
286
290
  - **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
- ![Six views of the panel: the stock UI, the NOC-console, and UI examples](docs/media/tour.gif)
28
+ ![Eight views of the panel: the stock UI and its reverse proxy, the NOC-console, and UI examples](docs/media/tour.gif)
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
- > Give a project its own panel port (`control.port`, e.g. `4399`) — the default `3999` is what the
167
- > global instance and every other project also want.
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 (no daemon needed)</b></summary>
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
 
@@ -228,11 +232,11 @@ default workspace.
228
232
  | `GET`/`PATCH /api/settings/workspace` | server defaults, logs, notifications | ✅ |
229
233
  | `GET`/`PATCH /api/settings` | panel-wide: listener, authentication, TLS, host vitals, backups | ❌ |
230
234
  | `GET`/`POST /api/backups`, restore | archives | ❌ |
235
+ | `GET`/`PATCH /api/proxy`, `POST /api/proxy/{engine,start,stop,apply,revert}`, `PUT`/`DELETE /api/proxy/certificates/:id` | the reverse proxy: its engine, its route table, and its uploaded certificates | ❌ |
231
236
  | `GET /healthz` | no session needed — the one an external monitor wants (its per-server detail needs a credential) | ❌ |
232
237
  | `GET /api/metrics` | Prometheus text (needs a token or session, like every `/api` route) | ❌ |
233
238
 
234
- ✅ takes `?workspace=<id>`; ❌ is panel-wide. Omitting it means the panel's default workspace — never
235
- another one.
239
+ ✅ takes `?workspace=<id>`; ❌ is panel-wide. Omitting `?workspace` means `default` workspace is used.
236
240
 
237
241
  `GET /openapi/spec.json` describes all of it, `/openapi/ui` is the browsable version, and every error
238
242
  comes back as one envelope (`{ message, code, detail }`) with a stable `code` a tool can branch on.
@@ -273,7 +277,7 @@ can be told what to be: *"Help me build a UI for home-hosted: nostalgic game the
273
277
  | 🔔 **Notifications** | Telegram on crash, unhealthy, forced restart, recovery, host thresholds and DNS changes — [setup here](./docs/NOTIFICATIONS.md). |
274
278
  | 🌐 **Dynamic DNS** | Keep hostnames pointed at your public IP — Cloudflare, Namecheap, Spaceship, Porkbun, GoDaddy, Gandi and more. [DDNS.md](./docs/DDNS.md) |
275
279
  | 🔀 **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** | Two levels: pick global settings, global secrets, TLS or a whole workspace at the top, then the pieces inside it (settings, servers, secrets, data directories) — plain `.zip`, or AES-256 with a password, restored per path. |
280
+ | 💾 **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
281
  | 🎨 **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
282
  | 🔐 **Security** | Cookie sessions, API tokens, scrypt hashes, per-IP lockout, optional TLS, and a refusal to expose itself without a password. |
279
283
  | 🧩 **No special treatment** | A server is `command` + `args` + `env` + `cwd`; nothing is built in for any particular app. |
@@ -283,27 +287,17 @@ can be told what to be: *"Help me build a UI for home-hosted: nostalgic game the
283
287
 
284
288
  ## 🗂 Workspaces
285
289
 
286
- A workspace is the ownership boundary: its own servers, secrets, logs, nanny state and settings. The
287
- header picker is the whole control — choose one, create one, rename it, or delete it (which stops
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).
290
+ A workspace is the ownership boundary: its own servers, secrets, logs, states and settings.
291
+ The first boot creates a `default` workspace.
296
292
 
297
293
  | page | url | scope |
298
294
  | --- | --- | --- |
299
295
  | **Overview** | `/w/<workspace>` | the selected workspace: its servers, counts and live status |
300
296
  | **Servers** · **Logs** · **Workspace settings** | `/w/<workspace>/servers` · `/w/<workspace>/logs` · `/w/<workspace>/settings` | the selected workspace |
297
+ | **One server** | `/w/<workspace>/servers/<id>` | that entry: config, live logs, resources |
301
298
  | **Global Overview** | `/global/overview` | host vitals plus every server from every workspace |
302
299
  | **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.
300
+ | **Reverse Proxy** | `/proxy` | panel-wide: one engine, every workspace's servers ([REVERSE_PROXY.md](./docs/REVERSE_PROXY.md)) |
307
301
 
308
302
  <details>
309
303
  <summary><b>🗂 What lives where</b></summary>
@@ -334,6 +328,7 @@ An entry is a few lines. Add one with **➕ Add server**, or write it into the s
334
328
  ```
335
329
 
336
330
  `dataEnvs` declares a data directory once: it is exported to the process *and* picked up by Backups.
331
+
337
332
  **Every field, every placeholder, the port-conflict policies (including adopting a server that
338
333
  restarts itself), and how hand-edits are validated: [SERVERS.md](./docs/SERVERS.md).**
339
334
 
@@ -352,9 +347,9 @@ restarts itself), and how hand-edits are validated: [SERVERS.md](./docs/SERVERS.
352
347
  | `home-hosted set-password` | set the panel password without opening a browser |
353
348
  | `home-hosted set-token` | set the API token scripts and agents use (`--generate`, `--clear`) |
354
349
  | `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 that keeps `state/` and its data in the repo |
350
+ | `home-hosted init` | scaffold a self-contained project, whose scripts keep state in the repo (`--home ./state`) |
356
351
  | `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, or pick a release (`--old`, `--check`) |
352
+ | `home-hosted ui-update` | bring an installed UI up to date automatically (official UI) or pick a release (`--old`, `--check`) |
358
353
  | `home-hosted ui-revert` | go back to the stock panel UI after uploading your own |
359
354
 
360
355
  <details>
@@ -390,16 +385,6 @@ for a launcher that pins one; a workspace picked in the UI keeps its own. `start
390
385
  <details>
391
386
  <summary><b>🧭 Upgrading, and why the panel sometimes refuses to start</b></summary>
392
387
 
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
388
  ```bash
404
389
  home-hosted migrate --dry-run # print the steps, write nothing
405
390
  home-hosted migrate # ask, then write — keeps a .bak beside each rewritten file
@@ -582,7 +567,10 @@ run.json the running panel (pid, url, token, mode 0600)
582
567
  <details>
583
568
  <summary><b>Which ports does it use?</b></summary>
584
569
 
585
- Just the control panel, `3999` by default. Supervised servers use the ports you give them.
570
+ The control panel, `3999` by default. Supervised servers use the ports you give them. The reverse
571
+ proxy is the one panel-wide listener, and only when you switch it on: `80`/`443` for automatic HTTPS,
572
+ or `4480`/`4443` unprivileged with your router forwarding to them
573
+ ([REVERSE_PROXY.md](./docs/REVERSE_PROXY.md#ports)).
586
574
 
587
575
  </details>
588
576
 
@@ -591,7 +579,9 @@ Just the control panel, `3999` by default. Supervised servers use the ports you
591
579
 
592
580
  Yes. Process trees are sampled from Win32_Process, termination uses `taskkill /T`, and the shipped
593
581
  examples avoid POSIX-only commands. CPU temperature and swap are best-effort where the OS does not
594
- expose them to an unprivileged process, and adopting a self-restarted process is Linux/macOS only.
582
+ expose them to an unprivileged process. Adopting a self-restarted process works on Windows too, off
583
+ its argv instead of the environment marker; a launcher whose real process is a `.cmd` shim is the one
584
+ case it cannot see through, and `kill` is the answer there.
595
585
 
596
586
  </details>
597
587
 
@@ -634,7 +624,7 @@ plus types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
634
624
  <details>
635
625
  <summary><b>🔗 Interesting resources</b></summary>
636
626
 
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/dsh)
627
+ - [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
628
 
639
629
  <sub><i>+ PR to add yours</i></sub>
640
630