home-hosted 0.7.1 → 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
@@ -109,12 +111,13 @@ a minor without one.
109
111
  `identity` (which port holder is this entry's own successor: marker first, then resolved image +
110
112
  argv), `nanny` (a persistent entry's spec/state files and the liveness rules for one),
111
113
  `log-tail` (an offset reader that survives rotation and truncation), `ddns/` (one `DdnsProvider` per
112
- registrar over one HTTP call each, plus `ip` and `zone` helpers), health-check, host, telegram,
113
- 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.
114
117
  - `src/services/` — stateful orchestration: `panel` (one `WorkspaceRuntime` per workspace, workspace
115
118
  CRUD, and the single aggregate state frame — every per-workspace service hangs off it), supervisor,
116
119
  control-server, config-watch, state, auth + exposure, dependencies, history, log-buffer/log-files,
117
- 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
118
121
  `home-hosted init`: a manifest, a `.gitignore`, and the prompts stay in the CLI), `nanny` (the
119
122
  process a persistent entry runs under) and `log-relay` (the tailer that feeds its lines to the
120
123
  supervisor). It names no server — the scaffold must stay as neutral as the supervisor.
@@ -146,9 +149,10 @@ a minor without one.
146
149
  - `scripts/` — `build-uis.mjs` (build one UI, optionally zip it), `typecheck-uis.mjs`,
147
150
  `capture-media.mjs` (the README's media), `check-release-version.mjs` and `release-notes.mjs`
148
151
  (used by the release workflow), `dev.mjs` (`pnpm dev`).
149
- - `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`),
150
154
  `mockups/*.html` (hand-written UI examples in four directions) and `media/*`: their screenshots plus
151
- 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
152
156
  published; the media is a build artifact.
153
157
 
154
158
  ## Conventions
@@ -293,6 +297,12 @@ either is a last resort, and never an accidental one.
293
297
  `$HHOSTED_HOME/.hh/.ui` overrides the packaged UI at runtime.
294
298
  - **A restore writes only where a config says** — archive allowlist, `origin` matching first,
295
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.
296
306
  - **Cross-platform.** Linux/macOS/Windows: `/proc` vs `ps` vs Win32_Process, process groups vs
297
307
  `taskkill /T`, graceful fallbacks, no shell utilities assumed.
298
308
  - **Writes are atomic** (`writeFileAtomic`) and validated before commit.
@@ -345,6 +355,9 @@ either is a last resort, and never an accidental one.
345
355
  file-only blocks on their own. The copy has to be **detached**, too: a shallow one leaves a nested
346
356
  group (`health.http`) aliased to live state, so an edit to "Healthy below status" mutated the very
347
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.
348
361
  - **A dialog's footer has to be a flex sibling of a scrolling body** (`uis/stock/src/components/ui/Modal.vue`):
349
362
  the sheet is `flex flex-col` with `max-h-[88dvh]`, the body `min-h-0 flex-1 overflow-y-auto`. When
350
363
  only the body carried a max-height, a tall form pushed its own save button below the clipped
@@ -400,5 +413,5 @@ either is a last resort, and never an accidental one.
400
413
  ## Publishing
401
414
 
402
415
  `pnpm pack` runs `prepack` (a full build) and ships `bin/`, `dist/`, `uis/stock/dist`, `README.md`, `docs/DDNS.md`,
403
- `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
404
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>
@@ -617,6 +622,7 @@ plus types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
617
622
  | --- | --- |
618
623
  | declare a server: every field, placeholders, port conflicts | [SERVERS.md](./docs/SERVERS.md) |
619
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) |
620
626
  | build a UI against the API | [UI_CREATION.md](./docs/UI_CREATION.md) |
621
627
  | change the internals: architecture and the rules | [AGENTS.md](./AGENTS.md) |
622
628
  | poke the live API on your own panel | [/openapi/ui](http://127.0.0.1:3999/openapi/ui) |
@@ -628,7 +634,7 @@ plus types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
628
634
  <details>
629
635
  <summary><b>🔗 Interesting resources</b></summary>
630
636
 
631
- - [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)
632
638
 
633
639
  <sub><i>+ PR to add yours</i></sub>
634
640