home-hosted 0.8.2 → 0.9.0

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.
Files changed (35) hide show
  1. package/AGENTS.md +9 -5
  2. package/README.md +50 -14
  3. package/dist/cli.js +1331 -282
  4. package/dist/cli.js.map +1 -1
  5. package/docs/DDNS.md +2 -1
  6. package/docs/NOTIFICATIONS.md +72 -20
  7. package/docs/SERVERS.md +20 -1
  8. package/docs/UI_CREATION.md +22 -7
  9. package/docs/branding/logo.svg +33 -0
  10. package/docs/branding/social-preview.png +0 -0
  11. package/package.json +27 -24
  12. package/uis/stock/dist/assets/de-DL4xkOYj.js +1 -0
  13. package/uis/stock/dist/assets/es-CKj-v3FY.js +1 -0
  14. package/uis/stock/dist/assets/fr-72WremqT.js +1 -0
  15. package/uis/stock/dist/assets/index-BNSSU69y.js +47 -0
  16. package/uis/stock/dist/assets/index-GxTN2fYn.css +2 -0
  17. package/uis/stock/dist/assets/ja-BU4XyMbC.js +1 -0
  18. package/uis/stock/dist/assets/ko-Db8xtJ0N.js +1 -0
  19. package/uis/stock/dist/assets/ru-Bs8KXP2O.js +1 -0
  20. package/uis/stock/dist/assets/vi-DdQD4h7B.js +1 -0
  21. package/uis/stock/dist/assets/zh-CN-DIrw2Ubv.js +1 -0
  22. package/uis/stock/dist/favicon.ico +0 -0
  23. package/uis/stock/dist/favicon.svg +14 -0
  24. package/uis/stock/dist/index.html +6 -2
  25. package/uis/stock/dist/ui.json +3 -3
  26. package/uis/stock/dist/assets/de-DQxDtYp2.js +0 -1
  27. package/uis/stock/dist/assets/es-BP0qzCTU.js +0 -1
  28. package/uis/stock/dist/assets/fr-DHCvxiys.js +0 -1
  29. package/uis/stock/dist/assets/index-C02FUAwz.js +0 -46
  30. package/uis/stock/dist/assets/index-Skf2FzpK.css +0 -2
  31. package/uis/stock/dist/assets/ja-DseIeS1D.js +0 -1
  32. package/uis/stock/dist/assets/ko-Dcu2b6c2.js +0 -1
  33. package/uis/stock/dist/assets/ru-DaAAVw09.js +0 -1
  34. package/uis/stock/dist/assets/vi-D780h-t9.js +0 -1
  35. package/uis/stock/dist/assets/zh-CN-C8OC-N1b.js +0 -1
package/AGENTS.md CHANGED
@@ -10,8 +10,8 @@ level: `settings.json` (listener, auth, TLS policy, host vitals, backups, revers
10
10
  `.ui/`, `.logs/` (the panel console), `.proxy/` (the engine, its generated config and nanny state), and
11
11
  `run.json` (the live daemon's pid/url/token, 0600). Every workspace owns `.hh/<id>/`: `settings.json`
12
12
  (server defaults, log retention, notifications, DDNS), `servers.config.json`, `.secrets.json` (0600:
13
- Telegram token, plus DDNS credentials sealed with AES-256-GCM under `HHOSTED_DDNS_SECRET` — only DDNS
14
- is sealed, because it is the one thing replayed to a third party), `.logs/` (per server, `<id>.log`
13
+ Telegram bot tokens, one per destination, plus DDNS credentials sealed with AES-256-GCM under
14
+ `HHOSTED_DDNS_SECRET` — only DDNS is sealed, because it is the one thing replayed to a third party), `.logs/` (per server, `<id>.log`
15
15
  plus `<id>.log.1` when it rotates), and `.state/` (nanny state, its 0600 spawn spec until read,
16
16
  `ddns.json`). A home still on the 0.6.x layout is **refused** by `ensureLayout()`, which names the file
17
17
  and tells the person to install the latest 0.7.x; 0.8.0 does not relocate it. **The package ships no
@@ -39,7 +39,7 @@ Where knowledge lives, and when to reach for each:
39
39
  | [`.agentDocs/COMPATIBILITY.md`](.agentDocs/COMPATIBILITY.md) | config and UI compatibility, migrations |
40
40
  | [`.agentDocs/RELEASING.md`](.agentDocs/RELEASING.md) | how a release is cut, and which version to dispatch |
41
41
  | [`docs/SERVERS.md`](docs/SERVERS.md) | entries, port conflicts, persistent entries |
42
- | [`docs/NOTIFICATIONS.md`](docs/NOTIFICATIONS.md) | Telegram events |
42
+ | [`docs/NOTIFICATIONS.md`](docs/NOTIFICATIONS.md) | notification destinations: Telegram, webhooks, shared defaults |
43
43
  | [`docs/DDNS.md`](docs/DDNS.md) | dynamic-DNS providers and hostnames |
44
44
  | [`docs/REVERSE_PROXY.md`](docs/REVERSE_PROXY.md) | the Caddy engine, routes, certificates |
45
45
  | [`docs/UI_CREATION.md`](docs/UI_CREATION.md) | the contract for a new UI |
@@ -56,6 +56,8 @@ pnpm run start # up --foreground (systemd, docker, a foregro
56
56
  pnpm exec tsx src/cli.ts logs # the panel's own console output (--lines, --follow, --json)
57
57
  pnpm dev # tsx-watch panel :6000 + the stock UI's Vite :6001, state in .dev-state/
58
58
  pnpm dev --ui noc-console # the same, for another UI; --port/--ui-port override
59
+ pnpm dev --home /tmp/hh-dev # state elsewhere; HHOSTED_HOME is IGNORED here, and the real
60
+ # ~/.home-hosted is refused. Dev password: `dev`
59
61
  pnpm run build # dist/cli.js + the stock UI (uis/stock/dist)
60
62
  pnpm run build:uis # every UI under uis/, zipped into uis/dist/ (release assets)
61
63
  pnpm run build:locales # i18n/sheets/*.csv -> the git-ignored i18n/generated/*.json (also
@@ -69,7 +71,8 @@ pnpm run set-password # non-interactive through HHOSTED_PASSWORD
69
71
  pnpm run set-token # --generate prints a new API token once
70
72
  pnpm run migrate # bring the config up to this release's schema
71
73
  pnpm exec tsx src/cli.ts init # scaffold a project (interactive; --yes for defaults)
72
- pnpm run media # regenerate docs/media (mockups, both served UIs, tour.gif)
74
+ pnpm run media # regenerate docs/media; needs `pnpm exec playwright install chromium`
75
+ # once (browser only; `gif` alone does not)
73
76
  ```
74
77
 
75
78
  The published bin is `home-hosted`, with an `hh` alias: both names run the same CLI.
@@ -128,7 +131,8 @@ These prevent defects if broken. The *why* for the non-obvious ones is in
128
131
  **refuses** a commit that changed a shipped UI without raising it (a UI's `test/` and its own
129
132
  `README.md` are exempt — neither is in the built asset).
130
133
  `src/shared/**` is a UI build input, so a runtime contract needs the bump too — the hook does not
131
- inspect that commit.
134
+ inspect that commit. **`tag` and `unix` are build provenance**, stamped into `dist/ui.json` by
135
+ `build-uis.mjs`; never committed to `public/ui.json`.
132
136
  - **Paths.** `dataRoot` is state, under `dataRoot/.hh`; `projectDir` is the base for relative entry
133
137
  paths. `{…}` and `${ENV}` expand in config — the full list is the table in
134
138
  [`docs/SERVERS.md`](docs/SERVERS.md), which is what to keep in step. No package-relative state.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  <div align="center">
2
2
 
3
- # 🏠 home-hosted
3
+ <img src="docs/branding/logo.svg" alt="home-hosted" width="128" height="128">
4
+
5
+ # home-hosted
4
6
 
5
7
  **The control panel for everything you self-host at home.**
6
8
 
@@ -81,7 +83,8 @@ some arguments, and the environment you give it:
81
83
  ```
82
84
 
83
85
  <sub>Manage them from the panel, `home-hosted start|stop <id>` from a shell, `home-hosted status --json`,
84
- or `GET /api/state`; `GET /healthz` is the same status line for your own monitor, no session needed.</sub>
86
+ or `GET /api/state`; `GET /healthz` is the same status line for your own monitor, no session needed —
87
+ `503` when an autostart server has crashed or a running one is failing its health check.</sub>
85
88
 
86
89
  ---
87
90
 
@@ -267,17 +270,17 @@ can be told what to be: *"Help me build a UI for home-hosted: nostalgic game the
267
270
  | 🗂 **Workspaces** | One panel, many scopes: pick a workspace in the header and it owns its servers, secrets, logs and settings. Create, rename and delete them there. Panel-wide things — listener, auth, host vitals, backups, TLS, UI — stay in **Global settings**. |
268
271
  | 🚦 **Lifecycle** | Start, stop, restart from the panel or the API; `autostart` entries come up with it. |
269
272
  | 📝 **Hand edits welcome** | Change a workspace's `servers.config.json` in an editor, a `git checkout` or a config tool: the panel notices within seconds, no restart. A file it cannot read is reported in the panel, and the running servers are left alone. |
270
- | ♻️ **Auto-restart** | Exponential backoff on crash, with the counter reset once a process stays up. |
273
+ | ♻️ **Auto-restart** | Exponential backoff on crash, with the counter reset once a process stays up. The budget is remembered across a restart of the panel. |
271
274
  | 🩺 **Health that acts** | TCP or HTTP probes per server: warn on the card, force a restart after a timeout, check ports before starting — and [follow or replace](./docs/SERVERS.md#when-a-program-restarts-itself) a program that restarts itself. |
272
275
  | 🔗 **Ordered startup** | `dependsOn` waits for a dependency to be *healthy* — not merely spawned. `stop-all`/`down` stop dependents before their dependencies; stopping one server names the dependents it leaves running. [SERVERS.md](./docs/SERVERS.md#field-reference) |
273
276
  | 📜 **Logs** | Live per-server stream, buffer plus rotated files on disk, search, filter to one stream, download, one click to clear. |
274
277
  | 📈 **Resources** | CPU and RSS of the whole process tree, with an optional memory ceiling that triggers a restart. |
275
278
  | 🌡️ **Host vitals** | Load, memory, swap, disk and CPU temperature, with thresholds that notify once and again on recovery. |
276
279
  | 🤖 **Token API** | Scripts and agents drive it with `Authorization: Bearer` — no browser, no session. [↑](#-agents-scripts-and-tools) |
277
- | 🔔 **Notifications** | Telegram on crash, unhealthy, forced restart, recovery, host thresholds and DNS changes — [setup here](./docs/NOTIFICATIONS.md). |
280
+ | 🔔 **Notifications** | One list of **destinations** — Telegram chats and webhooks (ntfy, Discord, Slack, your own endpoint) — on crash, unhealthy, forced restart, recovery, host thresholds and DNS changes. The event toggles and throttle are set once, and each destination may inherit them or carry its own. [Setup here](./docs/NOTIFICATIONS.md). |
278
281
  | 🌐 **Dynamic DNS** | Keep hostnames pointed at your public IP — Cloudflare, Namecheap, Spaceship, Porkbun, GoDaddy, Gandi and more. [DDNS.md](./docs/DDNS.md) |
279
282
  | 🔀 **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) |
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! |
283
+ | 💾 **Backups** | Global settings, TLS, workspaces, and inside each one its settings, servers, secrets and declared data — plain `.zip`, or AES-256 with a password. Each secret is picked separately, and a restore can be customized. |
281
284
  | 🌍 **Nine languages** | English, Vietnamese, Chinese, Japanese, Spanish, Korean, Russian, French, German — picked on the login page or in **Global settings → Language**, kept per browser. |
282
285
  | 🎨 **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) |
283
286
  | 🔐 **Security** | Cookie sessions, API tokens, scrypt hashes, per-IP lockout, optional TLS, and a refusal to expose itself without a password. |
@@ -464,10 +467,15 @@ Everything binds `127.0.0.1` until you say otherwise.
464
467
 
465
468
  ## 🔔 Notifications
466
469
 
467
- Telegram, when something happens while you are not looking: a server that gave up restarting, a
470
+ A message when something happens while you are not looking: a server that gave up restarting, a
468
471
  failing health check, a forced restart, a recovery, a host threshold (disk, memory, swap, load,
469
- temperature), or a dynamic DNS change. Opt-in, rate-limited per server *and* reason, and the bot token
470
- stays in the secrets file. **Two minutes of setup: [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md).**
472
+ temperature), or a dynamic DNS change.
473
+
474
+ Every alert goes to a list of **destinations** — a Telegram chat, a webhook, or several of each. The
475
+ event toggles and the throttle are set **once**, and each destination either inherits them or carries
476
+ its own. Rate-limited per destination, server *and* reason. Bot tokens and webhook credentials stay in
477
+ the secrets file, never in a config. **Two minutes of setup:
478
+ [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md).**
471
479
 
472
480
  ---
473
481
 
@@ -485,11 +493,38 @@ Provider tokens stay in the workspace's secrets file. **Providers and the config
485
493
 
486
494
  **Global settings → Backups** archives the panel's own settings, its secrets and the TLS pair — plus
487
495
  every workspace you pick. It is two-level: the top of *Create backup…* lists the global items and
488
- each workspace; opening a workspace lists its **settings**, **servers**, **secrets** and every data
489
- directory its entries declare, and you can drop any single piece. *Restore…* shows the same list from
490
- an archive before anything is written. Either way it is an ordinary `.zip`, or WinZip AES-256 with a
491
- password, restored per path. Known build output and dependency directories (`node_modules`, `dist`,
492
- `.next`, framework caches) are skipped per entry; `backupIgnoreGenerated: false` captures them anyway.
496
+ each workspace; opening one lists its **settings**, **servers**, its **secrets** and every data
497
+ directory its entries declare, and you can drop any single piece. Each secret has its own checkbox.
498
+ *Restore…* shows the same list from an archive before anything is written. Either way it is an
499
+ ordinary `.zip`, or WinZip AES-256 with a password, restored per path. Known build output and
500
+ dependency directories (`node_modules`, `dist`, `.next`, framework caches) are skipped per entry;
501
+ `backupIgnoreGenerated: false` captures them anyway.
502
+
503
+ **Automatic backups** write one archive on a schedule you set — the page asks in hours; the config
504
+ stores `intervalMs` — with a retention of its own (`Keep automatic`). The two counts are independent: `Keep` bounds the archives you take by hand,
505
+ and a scheduled pass never prunes one of yours to make room for its own. Both live in the same
506
+ directory, and an automatic archive is named `auto-…` so you can tell them apart — a name you drop
507
+ in by hand with that prefix is treated as one, so do not use it.
508
+
509
+ <details>
510
+ <summary><b>🔑 Restoring a secret</b></summary>
511
+
512
+ An archive records **which release wrote it and the config schema it understood**; a restore refuses
513
+ a newer schema outright rather than restoring the parts it recognizes.
514
+
515
+ Ticking a **single secret** merges by field: it lands and the others in that file are left alone. So
516
+ restoring the Telegram token does not clear the DDNS credentials. Ticking the whole **Secrets** entry
517
+ replaces the file with the archive's copy instead, which is what makes the two worth telling apart.
518
+
519
+ Two things to know before restoring onto a machine already in use:
520
+
521
+ - **The password hash and API token hash are secrets like any other.** Restoring them reinstates the
522
+ old machine's password and long-lived token, so anyone holding that token keeps access. Leave them
523
+ unticked, or rotate afterwards with `pnpm run set-token --generate`.
524
+ - **`HHOSTED_DDNS_SECRET` is not in the archive.** DDNS credentials travel sealed under it, so a host
525
+ with a different value cannot open them. Supply the same secret, or re-enter the credentials.
526
+
527
+ </details>
493
528
 
494
529
  <details>
495
530
  <summary><b>🚚 One archive is a whole setup</b></summary>
@@ -649,6 +684,7 @@ src/cli.ts the command line; one file per command under src/cli/
649
684
  src/index.ts the control plane itself, used by `up --foreground`
650
685
  uis/ UIs: `stock` (shipped in the package) and alternatives — any framework, static output
651
686
  bin/ the published entry point
687
+ docs/branding/ logo, icons and the social preview; the SVGs are the source, `pnpm run brand` renders them
652
688
  docs/ topic docs, UI examples and the README's media
653
689
  scripts/ builds, typechecks, media capture, release helpers
654
690
  test/ the vitest suite
@@ -665,7 +701,7 @@ plus types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
665
701
  | if you want to… | read |
666
702
  | --- | --- |
667
703
  | declare a server: every field, placeholders, port conflicts | [SERVERS.md](./docs/SERVERS.md) |
668
- | get Telegram alerts working end to end | [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md) |
704
+ | get Telegram or webhook alerts working end to end | [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md) |
669
705
  | expose the stack behind one hostname, with HTTPS | [REVERSE_PROXY.md](./docs/REVERSE_PROXY.md) |
670
706
  | build a UI against the API | [UI_CREATION.md](./docs/UI_CREATION.md) |
671
707
  | change the internals: architecture and the rules | [AGENTS.md](./AGENTS.md) |