home-hosted 0.7.16 → 0.7.18
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 +193 -385
- package/README.md +25 -6
- package/dist/cli.js +1082 -542
- package/dist/cli.js.map +1 -1
- package/docs/DDNS.md +6 -2
- package/docs/REVERSE_PROXY.md +11 -4
- package/docs/SERVERS.md +8 -3
- package/docs/UI_CREATION.md +13 -4
- package/package.json +3 -2
- package/uis/stock/dist/assets/{index-CAp6K13b.js → index-DaynUz2r.js} +7 -7
- package/uis/stock/dist/assets/{index-D8S55m4E.css → index-Dp4rjHQQ.css} +1 -1
- package/uis/stock/dist/index.html +2 -2
- package/uis/stock/dist/ui.json +3 -3
package/docs/DDNS.md
CHANGED
|
@@ -30,7 +30,9 @@ credentials button saves on its own, being a secret. Provider credentials never
|
|
|
30
30
|
|
|
31
31
|
Credentials live in the workspace's `.secrets.json` (0600), keyed by the account `id` and sealed with
|
|
32
32
|
AES-256-GCM under `HHOSTED_DDNS_SECRET` — set your own, or the default `hh` only stops a casual
|
|
33
|
-
read. The
|
|
33
|
+
read. The panel logs a warning when it seals credentials under that default, so a copied state
|
|
34
|
+
directory is not the first time you hear about it. The block above is the workspace's
|
|
35
|
+
`.hh/<workspace>/settings.json`; the credentials are cached
|
|
34
36
|
separately in `.secrets.json`, both beside that workspace's `servers.config.json`. The account does
|
|
35
37
|
not have to be saved first, and the provider is stored with the entry, so an id that later changes
|
|
36
38
|
provider does not silently keep the old secret. Removing an account drops its credentials on the next
|
|
@@ -49,7 +51,9 @@ Header fields:
|
|
|
49
51
|
| `domains[]` | — | `host`, `account`, `types` (`A`/`AAAA`, default `A`), `proxied` (default off; Cloudflare only), optional `ttl`, `zone`, `enabled`. |
|
|
50
52
|
|
|
51
53
|
Per-hostname `zone` pins the registered domain — only needed where a provider has to be told the apex
|
|
52
|
-
and the guess is wrong (`home.example.co.uk` is handled; an unusual suffix may not be).
|
|
54
|
+
and the guess is wrong (`home.example.co.uk` is handled; an unusual suffix may not be). It must be a
|
|
55
|
+
domain the host is **inside**: a typo is refused on save, because the record name would otherwise become
|
|
56
|
+
the whole hostname and the write would go to a domain that is not yours.
|
|
53
57
|
|
|
54
58
|
## Providers
|
|
55
59
|
|
package/docs/REVERSE_PROXY.md
CHANGED
|
@@ -67,13 +67,18 @@ panel did not start is ever killed.
|
|
|
67
67
|
| `url` | For `target: "external"`, e.g. `http://10.0.0.5:8080`. The port is optional: without one the scheme's own default is used (`:80` for `http:`, `:443` for `https:`). An `https://` upstream is dialled with TLS, and **its certificate is not verified** — a homelab upstream behind a self-signed certificate works, and so does an impostor. |
|
|
68
68
|
| `path` | Optional prefix, so one hostname can serve several apps. |
|
|
69
69
|
| `tls` | `auto` (default), `off`, `manual`. |
|
|
70
|
+
| `dnsAccount` | Optional `workspace/account` for a DNS-01 challenge, referencing a DNS account from Settings. Empty picks the workspace's only account that can write TXT records, so a single-account setup needs nothing here. |
|
|
70
71
|
|
|
71
72
|
The list's hostname is a link to where the route answers — `https://host:<https port>`, or
|
|
72
73
|
the plain port for `tls: "off"`, path included. A route that is switched off is plain text.
|
|
73
74
|
|
|
74
75
|
A route pointing at the panel is exposure, and answers to the same rule as binding the
|
|
75
76
|
listener beyond loopback: authentication on and a non-default password, or the save is
|
|
76
|
-
refused.
|
|
77
|
+
refused. `tls` does not change that bar — it is about who can reach the panel, not whether
|
|
78
|
+
the hop is encrypted, and `tls: "off"` is if anything the worse case. The engine upstreams a
|
|
79
|
+
panel route to `127.0.0.1`, which the panel's own guard reads as a local request, so a
|
|
80
|
+
plain-HTTP route with no password set would serve the panel to anyone who reached that
|
|
81
|
+
hostname.
|
|
77
82
|
|
|
78
83
|
## HTTPS
|
|
79
84
|
|
|
@@ -143,9 +148,11 @@ route still serves it — so the two can never disagree.
|
|
|
143
148
|
|
|
144
149
|
Certificates are read, never edited: the panel reports each pair's subject, issuer,
|
|
145
150
|
expiry and the hostnames it covers (from its SANs, wildcards included), and marks a pair
|
|
146
|
-
no route would be served from.
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
no route would be served from. A pair outside its validity window — expired, or not valid
|
|
152
|
+
yet — is never served, and the route that asks for it is refused with the reason; a pair
|
|
153
|
+
whose key does not match its certificate is refused the same way. An uploaded pair is
|
|
154
|
+
re-read as soon as its file changes, so editing or replacing one outside the panel is
|
|
155
|
+
picked up on the next look; the engine's
|
|
149
156
|
own certificate store (the issued certificates) is read at most every 15 seconds.
|
|
150
157
|
|
|
151
158
|
Certificates are managed by the engine, inside `.hh/.proxy/engine/`, and nothing else on
|
package/docs/SERVERS.md
CHANGED
|
@@ -31,14 +31,16 @@ servers and its state together.
|
|
|
31
31
|
| --- | --- |
|
|
32
32
|
| `command`, `args`, `cwd` | what to run, with `{placeholders}` resolved per entry |
|
|
33
33
|
| `env`, `dataEnvs`, `envFile` | environment; `dataEnvs` also marks data directories for backups, `envFile` keeps secrets out of the config |
|
|
34
|
+
| `enabled` | `true` (default) starts it; `false` keeps the entry in the config but refuses to start it, stops it if it is running, and leaves a disabled `persistent` entry unattached at boot |
|
|
34
35
|
| `port`, `bind` | enables the readiness wait, health checks and the conflict preflight; `local` keeps it on `127.0.0.1` |
|
|
35
36
|
| `onPortConflict` | `block` (default), `warn`, `follow`, `reclaim`, or `kill` — see below |
|
|
36
37
|
| `persistent` | run it under its own nanny so it survives the panel — see below |
|
|
37
|
-
| `health.mode` | `port` (TCP connect) or `http` (path, expected status, expected body) |
|
|
38
|
+
| `health.mode` | `port` (TCP connect) or `http` (path, expected status, expected body — a `HEAD` carries no body, so it is warned about and the body check is skipped) |
|
|
38
39
|
| `health.unhealthyThreshold`, `forceRestartAfterMs` | how many failed probes before the card warns, and when to restart anyway |
|
|
39
40
|
| `restart.*` | backoff: `maxRetries`, `baseDelayMs`, `factor`, `maxDelayMs`, `resetAfterMs` |
|
|
40
41
|
| `stop.*` | `signal`, `killGroup`, `graceMs`, and `killPortHolders` to sweep a leftover listener |
|
|
41
|
-
| `dependsOn` | ids that must be healthy first;
|
|
42
|
+
| `dependsOn` | ids that must be healthy first. `stop-all`/`down` stop dependents before their dependencies; stopping **one** server does not cascade — the log names any dependent it left running |
|
|
43
|
+
| `logBufferLines` | how many lines of live output the panel keeps in memory per entry (50–100000, default 500). The file on disk is separate: this is the buffer the Logs page streams from |
|
|
42
44
|
| `resources.maxRssBytes` | restart when the process tree grows past a limit |
|
|
43
45
|
| `bootstrap` | one command to run once before the first start (migrations, warmups) |
|
|
44
46
|
| `backupPaths`, `backupIgnoreGenerated` | extra paths this entry owns, included in backups; the flag (on by default) skips the known build and dependency directories inside them — `node_modules`, `dist`, `.next`, framework caches |
|
|
@@ -167,7 +169,10 @@ What that changes:
|
|
|
167
169
|
workspace's `.logs/`, because there is no pipe to carry the output.
|
|
168
170
|
- **A crash while the panel is away is reported, not restarted.** Retries and backoff remain the
|
|
169
171
|
panel's job, so an entry that dies with nobody watching shows up as **crashed** on the next boot
|
|
170
|
-
with the exit code
|
|
172
|
+
with the reason in its log — the exit code, explained when it has a conventional meaning
|
|
173
|
+
(`code 137 (SIGKILL — which the kernel sends when memory runs out)`), because that is the one an
|
|
174
|
+
operator would otherwise have to look up. `home-hosted status` prints the directory those files
|
|
175
|
+
live in, and names the `<id>.log` convention once there is one to read.
|
|
171
176
|
- **Port policies still apply, as a fallback.** A persistent entry reattaches from its state file
|
|
172
177
|
before any preflight runs, so `onPortConflict` matters only when that state is gone — and `follow`
|
|
173
178
|
is then the policy that adopts instead of blocking.
|
package/docs/UI_CREATION.md
CHANGED
|
@@ -133,6 +133,7 @@ naming one that does not exist is a `404 UNKNOWN_WORKSPACE`, never a silent fall
|
|
|
133
133
|
| `GET /api/servers/:id/stream` | one server's `server` + `log` frames |
|
|
134
134
|
| `POST /api/servers/:id/{start,stop,restart}`, `/api/servers/{start-all,stop-all}` | lifecycle (workspace-scoped) |
|
|
135
135
|
| `POST /api/servers/:id/free-port` | ask whatever holds that server's port to stop (`409` when nothing can be freed — a supervised listener is refused, never killed) |
|
|
136
|
+
| `POST /api/servers/:id/clear-logs` | forget the buffered lines; the file on disk is cleared separately through `DELETE /api/logs/:id` |
|
|
136
137
|
| `GET` / `POST /api/servers`, `PATCH` / `DELETE /api/servers/:id` | the entries themselves, in one workspace |
|
|
137
138
|
| `GET /api/logs`, `/api/logs/:id?tail=&search=`, `/api/logs/:id/download?file=` | persisted logs, for one workspace |
|
|
138
139
|
| `GET` / `PATCH /api/settings` | the panel-wide settings (`control.label`, host thresholds, backups, UI) |
|
|
@@ -141,7 +142,9 @@ naming one that does not exist is a `404 UNKNOWN_WORKSPACE`, never a silent fall
|
|
|
141
142
|
| `POST` / `DELETE /api/settings/tls` | upload or clear a PEM pair |
|
|
142
143
|
| `GET` / `PATCH /api/proxy`, `POST /api/proxy/{engine,start,stop,apply,revert}`, `PUT` / `DELETE /api/proxy/certificates/:id`, `POST /api/proxy/routes/:id/retry-certificate` | the panel-wide reverse proxy: engine, route table, its uploaded PEM pairs. Its live view also rides in the state frame as an optional `proxy` — read it defensively |
|
|
143
144
|
| `GET` / `POST /api/backups`, `/api/backups/restore`, `/api/backups/:name/download` | archives; entries are global (`global:settings`, `global:secrets`, `global:tls`) or per workspace, whose leaves are `workspace:<id>:settings`, `:servers`, `:secrets` plus `data:<path>`; a create/restore body `include` selects them |
|
|
145
|
+
| `POST` / `DELETE /api/auth/password` | set the panel password, or clear it and disable auth. `POST` needs the current one unless none is set; an empty password is refused |
|
|
144
146
|
| `POST` / `DELETE /api/notifications/token`, `/api/notifications/test`, `/detect-chats` | Telegram, per workspace |
|
|
147
|
+
| `GET` / `PUT /api/ddns`, `PUT` / `DELETE /api/ddns/credentials/:id`, `POST /api/ddns/check` | dynamic DNS, per workspace: the policy plus live per-record state, one account's sealed credentials (write-only — the view exposes ids and whether each is set, never the values), and a pass run now |
|
|
145
148
|
| `POST /api/auth/login`, `GET /api/auth/session`, `POST /api/auth/logout` | the session |
|
|
146
149
|
| `GET /healthz` | liveness — **no session**, and `503` when an autostart server has crashed |
|
|
147
150
|
| `GET /api/metrics` | Prometheus text (needs a session) |
|
|
@@ -185,10 +188,14 @@ session, 403 bad token or origin, 404 unknown id, 409 conflict, 413 too large.
|
|
|
185
188
|
| --- | --- |
|
|
186
189
|
| `hello` | `{ ts, state }` — the first frame, the complete snapshot |
|
|
187
190
|
| `state` | `{ ts, state }` — anything changed: a status, a resource sample, host vitals, a workspace |
|
|
188
|
-
| `server` | `{ ts, serverId, server }` — one entry
|
|
189
|
-
| `log` | `{ ts, serverId, lines }` — new output (dropped under backpressure, never state) |
|
|
191
|
+
| `server` | `{ ts, workspaceId, serverId, server }` — one entry |
|
|
192
|
+
| `log` | `{ ts, workspaceId, serverId, lines }` — new output (dropped under backpressure, never state) |
|
|
190
193
|
| `ping` | the current time, every 15 s |
|
|
191
194
|
|
|
195
|
+
`workspaceId` is on every `server` and `log` frame the panel sends, and the schema marks it optional only
|
|
196
|
+
so an **older** panel still parses: treat a frame without it as ambiguous and look the id up rather than
|
|
197
|
+
guessing, because a server id is unique only inside its workspace.
|
|
198
|
+
|
|
192
199
|
`GET /api/events` takes `?logs=0` to skip log frames and `?serverId=<id>` to follow one server — but
|
|
193
200
|
it matches on the id alone, so an id used in two workspaces mixes their frames. Use
|
|
194
201
|
`GET /api/servers/:id/stream?workspace=<id>` for one server: it validates the pair and replays the
|
|
@@ -233,8 +240,10 @@ pnpm run build:uis # every UI, zipped; releases attach th
|
|
|
233
240
|
|
|
234
241
|
Only `stock` ships inside the npm package; the rest are release assets you install from Global
|
|
235
242
|
settings.
|
|
236
|
-
`pnpm run quickcheck` type-checks every UI, and `pnpm test` picks up any `test/*.test.ts` you add
|
|
237
|
-
|
|
243
|
+
`pnpm run quickcheck` type-checks every UI, and `pnpm test` picks up any `test/*.test.ts` you add.
|
|
244
|
+
`@/…` resolves from the UI that asks — each UI's own `src`, so a test can import its own components
|
|
245
|
+
and logic directly; use `@shared/…` for what the panel and the UIs genuinely share
|
|
246
|
+
(`src/shared/`), and never import runtime server code (`@server` is types only).
|
|
238
247
|
|
|
239
248
|
## A worked example
|
|
240
249
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "home-hosted",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.18",
|
|
5
5
|
"packageManager": "pnpm@12.5.1",
|
|
6
6
|
"description": "Self-hosted control panel that keeps your home-server processes alive: supervision, restarts, port conflicts, logs — and a UI you can replace.",
|
|
7
7
|
"author": "NamesMT <dangquoctrung123@gmail.com>",
|
|
@@ -80,9 +80,10 @@
|
|
|
80
80
|
"set-token": "tsx src/cli.ts set-token",
|
|
81
81
|
"migrate": "tsx src/cli.ts migrate",
|
|
82
82
|
"lint": "eslint .",
|
|
83
|
+
"test:docs": "node scripts/check-doc-links.mjs",
|
|
83
84
|
"test": "vitest",
|
|
84
85
|
"test:types": "tsc --noEmit && node scripts/typecheck-uis.mjs",
|
|
85
|
-
"quickcheck": "pnpm run lint && pnpm run test:types",
|
|
86
|
+
"quickcheck": "pnpm run lint && pnpm run test:types && pnpm run test:docs",
|
|
86
87
|
"check": "pnpm run quickcheck && vitest run --coverage",
|
|
87
88
|
"prepare": "simple-git-hooks",
|
|
88
89
|
"prepack": "pnpm run build",
|