home-hosted 0.4.1 → 0.5.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.
- package/AGENTS.md +76 -15
- package/README.md +65 -18
- package/dist/cli.js +1586 -616
- package/dist/cli.js.map +1 -1
- package/{SERVERS.md → docs/SERVERS.md} +16 -5
- package/{UI_CREATION.md → docs/UI_CREATION.md} +34 -2
- package/package.json +7 -4
- package/uis/stock/dist/assets/index-C7Rc5zP8.js +46 -0
- package/uis/stock/dist/assets/index-DMo6krK0.css +2 -0
- package/uis/stock/dist/index.html +2 -2
- package/uis/stock/dist/assets/index-DOD80X00.css +0 -2
- package/uis/stock/dist/assets/index-Dl2nDThm.js +0 -47
- /package/{NOTIFICATIONS.md → docs/NOTIFICATIONS.md} +0 -0
package/AGENTS.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
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 `$HHOSTED_HOME/servers.config.json`
|
|
5
|
-
and serves a UI. User docs: `README.md`, `SERVERS.md` (entries and port conflicts),
|
|
6
|
-
`NOTIFICATIONS.md`; UI authors: `UI_CREATION.md`.
|
|
5
|
+
and serves a UI. User docs: `README.md`, `docs/SERVERS.md` (entries and port conflicts),
|
|
6
|
+
`docs/NOTIFICATIONS.md`; UI authors: `docs/UI_CREATION.md`.
|
|
7
7
|
|
|
8
8
|
State lives only in `$HHOSTED_HOME` (default `~/.home-hosted`): `servers.config.json`,
|
|
9
9
|
`.control-secrets.json` (0600: password hash, API token hash, Telegram bot token), `.logs/`, `.tls/`,
|
|
@@ -37,9 +37,22 @@ exists, so the first release has to be published by hand.
|
|
|
37
37
|
|
|
38
38
|
## Architecture (and why)
|
|
39
39
|
|
|
40
|
-
- `src/cli.ts` — the CLI
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
- `src/cli.ts` — the CLI's thin root. Two things happen before [citty](https://github.com/unjs/citty)
|
|
41
|
+
is asked anything: `--home`/`--project` are peeled off and applied (`src/cli/args.ts`), and the
|
|
42
|
+
curated dispatch — `help`/`version`, `unknown command`, and the `-p 4000` shorthand for `up` — is
|
|
43
|
+
decided, because `#src/helpers/paths.ts` resolves at import time. Its static imports stay node
|
|
44
|
+
builtins, citty and those two path-free local modules; every command is a lazy
|
|
45
|
+
`() => import('#src/cli/<command>')` in citty's `subCommands`, so a command module *may* use static
|
|
46
|
+
`#src` imports (that is the whole point of the pre-pass). citty's `runMain` is deliberately not
|
|
47
|
+
used: it prints its own usage and `console.error`s before `process.exit(1)`, replacing `fail()`'s
|
|
48
|
+
one error shape; the root calls `runCommand` and catches. `src/cli/<command>.ts` is one command per
|
|
49
|
+
file — citty owns dispatch and argument parsing, with `--no-autostart`/`--no-install` declared as
|
|
50
|
+
boolean negations rather than literal arg names. The curated `--help` prose stays in `src/cli.ts`
|
|
51
|
+
because citty cannot generate it. `up` re-spawns itself detached as `up --foreground …`, with the
|
|
52
|
+
argv built from the *parsed* flags (`buildDaemonArgv`) plus this file's own URL as the entry, so it
|
|
53
|
+
works under tsx and from `dist/cli.js` alike. `src/cli/io.ts` is the one place the readline prompts
|
|
54
|
+
and colours live; commands take `prompt`/`style` through that seam (`src/cli/ui-switch.ts` keeps
|
|
55
|
+
`UiSwitchIo`).
|
|
43
56
|
- `src/index.ts` — `runControlPlane()`: wiring, startup guards (exposure, free port, live run.json),
|
|
44
57
|
`run.json`, signals. Wiring belongs here and nowhere else.
|
|
45
58
|
- `src/app.ts` — the Hono root, chained routes only. `/_hh` is mounted *before* the `/api/*` auth
|
|
@@ -55,10 +68,10 @@ exists, so the first release has to be published by hand.
|
|
|
55
68
|
- `src/providers/` — stateless leaves: `process` (spawn, `terminate`, `terminatePid` for a process we
|
|
56
69
|
adopted), `port` (probe, holder lookup, `terminatePids`), `proc` (the sampler, plus
|
|
57
70
|
`processCarriesServerId` for ownership), health-check, host, telegram, archive.
|
|
58
|
-
- `src/services/` — stateful orchestration: supervisor, control-server,
|
|
59
|
-
dependencies, history, log-buffer/log-files, notifications, host-monitor, backups,
|
|
60
|
-
`init` (the scaffold behind `home-hosted init`: a manifest, a `.gitignore`, and the
|
|
61
|
-
the CLI). It names no server — the scaffold must stay as neutral as the supervisor.
|
|
71
|
+
- `src/services/` — stateful orchestration: supervisor, control-server, config-watch, state,
|
|
72
|
+
auth + exposure, dependencies, history, log-buffer/log-files, notifications, host-monitor, backups,
|
|
73
|
+
tls, ui, plus `init` (the scaffold behind `home-hosted init`: a manifest, a `.gitignore`, and the
|
|
74
|
+
prompts stay in the CLI). It names no server — the scaffold must stay as neutral as the supervisor.
|
|
62
75
|
- `src/middleware/auth.ts` — the `/api/*` guard, and `requestIdentity()`, the one place a request's
|
|
63
76
|
credentials are read: the `hh2_session` cookie or `Authorization: Bearer <api token>`. A token is
|
|
64
77
|
a first-class credential (same authority as a signed-in browser) and is verified from the secrets
|
|
@@ -74,12 +87,20 @@ exists, so the first release has to be published by hand.
|
|
|
74
87
|
- `scripts/` — `build-uis.mjs` (build one UI, optionally zip it), `typecheck-uis.mjs`,
|
|
75
88
|
`capture-media.mjs` (the README's media), `check-release-version.mjs` and `release-notes.mjs`
|
|
76
89
|
(used by the release workflow), `dev.mjs` (`pnpm dev`).
|
|
77
|
-
- `docs/` —
|
|
78
|
-
|
|
90
|
+
- `docs/` — the topic docs (`SERVERS.md`, `NOTIFICATIONS.md`, `UI_CREATION.md`), `mockups/*.html`
|
|
91
|
+
(hand-written UI examples in four directions) and `media/*`: their screenshots plus the README's
|
|
92
|
+
`tour.gif`, all regenerated by `pnpm run media`. Only the three `.md` files are published; the
|
|
93
|
+
media is a build artifact.
|
|
79
94
|
|
|
80
95
|
## Conventions
|
|
81
96
|
|
|
82
97
|
- `#src/*` imports inside `src/`; UIs use `@shared/*`.
|
|
98
|
+
- A single on/off setting is a `ToggleSwitch`; `CheckField` is only for picking items out of a set
|
|
99
|
+
(the restore plan). A checkbox in a `FieldGroup` grid reads as misaligned next to the inputs.
|
|
100
|
+
- A create body carries only what differs from what the entry would inherit — the schema's defaults
|
|
101
|
+
with the panel's `Settings → Server defaults` on top (`inheritBaseline` + `diffFields` in both
|
|
102
|
+
UIs). A value written into `servers.config.json` stops following those defaults, so anything the
|
|
103
|
+
person did not decide stays out; the editor applies the same rule to an edit.
|
|
83
104
|
- ArkType at every runtime boundary: routes use `validate('json'|'query'|'param', schema)` then
|
|
84
105
|
`c.req.valid(...)`; ad-hoc payloads use `parseOrThrow`. Schemas reject undeclared keys.
|
|
85
106
|
- Every failure is a `DetailedError` (`@namesmt/utils`), mapped by `src/helpers/error.ts` into one
|
|
@@ -175,8 +196,44 @@ either is a last resort, and never an accidental one.
|
|
|
175
196
|
`PATCH /api/settings`, the TLS routes and `/_hh/shutdown` defer with `afterResponse()`.
|
|
176
197
|
- `Supervisor.start()` sets `starting` synchronously before its first await, and `stop()` sets
|
|
177
198
|
`stopping` first: overlapping calls would double-spawn or resurrect a stopped process. Tests must
|
|
178
|
-
call `supervisor.dispose()`.
|
|
199
|
+
call `supervisor.dispose()`. `stopping` is cleared on **every** exit path of `stopEntry()`: the
|
|
200
|
+
early return for an entry with no child used to leave it set, and since `start()` refuses while it
|
|
201
|
+
is true, one Stop on an already-stopped server made that entry unstartable until a daemon restart.
|
|
202
|
+
- Timers and `void`-ed promises in the supervisor carry a `.catch`: a throw in the tick or in a retry
|
|
203
|
+
is an unhandled rejection, and Node 24 ends the process on one.
|
|
204
|
+
- **`Settings → Server defaults` merge into the nested groups key by key** (`mergeDefaults` in
|
|
205
|
+
`src/config/schema.ts`, used by both the config parser and `validateServer`). A shallow
|
|
206
|
+
`{ ...defaults, ...entry }` lets an entry that decides `restart.maxRetries` silently drop the
|
|
207
|
+
panel's `restart.baseDelayMs` — and the create/edit diff flow depends on inheriting exactly that.
|
|
208
|
+
- The curated CLI dispatch reads only the **flag** forms (`--help`/`-h`, `--version`/`-v`) after a
|
|
209
|
+
command name, never a bare `help`/`version`: that is an option's value (`init --name help`).
|
|
179
210
|
- Port preflight re-probes after 300 ms — a just-closed listener can still complete a handshake.
|
|
211
|
+
- **The config is re-read whenever the file changes on disk** (`src/services/config-watch.ts` →
|
|
212
|
+
`ConfigStore.reloadFromDisk()`, wired in `src/index.ts`). The watch is on the *directory*, because
|
|
213
|
+
an editor's save is a temporary file renamed over the target — the inode changes, the name does not
|
|
214
|
+
— and a two-second poll backs it up where `fs.watch` is undependable (network mounts). The store
|
|
215
|
+
compares the bytes it last read and the bytes it wrote, so the panel's own saves never reload
|
|
216
|
+
anything, and a revision it cannot read (unparseable JSON included — that path used to fall back to
|
|
217
|
+
an *empty* config until a watcher made it reachable, which would have stopped every server) is
|
|
218
|
+
reported in the state frame while the running config is kept. A changed definition takes effect on
|
|
219
|
+
that entry's next start; only an added `autostart` entry is started, and `--no-autostart` still
|
|
220
|
+
means the panel starts nothing on its own.
|
|
221
|
+
- **The shell must watch the *session*, not only the flags it derives.** With authentication off,
|
|
222
|
+
`authRequired` and `authenticated` are both `false` from the first paint to the last, so a watcher
|
|
223
|
+
on those two never ran after the session landed: the panel never opened its event stream, the
|
|
224
|
+
connection badge sat on "Connecting", and the dashboard showed one stale snapshot forever. Both
|
|
225
|
+
UIs turn it into a single `streamDecision(session)` value (`wait` | `connect` | `login`) and watch
|
|
226
|
+
that, which is also what the test pins.
|
|
227
|
+
- **A form that copies live state must guard per block, never globally.** The host thresholds and the
|
|
228
|
+
backups policy arrive from `/api/settings` *after* the SSE frame, so a single "has anything
|
|
229
|
+
changed?" gate leaves them showing schema defaults forever — the backups toggle reported itself as
|
|
230
|
+
changed and flipped back to `true` on every reload. `uis/stock` compares each block against the
|
|
231
|
+
snapshot it was last filled from (`blockSnapshot`/`isBlockEdited`, `syncFromLive`) and reads the
|
|
232
|
+
file-only blocks on their own.
|
|
233
|
+
- **A dialog's footer has to be a flex sibling of a scrolling body** (`uis/stock/src/components/ui/Modal.vue`):
|
|
234
|
+
the sheet is `flex flex-col` with `max-h-[88dvh]`, the body `min-h-0 flex-1 overflow-y-auto`. When
|
|
235
|
+
only the body carried a max-height, a tall form pushed its own save button below the clipped
|
|
236
|
+
edge — the add-server dialog looked like it had no save button at all.
|
|
180
237
|
- Vue does not notify a computed's subscribers when its recomputed value is `Object.is`-equal to the
|
|
181
238
|
old one, so anything mutated in place silently freezes every value derived from it. The log buffers
|
|
182
239
|
(`uis/stock/src/composables/useControlPlane.ts`) therefore hand out a **new array per batch**, and
|
|
@@ -197,6 +254,10 @@ either is a last resort, and never an accidental one.
|
|
|
197
254
|
- Backups are zips; a password makes them WinZip AES-256/AE-2, and zero-byte entries stay
|
|
198
255
|
unencrypted on purpose (p7zip 16.02 reports a CRC failure otherwise). `list()` is sync, so
|
|
199
256
|
encryption flags are cached and refreshed in the background.
|
|
257
|
+
- Generated directories are skipped by the `filter` handed to `fs.cpSync`, matched on an exact path
|
|
258
|
+
segment at any depth (`src/shared/generated.ts`): `dist/` and `app/node_modules/` go, while
|
|
259
|
+
`distributed/` and `my-node_modules/` stay. Only the paths an entry declares are filtered — a
|
|
260
|
+
global `backups.includePaths` entry is captured as it stands.
|
|
200
261
|
- `vite.server.config.ts` targets `node22` while `engines` requires >= 24 — deliberate margin, leave it.
|
|
201
262
|
- `pnpm test` watches; CI runs `vitest run`.
|
|
202
263
|
- `pnpm run media` drives Chromium through Playwright, which needs fonts *and* the X/NSS/Mesa
|
|
@@ -211,11 +272,11 @@ either is a last resort, and never an accidental one.
|
|
|
211
272
|
- **Server field**: `serverSchema` + its patch in contracts, merge keys in `config/store.ts` when
|
|
212
273
|
nested, the form in `uis/stock/src/components/settings/`, and the contract tests.
|
|
213
274
|
- **UI**: a new `uis/<name>/` with a `vite.config.ts` from the shared factory;
|
|
214
|
-
`node scripts/build-uis.mjs <name> --zip`. The contract is `UI_CREATION.md`.
|
|
275
|
+
`node scripts/build-uis.mjs <name> --zip`. The contract is `docs/UI_CREATION.md`.
|
|
215
276
|
- **Capability**: a stateless `src/providers/*` returning plain data.
|
|
216
277
|
|
|
217
278
|
## Publishing
|
|
218
279
|
|
|
219
|
-
`pnpm pack` runs `prepack` (a full build) and ships `bin/`, `dist/`, `uis/stock/dist`, `README.md`, `SERVERS.md`,
|
|
220
|
-
`NOTIFICATIONS.md`, `UI_CREATION.md`, `AGENTS.md` and `LICENSE`. The bin falls back to tsx so `pnpm link` works before a
|
|
280
|
+
`pnpm pack` runs `prepack` (a full build) and ships `bin/`, `dist/`, `uis/stock/dist`, `README.md`, `docs/SERVERS.md`,
|
|
281
|
+
`docs/NOTIFICATIONS.md`, `docs/UI_CREATION.md`, `AGENTS.md` and `LICENSE`. The bin falls back to tsx so `pnpm link` works before a
|
|
221
282
|
build; `vue`/`vue-router` are devDependencies because the UIs are prebuilt.
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ them, watches them, restarts what dies, and shows you one page of what is going
|
|
|
16
16
|
[](./LICENSE)
|
|
17
17
|
[](https://nodejs.org)
|
|
18
18
|
|
|
19
|
-
[🚀 Quick start](#-quick-start) · [🤖 Agents & API](#-agents-scripts-and-tools) · [✨ Features](#-features) · [🧩 Servers](./SERVERS.md) · [🛠 CLI](#-cli) · [🔔 Notifications](./NOTIFICATIONS.md) · [🎨 BYOU](#-bring-your-own-ui-byou)
|
|
19
|
+
[🚀 Quick start](#-quick-start) · [🤖 Agents & API](#-agents-scripts-and-tools) · [✨ Features](#-features) · [🧩 Servers](./docs/SERVERS.md) · [🛠 CLI](#-cli) · [🔔 Notifications](./docs/NOTIFICATIONS.md) · [🎨 BYOU](#-bring-your-own-ui-byou)
|
|
20
20
|
|
|
21
21
|
</div>
|
|
22
22
|
|
|
@@ -34,6 +34,40 @@ shortcuts wizards), and UI directions you could build yourself —
|
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
37
|
+
## 🤔 Why?
|
|
38
|
+
|
|
39
|
+
Running services on a home machine usually means one of two extremes: `tmux` sessions you
|
|
40
|
+
forget about, or a hand-written systemd unit per service (times six) — or a complex docker/k8s
|
|
41
|
+
setup?
|
|
42
|
+
|
|
43
|
+
| | |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| ❌ **"Is it still running?"** | You check with `ps`, then `curl`, then hope. |
|
|
46
|
+
| ❌ **Silent deaths** | Something crashes at 3am and you notice days later. |
|
|
47
|
+
| ❌ **One terminal per service** | Logs scroll away in tabs you closed. |
|
|
48
|
+
| ❌ **Fragile restarts** | The box reboots and half the stack is gone. |
|
|
49
|
+
| ✅ **home-hosted** | Declare it once, watch it forever, one command to stop it all. |
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
┌──────────────────────────────────────┐
|
|
53
|
+
your browser ──▶│ home-hosted · 127.0.0.1:3999 │
|
|
54
|
+
│ your UI + JSON API + SSE logs │
|
|
55
|
+
└───────────────┬──────────────────────┘
|
|
56
|
+
│ supervises
|
|
57
|
+
┌──────────────────────────┼──────────────────────────┐
|
|
58
|
+
▼ ▼ ▼
|
|
59
|
+
┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
60
|
+
│ gateway │ │ files │ │ bot │
|
|
61
|
+
│ :4000 │ │ :4010 │ │ ... │
|
|
62
|
+
└─────────┘ └─────────┘ └─────────┘
|
|
63
|
+
health ✓ health ✓ restarts ↻
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
It ships with **nothing**. No blessed paths, no "data directory" setting, no opinion about
|
|
67
|
+
what you run — a server is a command, some arguments, and the environment you give it.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
37
71
|
## ⚡ Quick start
|
|
38
72
|
|
|
39
73
|
```bash
|
|
@@ -104,6 +138,10 @@ Worked example, with per-server data inside the project:
|
|
|
104
138
|
|
|
105
139
|
<sub>Call them as `pnpm run up` — `pnpm up` is pnpm's own update, not your script.</sub>
|
|
106
140
|
|
|
141
|
+
> [!TIP]
|
|
142
|
+
> Give a project its own panel port (`control.port`, e.g. `4399`) — the default `3999` is what the
|
|
143
|
+
> global instance and every other project also want.
|
|
144
|
+
|
|
107
145
|
</details>
|
|
108
146
|
|
|
109
147
|
<details>
|
|
@@ -160,7 +198,7 @@ revokes it instantly.
|
|
|
160
198
|
| `PATCH /api/servers/:id`, `PATCH /api/settings` | edit configuration |
|
|
161
199
|
| `GET /api/logs`, `/api/backups`, `/api/notifications` | logs, archives, Telegram |
|
|
162
200
|
| `GET /healthz` | no session needed — the one an external monitor wants (its per-server detail needs a credential) |
|
|
163
|
-
| `GET /api/metrics` | Prometheus text |
|
|
201
|
+
| `GET /api/metrics` | Prometheus text (needs a token or session, like every `/api` route) |
|
|
164
202
|
|
|
165
203
|
`GET /openapi/spec.json` describes all of it, `/openapi/ui` is the browsable version, and every error
|
|
166
204
|
comes back as one envelope (`{ message, code, detail }`) with a stable `code` a tool can branch on.
|
|
@@ -175,9 +213,9 @@ Give the agent four things and it can run your home server without guessing:
|
|
|
175
213
|
1. the token (`home-hosted set-token --generate`),
|
|
176
214
|
2. `http://127.0.0.1:3999/openapi/spec.json` — the API it may call,
|
|
177
215
|
3. `home-hosted status --json` — where things are,
|
|
178
|
-
4. [SERVERS.md](./SERVERS.md) — how an entry is declared when it needs a new server.
|
|
216
|
+
4. [SERVERS.md](./docs/SERVERS.md) — how an entry is declared when it needs a new server.
|
|
179
217
|
|
|
180
|
-
For a UI rather than the API, [UI_CREATION.md](./UI_CREATION.md) is the whole contract, and the panel
|
|
218
|
+
For a UI rather than the API, [UI_CREATION.md](./docs/UI_CREATION.md) is the whole contract, and the panel
|
|
181
219
|
can be told what to be: *"Help me build a UI for home-hosted: nostalgic game theme, including …"*.
|
|
182
220
|
|
|
183
221
|
</details>
|
|
@@ -189,16 +227,17 @@ can be told what to be: *"Help me build a UI for home-hosted: nostalgic game the
|
|
|
189
227
|
| | |
|
|
190
228
|
| --- | --- |
|
|
191
229
|
| 🚦 **Lifecycle** | Start, stop, restart from the panel or the API; `autostart` entries come up with it. |
|
|
230
|
+
| 📝 **Hand edits welcome** | Change `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. |
|
|
192
231
|
| ♻️ **Auto-restart** | Exponential backoff on crash, with the counter reset once a process stays up. |
|
|
193
|
-
| 🩺 **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](./SERVERS.md#when-a-program-restarts-itself) a program that restarts itself. |
|
|
232
|
+
| 🩺 **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. |
|
|
194
233
|
| 🔗 **Ordered startup** | `dependsOn` waits for a dependency to be *healthy* — not merely spawned — and stops in reverse. |
|
|
195
234
|
| 📜 **Logs** | Live per-server stream, buffer plus rotated files on disk, search, download, one click to clear. |
|
|
196
235
|
| 📈 **Resources** | CPU and RSS of the whole process tree, with an optional memory ceiling that triggers a restart. |
|
|
197
236
|
| 🌡️ **Host vitals** | Load, memory, swap, disk and CPU temperature, with thresholds that notify once and again on recovery. |
|
|
198
237
|
| 🤖 **Token API** | Scripts and agents drive it with `Authorization: Bearer` — no browser, no session. [↑](#-agents-scripts-and-tools) |
|
|
199
|
-
| 🔔 **Notifications** | Telegram on crash, unhealthy, forced restart, recovery and host thresholds — [setup here](./NOTIFICATIONS.md). |
|
|
238
|
+
| 🔔 **Notifications** | Telegram on crash, unhealthy, forced restart, recovery and host thresholds — [setup here](./docs/NOTIFICATIONS.md). |
|
|
200
239
|
| 💾 **Backups** | One click for config, secrets, TLS and your declared data directories — plain `.zip`, or AES-256 with a password, restored per path. |
|
|
201
|
-
| 🎨 **BYOU — Bring Your Own UI** | Upload a static build, `home-hosted ui-revert` to go back. [UI_CREATION.md](./UI_CREATION.md) |
|
|
240
|
+
| 🎨 **BYOU — Bring Your Own UI** | Upload a static build, `home-hosted ui-revert` to go back. [UI_CREATION.md](./docs/UI_CREATION.md) |
|
|
202
241
|
| 🔐 **Security** | Cookie sessions, API tokens, scrypt hashes, per-IP lockout, optional TLS, and a refusal to expose itself without a password. |
|
|
203
242
|
| 🧩 **No special treatment** | A server is `command` + `args` + `env` + `cwd`; nothing is built in for any particular app. |
|
|
204
243
|
| 🖥 **Cross-platform** | Linux, macOS and Windows: `/proc`, `ps` or Win32_Process, process groups or `taskkill /T`, no shell dependencies. |
|
|
@@ -222,7 +261,7 @@ An entry is a few lines. Add one with **➕ Add server**, or write it into `serv
|
|
|
222
261
|
|
|
223
262
|
`dataEnvs` declares a data directory once: it is exported to the process *and* picked up by Backups.
|
|
224
263
|
**Every field, every placeholder, the port-conflict policies (including adopting a server that
|
|
225
|
-
restarts itself), and how hand-edits are validated: [SERVERS.md](./SERVERS.md).**
|
|
264
|
+
restarts itself), and how hand-edits are validated: [SERVERS.md](./docs/SERVERS.md).**
|
|
226
265
|
|
|
227
266
|
---
|
|
228
267
|
|
|
@@ -238,11 +277,14 @@ restarts itself), and how hand-edits are validated: [SERVERS.md](./SERVERS.md).*
|
|
|
238
277
|
| `home-hosted set-token` | set the API token scripts and agents use (`--generate`, `--clear`) |
|
|
239
278
|
| `home-hosted migrate` | bring `servers.config.json` up to this release's schema (`--dry-run`, `--yes`) |
|
|
240
279
|
| `home-hosted init` | scaffold a project that keeps `state/` and its data in the repo |
|
|
280
|
+
| `home-hosted ui-switch` | install a UI from a release asset, a zip file or a URL (`--list` to see them) |
|
|
241
281
|
| `home-hosted ui-revert` | go back to the stock panel UI after uploading your own |
|
|
242
282
|
|
|
243
283
|
<details>
|
|
244
284
|
<summary><b>⚙️ Flags</b></summary>
|
|
245
285
|
|
|
286
|
+
`home-hosted <command> --help` prints the options that command takes.
|
|
287
|
+
|
|
246
288
|
```text
|
|
247
289
|
-c, --config <file> servers config (default: <state>/servers.config.json)
|
|
248
290
|
-p, --port <port> control panel port (default: 3999)
|
|
@@ -299,7 +341,7 @@ Everything binds `127.0.0.1` until you say otherwise.
|
|
|
299
341
|
- **Port conflicts** are named — `port 4010 is already in use (pid 4242)` — and can be resolved from
|
|
300
342
|
a confirmation popover on that banner or card. The process is looked up again at that moment,
|
|
301
343
|
never taken from the message, and anything the panel supervises is refused, not killed. A server
|
|
302
|
-
that [restarts itself](./SERVERS.md#when-a-program-restarts-itself) can be followed, or replaced
|
|
344
|
+
that [restarts itself](./docs/SERVERS.md#when-a-program-restarts-itself) can be followed, or replaced
|
|
303
345
|
with a supervised copy.
|
|
304
346
|
- **Secrets never enter the config**: the password hash, the API token hash, the Telegram bot token
|
|
305
347
|
and the TLS key live in `$HHOSTED_HOME/.control-secrets.json` with mode `0600`.
|
|
@@ -313,14 +355,16 @@ Everything binds `127.0.0.1` until you say otherwise.
|
|
|
313
355
|
Telegram, when something happens while you are not looking: a server that gave up restarting, a
|
|
314
356
|
failing health check, a forced restart, a recovery, or a host threshold (disk, memory, swap, load,
|
|
315
357
|
temperature). Opt-in, rate-limited per server *and* reason, and the bot token stays in the secrets
|
|
316
|
-
file. **Two minutes of setup: [NOTIFICATIONS.md](./NOTIFICATIONS.md).**
|
|
358
|
+
file. **Two minutes of setup: [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md).**
|
|
317
359
|
|
|
318
360
|
---
|
|
319
361
|
|
|
320
362
|
## 💾 Backups
|
|
321
363
|
|
|
322
364
|
**Settings → Backups** archives the config, secrets, TLS pair and every data directory your entries
|
|
323
|
-
declare — an ordinary `.zip`, or WinZip AES-256 with a password, restored per path.
|
|
365
|
+
declare — an ordinary `.zip`, or WinZip AES-256 with a password, restored per path. Known build output
|
|
366
|
+
and dependency directories (`node_modules`, `dist`, `.next`, framework caches) are skipped per entry;
|
|
367
|
+
`backupIgnoreGenerated: false` captures them anyway.
|
|
324
368
|
|
|
325
369
|
<details>
|
|
326
370
|
<summary><b>🚚 One archive is a whole setup</b></summary>
|
|
@@ -355,7 +399,7 @@ specific:
|
|
|
355
399
|
|
|
356
400
|
> Help me build a UI for `home-hosted`: nostalgic game theme, including … features.
|
|
357
401
|
|
|
358
|
-
[UI_CREATION.md](./UI_CREATION.md) has the endpoints, the SSE frames, the auth rules and a checklist.
|
|
402
|
+
[UI_CREATION.md](./docs/UI_CREATION.md) has the endpoints, the SSE frames, the auth rules and a checklist.
|
|
359
403
|
|
|
360
404
|
</details>
|
|
361
405
|
|
|
@@ -384,7 +428,7 @@ the same way: every supervised process tree is stopped before the panel exits.
|
|
|
384
428
|
|
|
385
429
|
A supervised server whose port is taken is reported rather than started over — the panel names the
|
|
386
430
|
holder and offers to free it, and a program that restarts itself can be followed or reclaimed instead
|
|
387
|
-
([SERVERS.md](./SERVERS.md#a-busy-port)). The control port itself is checked before the listener is
|
|
431
|
+
([SERVERS.md](./docs/SERVERS.md#a-busy-port)). The control port itself is checked before the listener is
|
|
388
432
|
opened.
|
|
389
433
|
|
|
390
434
|
</details>
|
|
@@ -430,10 +474,13 @@ expose them to an unprivileged process, and adopting a self-restarted process is
|
|
|
430
474
|
|
|
431
475
|
```text
|
|
432
476
|
src/ control plane: config, supervisor, API, providers, services
|
|
433
|
-
src/cli.ts the command line
|
|
477
|
+
src/cli.ts the command line; one file per command under src/cli/
|
|
434
478
|
src/index.ts the control plane itself, used by `up --foreground`
|
|
435
|
-
uis/ UIs: `stock` (shipped) and alternatives — any framework, static output
|
|
479
|
+
uis/ UIs: `stock` (shipped in the package) and alternatives — any framework, static output
|
|
436
480
|
bin/ the published entry point
|
|
481
|
+
docs/ topic docs, UI examples and the README's media
|
|
482
|
+
scripts/ builds, typechecks, media capture, release helpers
|
|
483
|
+
test/ the vitest suite
|
|
437
484
|
```
|
|
438
485
|
|
|
439
486
|
`pnpm dev` runs the panel with `tsx watch` plus the stock UI's dev server (state goes to
|
|
@@ -445,9 +492,9 @@ types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above.
|
|
|
445
492
|
|
|
446
493
|
| file | for |
|
|
447
494
|
| --- | --- |
|
|
448
|
-
| [SERVERS.md](./SERVERS.md) | declaring a server: every field, placeholders, port conflicts |
|
|
449
|
-
| [NOTIFICATIONS.md](./NOTIFICATIONS.md) | Telegram alerts, end to end |
|
|
450
|
-
| [UI_CREATION.md](./UI_CREATION.md) | building a UI against the API |
|
|
495
|
+
| [SERVERS.md](./docs/SERVERS.md) | declaring a server: every field, placeholders, port conflicts |
|
|
496
|
+
| [NOTIFICATIONS.md](./docs/NOTIFICATIONS.md) | Telegram alerts, end to end |
|
|
497
|
+
| [UI_CREATION.md](./docs/UI_CREATION.md) | building a UI against the API |
|
|
451
498
|
| [AGENTS.md](./AGENTS.md) | the architecture and the rules worth knowing before changing anything |
|
|
452
499
|
| [/openapi/ui](http://127.0.0.1:3999/openapi/ui) | the live API, on your own panel |
|
|
453
500
|
|