home-hosted 0.7.0 → 0.7.2

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
@@ -3,12 +3,14 @@
3
3
  `home-hosted` is a Node 24 / TypeScript harness for self-hosted servers: `up` starts a Hono/srvx
4
4
  panel (default `127.0.0.1:3999`) that supervises the entries in each workspace's
5
5
  `servers.config.json` and serves a UI. User docs: `README.md`, `docs/SERVERS.md` (entries and port
6
- conflicts), `docs/NOTIFICATIONS.md`, `docs/DDNS.md`; UI authors: `docs/UI_CREATION.md`.
6
+ conflicts), `docs/NOTIFICATIONS.md`, `docs/DDNS.md`, `docs/REVERSE_PROXY.md`; UI authors:
7
+ `docs/UI_CREATION.md`.
7
8
 
8
9
  State lives only in `$HHOSTED_HOME/.hh` (default `~/.home-hosted/.hh`). Global files sit at its top
9
- level: `settings.json` (listener, auth, TLS policy, host vitals, backups), `workspaces.json`,
10
- `.control-secrets.json` (0600: password hash, API token hash), `.tls/`, `.backups/`, `.ui/`, `.logs/`
11
- (the panel console), and `run.json` — the live daemon's pid/url/token, 0600. Every workspace owns
10
+ level: `settings.json` (listener, auth, TLS policy, host vitals, backups, reverse proxy),
11
+ `workspaces.json`, `.control-secrets.json` (0600: password hash, API token hash), `.tls/`,
12
+ `.backups/`, `.ui/`, `.logs/` (the panel console), `.proxy/` (the reverse-proxy engine, its generated
13
+ configuration and its nanny state), and `run.json` — the live daemon's pid/url/token, 0600. Every workspace owns
12
14
  `.hh/<id>/`: `settings.json` (server defaults, log retention, notifications, DDNS), `servers.config.json`,
13
15
  `.secrets.json` (0600: Telegram bot token and DDNS credentials sealed with AES-256-GCM under
14
16
  `HHOSTED_DDNS_SECRET`), `.logs/`, and `.state/` (a persistent entry's nanny state plus its 0600 spawn
@@ -21,7 +23,8 @@ seed entry, or a path that names one.
21
23
  ```sh
22
24
  pnpm run up|down|restart|status # detached; `down` asks /_hh/shutdown, signals are the fallback
23
25
  pnpm run start # up --foreground (systemd, docker, a foreground shell)
24
- pnpm dev # tsx-watch panel :3999 + the stock UI's Vite :3998 (proxies /api)
26
+ pnpm dev # tsx-watch panel :6000 + the stock UI's Vite :6001 (proxies /api), state in .dev-state/
27
+ pnpm dev --ui noc-console # the same, for another UI; --port/--ui-port override
25
28
  pnpm run build # dist/cli.js + the stock UI (uis/stock/dist)
26
29
  pnpm run build:uis # every UI under uis/, zipped into uis/dist/ (release assets)
27
30
  pnpm run quickcheck # eslint + tsc + vue-tsc for every UI under uis/
@@ -36,6 +39,12 @@ pnpm run media # regenerate docs/media (mockups, both served
36
39
 
37
40
  The published bin is `home-hosted`, with an `hh` alias: both names run the same CLI.
38
41
 
42
+ **Dev servers and test instances stay in the 6xxx range** — panel 6000, UI 6001, test-spawned panels
43
+ 6100+ (`pnpm dev` takes `--ui`/`--port`/`--ui-port`, or `HHOSTED_DEV_PANEL_PORT` /
44
+ `HHOSTED_DEV_UI_PORT`). Never assume 3999 is free: an installed panel or another dev instance may hold
45
+ it, and the CLI's port preflight runs *before* its config guard, so a busy default port turns config
46
+ tests red for the wrong reason.
47
+
39
48
  Releases are dispatched from `.github/workflows/release.yml` with a version (and a `dry-run` switch
40
49
  that stops before pushing). It verifies the version, lints/types/tests, builds the CLI plus the stock
41
50
  UI and every UI zip, lets changelogen write the changelog, bump `package.json`, commit and tag
@@ -102,12 +111,13 @@ a minor without one.
102
111
  `identity` (which port holder is this entry's own successor: marker first, then resolved image +
103
112
  argv), `nanny` (a persistent entry's spec/state files and the liveness rules for one),
104
113
  `log-tail` (an offset reader that survives rotation and truncation), `ddns/` (one `DdnsProvider` per
105
- registrar over one HTTP call each, plus `ip` and `zone` helpers), health-check, host, telegram,
106
- archive.
114
+ registrar over one HTTP call each, plus `ip` and `zone` helpers), `proxy/` (the reverse-proxy
115
+ engines this build can drive: where the binary comes from and how to start it — the route model
116
+ stays the panel's), health-check, host, telegram, archive.
107
117
  - `src/services/` — stateful orchestration: `panel` (one `WorkspaceRuntime` per workspace, workspace
108
118
  CRUD, and the single aggregate state frame — every per-workspace service hangs off it), supervisor,
109
119
  control-server, config-watch, state, auth + exposure, dependencies, history, log-buffer/log-files,
110
- notifications, host-monitor, ddns, backups, tls, ui, plus `init` (the scaffold behind
120
+ notifications, host-monitor, ddns, proxy, backups, tls, ui, plus `init` (the scaffold behind
111
121
  `home-hosted init`: a manifest, a `.gitignore`, and the prompts stay in the CLI), `nanny` (the
112
122
  process a persistent entry runs under) and `log-relay` (the tailer that feeds its lines to the
113
123
  supervisor). It names no server — the scaffold must stay as neutral as the supervisor.
@@ -139,9 +149,10 @@ a minor without one.
139
149
  - `scripts/` — `build-uis.mjs` (build one UI, optionally zip it), `typecheck-uis.mjs`,
140
150
  `capture-media.mjs` (the README's media), `check-release-version.mjs` and `release-notes.mjs`
141
151
  (used by the release workflow), `dev.mjs` (`pnpm dev`).
142
- - `docs/` — the topic docs (`SERVERS.md`, `NOTIFICATIONS.md`, `DDNS.md`, `UI_CREATION.md`),
152
+ - `docs/` — the topic docs (`SERVERS.md`, `NOTIFICATIONS.md`, `DDNS.md`, `REVERSE_PROXY.md`,
153
+ `UI_CREATION.md`),
143
154
  `mockups/*.html` (hand-written UI examples in four directions) and `media/*`: their screenshots plus
144
- the README's `tour.gif`, all regenerated by `pnpm run media`. Only the four `.md` files are
155
+ the README's `tour.gif`, all regenerated by `pnpm run media`. Only the five `.md` files are
145
156
  published; the media is a build artifact.
146
157
 
147
158
  ## Conventions
@@ -286,6 +297,12 @@ either is a last resort, and never an accidental one.
286
297
  `$HHOSTED_HOME/.hh/.ui` overrides the packaged UI at runtime.
287
298
  - **A restore writes only where a config says** — archive allowlist, `origin` matching first,
288
299
  symlinks never recreated.
300
+ - **The reverse proxy's engine is a process the panel owns, not a server entry.** Its generated
301
+ configuration is applied through the engine's admin API (atomic, never hand-edited), the engine runs
302
+ under the same nanny a `persistent: true` entry uses so a panel restart does not drop the socket,
303
+ and its admin endpoint stays panel-side — a unix socket in a `0700` directory, loopback plus the
304
+ engine's origin check on Windows. A route that points at the panel is exposure and obeys
305
+ `checkProxyExposure()`, the same bar as a non-loopback bind.
289
306
  - **Cross-platform.** Linux/macOS/Windows: `/proc` vs `ps` vs Win32_Process, process groups vs
290
307
  `taskkill /T`, graceful fallbacks, no shell utilities assumed.
291
308
  - **Writes are atomic** (`writeFileAtomic`) and validated before commit.
@@ -338,6 +355,9 @@ either is a last resort, and never an accidental one.
338
355
  file-only blocks on their own. The copy has to be **detached**, too: a shallow one leaves a nested
339
356
  group (`health.http`) aliased to live state, so an edit to "Healthy below status" mutated the very
340
357
  config the guard compared against and the next frame reset the whole editor (`cloneHealth`).
358
+ - **An absolutely positioned element inside a *static* scroll container resolves against the
359
+ page, not the scroller** — an `sr-only` label in a table row was enough to give the document a
360
+ second, empty scrollbar. The shell's `main` carries `relative` for that reason; keep it there.
341
361
  - **A dialog's footer has to be a flex sibling of a scrolling body** (`uis/stock/src/components/ui/Modal.vue`):
342
362
  the sheet is `flex flex-col` with `max-h-[88dvh]`, the body `min-h-0 flex-1 overflow-y-auto`. When
343
363
  only the body carried a max-height, a tall form pushed its own save button below the clipped
@@ -393,5 +413,5 @@ either is a last resort, and never an accidental one.
393
413
  ## Publishing
394
414
 
395
415
  `pnpm pack` runs `prepack` (a full build) and ships `bin/`, `dist/`, `uis/stock/dist`, `README.md`, `docs/DDNS.md`,
396
- `docs/SERVERS.md`, `docs/NOTIFICATIONS.md`, `docs/UI_CREATION.md`, `AGENTS.md` and `LICENSE`. The bin falls back to tsx so `pnpm link` works before a
416
+ `docs/SERVERS.md`, `docs/NOTIFICATIONS.md`, `docs/REVERSE_PROXY.md`, `docs/UI_CREATION.md`, `AGENTS.md` and `LICENSE`. The bin falls back to tsx so `pnpm link` works before a
397
417
  build; `vue`/`vue-router` are devDependencies because the UIs are prebuilt.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  Point it at the things you run — a gateway, a media server, a bot, a database — and it starts
10
10
  them, watches them, restarts what dies, and shows you one page of what is going on. Split them into
11
- [workspaces](#-workspaces) when one panel holds more than one setup. Or
11
+ [workspaces](#-workspaces) when one panel holds more than one setup. And
12
12
  [BYOU](#-bring-your-own-ui-byou), for a specialized UI that fits you exactly.
13
13
 
14
14
  [![npm](https://img.shields.io/npm/v/home-hosted.svg)](https://www.npmjs.com/package/home-hosted)
@@ -17,7 +17,7 @@ them, watches them, restarts what dies, and shows you one page of what is going
17
17
  [![License](https://img.shields.io/npm/l/home-hosted.svg)](./LICENSE)
18
18
  [![Node](https://img.shields.io/node/v/home-hosted.svg)](https://nodejs.org)
19
19
 
20
- [🚀 Quick start](#-quick-start) · [🗂 Workspaces](#-workspaces) · [🧩 Servers](./docs/SERVERS.md) · [🤖 Agents & API](#-agents-scripts-and-tools) · [✨ Features](#-features) · [🛠 CLI](#-cli) · [🔔 Notifications](./docs/NOTIFICATIONS.md) · [🎨 BYOU](#-bring-your-own-ui-byou)
20
+ [🚀 Quick start](#-quick-start) · [🗂 Workspaces](#-workspaces) · [🧩 Servers](./docs/SERVERS.md) · [🤖 Agents & API](#-agents-scripts-and-tools) · [✨ Features](#-features) · [🛠 CLI](#-cli) · [🔔 Notifications](./docs/NOTIFICATIONS.md) · [🔀 Reverse proxy](./docs/REVERSE_PROXY.md) · [🎨 BYOU](#-bring-your-own-ui-byou)
21
21
 
22
22
  </div>
23
23
 
@@ -213,19 +213,26 @@ default workspace.
213
213
  <details>
214
214
  <summary><b>🌐 The endpoints worth knowing</b></summary>
215
215
 
216
- | | |
217
- | --- | --- |
218
- | `GET /api/state` | the full snapshot: global settings, every workspace with its servers, host vitals |
219
- | `GET`/`POST /api/workspaces`, `PATCH`/`DELETE /api/workspaces/:id` | the registry: list, create, rename, remove |
220
- | `GET /api/events` | SSE: the live state, plus logs (`?logs=0`, `?serverId=…`; a `server` payload carries its `workspaceId`) |
221
- | `GET /api/servers/:id/stream` | SSE: one server's state and logs (`?workspace=…`) |
222
- | `POST /api/servers/:id/{start,stop,restart}` | lifecycle (workspace-scoped) |
223
- | `POST /api/servers/:id/free-port` | ask whatever holds that server's port to stop |
224
- | `PATCH /api/servers/:id`, `PATCH /api/settings`, `PATCH /api/settings/workspace` | edit configuration |
225
- | `GET /api/logs`, `/api/backups` | history and archives |
226
- | `PUT`/`DELETE /api/notifications/token`, `POST /api/notifications/{test,detect-chats}` | the bot credential, a test send |
227
- | `GET /healthz` | no session needed — the one an external monitor wants (its per-server detail needs a credential) |
228
- | `GET /api/metrics` | Prometheus text (needs a token or session, like every `/api` route) |
216
+ | | | `?workspace` |
217
+ | --- | --- | --- |
218
+ | `GET /api/state` | the full snapshot: global settings, every workspace with its servers, host vitals | ❌ |
219
+ | `GET`/`POST /api/workspaces`, `PATCH`/`DELETE /api/workspaces/:id` | the registry: list, create, rename, remove | ❌ |
220
+ | `GET /api/events` | SSE: the live state, plus logs (`?logs=0`, `?serverId=…`; a `server` payload carries its `workspaceId`) | ❌ |
221
+ | `GET`/`POST /api/servers`, `POST /api/servers/{start,stop}-all` | that workspace's entries, and its lifecycle for all of them | ✅ |
222
+ | `GET`/`PATCH`/`DELETE /api/servers/:id` | one entry | ✅ |
223
+ | `POST /api/servers/:id/{start,stop,restart}`, `POST /api/servers/:id/free-port` | one entry's lifecycle, and freeing its port | ✅ |
224
+ | `GET /api/servers/:id/logs`, `GET /api/servers/:id/stream` | that entry's buffered lines, and its SSE | ✅ |
225
+ | `GET /api/logs` | persisted history and files | ✅ |
226
+ | `GET`/`PUT /api/ddns`, `/api/ddns/credentials/:id`, `/api/ddns/check` | that workspace's Dynamic DNS policy, credentials and a check | ✅ |
227
+ | `PUT`/`DELETE /api/notifications/token`, `POST /api/notifications/{test,detect-chats}` | that workspace's bot credential and a test send | ✅ |
228
+ | `GET`/`PATCH /api/settings/workspace` | server defaults, logs, notifications | ✅ |
229
+ | `GET`/`PATCH /api/settings` | panel-wide: listener, authentication, TLS, host vitals, backups | ❌ |
230
+ | `GET`/`POST /api/backups`, restore | archives | ❌ |
231
+ | `GET /healthz` | no session needed — the one an external monitor wants (its per-server detail needs a credential) | ❌ |
232
+ | `GET /api/metrics` | Prometheus text (needs a token or session, like every `/api` route) | ❌ |
233
+
234
+ ✅ takes `?workspace=<id>`; ❌ is panel-wide. Omitting it means the panel's default workspace — never
235
+ another one.
229
236
 
230
237
  `GET /openapi/spec.json` describes all of it, `/openapi/ui` is the browsable version, and every error
231
238
  comes back as one envelope (`{ message, code, detail }`) with a stable `code` a tool can branch on.
@@ -265,6 +272,7 @@ can be told what to be: *"Help me build a UI for home-hosted: nostalgic game the
265
272
  | 🤖 **Token API** | Scripts and agents drive it with `Authorization: Bearer` — no browser, no session. [↑](#-agents-scripts-and-tools) |
266
273
  | 🔔 **Notifications** | Telegram on crash, unhealthy, forced restart, recovery, host thresholds and DNS changes — [setup here](./docs/NOTIFICATIONS.md). |
267
274
  | 🌐 **Dynamic DNS** | Keep hostnames pointed at your public IP — Cloudflare, Namecheap, Spaceship, Porkbun, GoDaddy, Gandi and more. [DDNS.md](./docs/DDNS.md) |
275
+ | 🔀 **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) |
268
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. |
269
277
  | 🎨 **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) |
270
278
  | 🔐 **Security** | Cookie sessions, API tokens, scrypt hashes, per-IP lockout, optional TLS, and a refusal to expose itself without a password. |
@@ -496,10 +504,7 @@ Two ship in this repo: `uis/stock`, and `uis/noc-console` for TUI and shortcuts
496
504
  attaches both as `home-hosted-ui-<name>.zip`. Yours can be anything that compiles to static files —
497
505
  the server never cares what built it.
498
506
 
499
- <sub>Install a UI from the CLI: `home-hosted ui-switch` — with no flags it fetches the official asset
500
- built for this release. An **official** UI keeps itself paired with the panel: upgrade the panel and
501
- the next `up` re-installs the matching asset. Someone else's UI declares its own `repo`/`asset` in
502
- `ui.json`, and `home-hosted ui-update` offers its newer releases to pick from — `--old` for older ones.</sub>
507
+ <sub>Install a UI from the CLI: `home-hosted ui-switch` `--asset noc-console` selects an UI directly, no flags interactively show official assets. Official UIs's version auto-sync when you update `home-hosted`. For unofficial UIs, `home-hosted ui-update` offers its newer releases to pick from — or `--old` for older (author must set up `ui.json` and GH releases).</sub>
503
508
 
504
509
  <details>
505
510
  <summary><b>🤖 Or have an agent build the UI you actually want</b></summary>
@@ -605,9 +610,10 @@ scripts/ builds, typechecks, media capture, release helpers
605
610
  test/ the vitest suite
606
611
  ```
607
612
 
608
- `pnpm dev` runs the panel with `tsx watch` plus the stock UI's dev server (state goes to
609
- `.dev-state/`); `pnpm build` produces `dist/` and `uis/stock/dist/`; `pnpm quickcheck` is lint plus
610
- types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
613
+ `pnpm dev` runs the panel with `tsx watch` plus one UI's dev server on the 6xxx range — panel `6000`,
614
+ UI `6001` (`pnpm dev --ui noc-console`), state in `.dev-state/` — so a dev instance never fights an
615
+ installed panel's 3999. `pnpm build` produces `dist/` and `uis/stock/dist/`; `pnpm quickcheck` is lint
616
+ plus types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
611
617
 
612
618
  <details>
613
619
  <summary><b>📚 Which doc do I need?</b></summary>
@@ -616,6 +622,7 @@ types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
616
622
  | --- | --- |
617
623
  | declare a server: every field, placeholders, port conflicts | [SERVERS.md](./docs/SERVERS.md) |
618
624
  | get Telegram alerts working end to end | [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md) |
625
+ | expose the stack behind one hostname, with HTTPS | [REVERSE_PROXY.md](./docs/REVERSE_PROXY.md) |
619
626
  | build a UI against the API | [UI_CREATION.md](./docs/UI_CREATION.md) |
620
627
  | change the internals: architecture and the rules | [AGENTS.md](./AGENTS.md) |
621
628
  | poke the live API on your own panel | [/openapi/ui](http://127.0.0.1:3999/openapi/ui) |
@@ -627,7 +634,7 @@ types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
627
634
  <details>
628
635
  <summary><b>🔗 Interesting resources</b></summary>
629
636
 
630
- - [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)
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)
631
638
 
632
639
  <sub><i>+ PR to add yours</i></sub>
633
640