home-hosted 0.2.0 → 0.3.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 +16 -3
- package/README.md +86 -63
- package/UI_CREATION.md +95 -75
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/package.json +8 -4
- package/uis/stock/dist/assets/{index-DqcdQi-W.js → index-CjmgiKjn.js} +1 -1
- package/uis/stock/dist/index.html +1 -1
package/AGENTS.md
CHANGED
|
@@ -21,11 +21,15 @@ pnpm run quickcheck # eslint + tsc + vue-tsc for every UI under u
|
|
|
21
21
|
pnpm exec vitest run # `pnpm test` is vitest in watch mode
|
|
22
22
|
pnpm run check # quickcheck + vitest run --coverage
|
|
23
23
|
pnpm run set-password # non-interactive through HHOSTED_PASSWORD
|
|
24
|
+
pnpm run media # regenerate docs/media (mockups, both served UIs, tour.gif)
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
Releases are dispatched from `.github/workflows/release.yml` with a version
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
Releases are dispatched from `.github/workflows/release.yml` with a version (and a `dry-run` switch
|
|
28
|
+
that stops before pushing). It verifies the version against `package.json`, lints/types/tests,
|
|
29
|
+
builds the CLI plus the stock UI and every UI zip, lets changelogen write the changelog and tag
|
|
30
|
+
`v<version>`, creates the GitHub release with the UI bundles attached, and publishes to npm through
|
|
31
|
+
trusted publishing (OIDC, no token). npm only offers a trusted publisher for a package that already
|
|
32
|
+
exists, so the first release has to be published by hand.
|
|
29
33
|
|
|
30
34
|
## Architecture (and why)
|
|
31
35
|
|
|
@@ -53,6 +57,11 @@ trusted publishing.
|
|
|
53
57
|
`src/shared`, `@server` → `src` (**types only** — never import runtime server code into a UI).
|
|
54
58
|
- `bin/home-hosted.mjs` — the published bin: `dist/cli.js`, or `src/cli.ts` through tsx when the
|
|
55
59
|
build is missing (a linked checkout).
|
|
60
|
+
- `scripts/` — `build-uis.mjs` (build one UI, optionally zip it), `typecheck-uis.mjs`,
|
|
61
|
+
`capture-media.mjs` (the README's media), `check-release-version.mjs` and `release-notes.mjs`
|
|
62
|
+
(used by the release workflow), `dev.mjs` (`pnpm dev`).
|
|
63
|
+
- `docs/` — `mockups/*.html`, hand-written UI examples in four directions, and `media/*`: their
|
|
64
|
+
screenshots plus the README's `tour.gif`, all regenerated by `pnpm run media`.
|
|
56
65
|
|
|
57
66
|
## Conventions
|
|
58
67
|
|
|
@@ -110,6 +119,10 @@ trusted publishing.
|
|
|
110
119
|
encryption flags are cached and refreshed in the background.
|
|
111
120
|
- `vite.server.config.ts` targets `node22` while `engines` requires >= 24 — deliberate margin, leave it.
|
|
112
121
|
- `pnpm test` watches; CI runs `vitest run`.
|
|
122
|
+
- `pnpm run media` drives Chromium through Playwright, which needs fonts *and* the X/NSS/Mesa
|
|
123
|
+
libraries. In a bare container point `FONTCONFIG_PATH` at a `fonts.conf` covering any TTF and
|
|
124
|
+
`LD_LIBRARY_PATH` at a directory holding those libraries — without fonts, Skia panics instead of
|
|
125
|
+
rendering, and the failure looks like a broken page rather than a missing package.
|
|
113
126
|
|
|
114
127
|
## Where to extend
|
|
115
128
|
|
package/README.md
CHANGED
|
@@ -6,17 +6,29 @@
|
|
|
6
6
|
|
|
7
7
|
<sub>The harness for your servers.</sub>
|
|
8
8
|
|
|
9
|
-
Point it at the things you run — a gateway, a media server, a bot, a database — and it
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
specialized panel that fits you exactly.
|
|
9
|
+
Point it at the things you run — a gateway, a media server, a bot, a database — and it starts
|
|
10
|
+
them, watches them, restarts what dies, and shows you one page of what is going on. Or
|
|
11
|
+
[BYOU](#-bring-your-own-ui-byou), for a specialized UI that fits you exactly.
|
|
13
12
|
|
|
14
13
|
[](https://www.npmjs.com/package/home-hosted)
|
|
15
14
|
[](https://www.npmjs.com/package/home-hosted)
|
|
15
|
+
[](https://github.com/NamesMT/home-hosted/actions/workflows/quickcheck.yml)
|
|
16
16
|
[](./LICENSE)
|
|
17
17
|
[](https://nodejs.org)
|
|
18
18
|
|
|
19
|
-
[🚀 Quick start](#-quick-start) · [✨ Features](#-features) · [🧩 Adding a server](#-adding-a-server) · [🛠 CLI](#-cli) · [🔐 Security](#-security) · [💾 Backups](#-backups)
|
|
19
|
+
[🚀 Quick start](#-quick-start) · [✨ Features](#-features) · [🧩 Adding a server](#-adding-a-server) · [🛠 CLI](#-cli) · [🔐 Security](#-security) · [💾 Backups](#-backups) · [🎨 BYOU](#-bring-your-own-ui-byou)
|
|
20
|
+
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<div align="center">
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
<sub>The stock panel, the [NOC-console](./uis/noc-console) that ships alongside it (for TUI and
|
|
30
|
+
shortcuts wizards), and UI directions you could build yourself —
|
|
31
|
+
<a href="#-bring-your-own-ui-byou">BYOU</a>.</sub>
|
|
20
32
|
|
|
21
33
|
</div>
|
|
22
34
|
|
|
@@ -38,7 +50,7 @@ forget about, or a hand-written systemd unit per service (times six).
|
|
|
38
50
|
```text
|
|
39
51
|
┌──────────────────────────────────────┐
|
|
40
52
|
your browser ──▶│ home-hosted · 127.0.0.1:3999 │
|
|
41
|
-
│
|
|
53
|
+
│ your UI + JSON API + SSE logs │
|
|
42
54
|
└───────────────┬──────────────────────┘
|
|
43
55
|
│ supervises
|
|
44
56
|
┌──────────────────────────┼──────────────────────────┐
|
|
@@ -62,8 +74,8 @@ npx home-hosted # start it — detached, it stays running
|
|
|
62
74
|
npx home-hosted status # where is it, is it healthy
|
|
63
75
|
```
|
|
64
76
|
|
|
65
|
-
That is it. The panel is on **<http://127.0.0.1:3999
|
|
66
|
-
|
|
77
|
+
That is it. The panel is on **<http://127.0.0.1:3999>** and keeps running after the terminal
|
|
78
|
+
closes. <sub>Needs Node 24 or newer.</sub> Stop it whenever you like:
|
|
67
79
|
|
|
68
80
|
```bash
|
|
69
81
|
npx home-hosted down # stops the panel *and* everything it started
|
|
@@ -71,7 +83,7 @@ npx home-hosted down # stops the panel *and* everything it started
|
|
|
71
83
|
|
|
72
84
|
> [!NOTE]
|
|
73
85
|
> The first boot writes a default password (`hh`) so the panel is never unprotected. Change
|
|
74
|
-
> it under **Settings →
|
|
86
|
+
> it under **Settings → Authentication** — binding beyond `127.0.0.1` stays refused until you do.
|
|
75
87
|
|
|
76
88
|
<details>
|
|
77
89
|
<summary><b>📦 Install it instead of npx-ing it</b></summary>
|
|
@@ -79,30 +91,22 @@ npx home-hosted down # stops the panel *and* everything it started
|
|
|
79
91
|
```bash
|
|
80
92
|
npm install -g home-hosted
|
|
81
93
|
home-hosted up
|
|
82
|
-
|
|
83
|
-
# or keep a project with its own servers *and* its own state directory
|
|
84
|
-
cd ~/my-servers
|
|
85
|
-
HHOSTED_HOME=./state npx home-hosted up
|
|
86
94
|
```
|
|
87
95
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
Everything home-hosted owns — config, secrets, logs, TLS, backups — lives in one directory:
|
|
92
|
-
`$HHOSTED_HOME`, defaulting to `~/.home-hosted`. Delete it and nothing of yours is left behind.
|
|
96
|
+
Everything it owns — config, secrets, logs, TLS, backups — lives in `$HHOSTED_HOME`, default
|
|
97
|
+
`~/.home-hosted`. Delete that and nothing of yours is left behind.
|
|
93
98
|
|
|
94
99
|
</details>
|
|
95
100
|
|
|
96
101
|
<details>
|
|
97
102
|
<summary><b>📁 Keep it in a project you can take anywhere (and share)</b></summary>
|
|
98
103
|
|
|
99
|
-
|
|
100
|
-
setup:
|
|
104
|
+
Install the servers next to it and commit the whole project — it *is* the setup:
|
|
101
105
|
|
|
102
106
|
```bash
|
|
103
107
|
mkdir my-servers && cd my-servers
|
|
104
108
|
npm init -y
|
|
105
|
-
npm install home-hosted
|
|
109
|
+
npm install home-hosted 9router serve # the lockfile pins them, so `npm ci` reproduces
|
|
106
110
|
|
|
107
111
|
npx home-hosted up --home ./state # state lives inside the project
|
|
108
112
|
```
|
|
@@ -125,8 +129,8 @@ state/* # secrets, logs, TLS keys and archives stay local…
|
|
|
125
129
|
|
|
126
130
|
<sub>Call them as `npm run up` — `npm up` is npm's own update, not your script.</sub>
|
|
127
131
|
|
|
128
|
-
One clone, `npm ci`, `npm run up`, and the whole setup is up on any machine with Node
|
|
129
|
-
|
|
132
|
+
One clone, `npm ci`, `npm run up`, and the whole setup is up on any machine with Node — or export
|
|
133
|
+
it as one archive under **Settings → Backups**.
|
|
130
134
|
|
|
131
135
|
</details>
|
|
132
136
|
|
|
@@ -165,7 +169,7 @@ WantedBy=multi-user.target
|
|
|
165
169
|
| 🌡️ **Host vitals** | Load, memory, swap, disk and CPU temperature, with thresholds that notify once and again on recovery. |
|
|
166
170
|
| 💾 **Backups** | One click for config, secrets, TLS and your declared data directories — plain `.zip`, or AES-256 with a password, restored per path. |
|
|
167
171
|
| 🚚 **Portable setup** | Restore a shared backup onto a blank instance and the whole server setup is back: definitions, data, secrets and all. |
|
|
168
|
-
| 🎨 **BYOU — Bring Your Own UI** |
|
|
172
|
+
| 🎨 **BYOU — Bring Your Own UI** | Upload a static build, `home-hosted ui-revert` to go back. [UI_CREATION.md](./UI_CREATION.md) |
|
|
169
173
|
| 🔔 **Notifications** | Telegram (grammY) on crash, unhealthy, forced restart, recovery and host thresholds. |
|
|
170
174
|
| 🔐 **Security** | httpOnly cookie sessions, scrypt hashes, per-IP lockout, optional TLS, and a refusal to expose itself without a password. |
|
|
171
175
|
| 🧩 **Server-agnostic** | `command` + `args` + `env` + `cwd`. Nothing in the code knows what you run. |
|
|
@@ -190,13 +194,8 @@ Use **➕ Add server** in the panel, or write it into `servers.config.json`:
|
|
|
190
194
|
}
|
|
191
195
|
```
|
|
192
196
|
|
|
193
|
-
Relative paths resolve against the directory you ran `home-hosted` from, so a project can keep
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
cd ~/my-servers
|
|
198
|
-
HHOSTED_HOME=./state npx home-hosted up
|
|
199
|
-
```
|
|
197
|
+
Relative paths resolve against the directory you ran `home-hosted` from, so a project can keep its
|
|
198
|
+
servers and its state together.
|
|
200
199
|
|
|
201
200
|
<details>
|
|
202
201
|
<summary><b>🧾 Field reference</b></summary>
|
|
@@ -214,9 +213,21 @@ HHOSTED_HOME=./state npx home-hosted up
|
|
|
214
213
|
| `bootstrap` | one command to run once before the first start (migrations, warmups) |
|
|
215
214
|
| `backupPaths` | extra paths this entry owns, included in backups |
|
|
216
215
|
|
|
217
|
-
Placeholders
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
Placeholders, and what each one resolves to:
|
|
217
|
+
|
|
218
|
+
| placeholder | resolves to |
|
|
219
|
+
| --- | --- |
|
|
220
|
+
| `{id}` `{label}` | the entry's id, and its label (falling back to the id) |
|
|
221
|
+
| `{port}` | its configured port, empty when it has none |
|
|
222
|
+
| `{bind}` `{host}` `{displayHost}` | `local`/`lan`/an address; the address it binds (`127.0.0.1`, `0.0.0.0`); the address to *show* (`127.0.0.1`, the LAN IP) |
|
|
223
|
+
| `{lanIp}` | this machine's LAN address, or `127.0.0.1` |
|
|
224
|
+
| `{cwd}` | the entry's working directory (its `cwd`, else the project directory) |
|
|
225
|
+
| `{projectDir}` | where you ran `home-hosted` (or `--project`) — `$HHOSTED_PROJECT` |
|
|
226
|
+
| `{dataRoot}` | the state directory — `$HHOSTED_HOME`, default `~/.home-hosted` |
|
|
227
|
+
| `{home}` | the current user's home directory |
|
|
228
|
+
|
|
229
|
+
Unknown placeholders stay visible instead of silently becoming empty, and `${VAR}` reads from the
|
|
230
|
+
environment (including `envFile`).
|
|
220
231
|
|
|
221
232
|
Every supervised process also gets `HHOSTED_SERVER_ID` and `HHOSTED_CONTROL_PORT`, so a
|
|
222
233
|
service can tell which entry it is and how to reach the panel.
|
|
@@ -282,15 +293,13 @@ directories your entries declare — as an ordinary `.zip`:
|
|
|
282
293
|
|
|
283
294
|
### 🚚 One archive is a whole setup
|
|
284
295
|
|
|
285
|
-
Start a **blank** home-hosted anywhere — another machine, another user, a fresh container —
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
`autostart` are brought up right away. Nothing to re-declare by hand.
|
|
296
|
+
Start a **blank** home-hosted anywhere — another machine, another user, a fresh container — upload
|
|
297
|
+
the archive under **Settings → Backups** and restore. Definitions come back, data lands where *this*
|
|
298
|
+
machine's config says, and `autostart` entries come up immediately.
|
|
289
299
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
not declare: yours, or the one inside the archive you chose to restore.
|
|
300
|
+
It works because an archive carries its own `servers.config.json` and paths are matched by the
|
|
301
|
+
**declaration** (`9router:DATA_DIR`), not by an absolute path from the source machine. A restore
|
|
302
|
+
never writes where no config declares.
|
|
294
303
|
|
|
295
304
|
<details>
|
|
296
305
|
<summary><b>🗂 Declaring data directories</b></summary>
|
|
@@ -309,24 +318,32 @@ A path already covered by a declared parent is skipped.
|
|
|
309
318
|
|
|
310
319
|
---
|
|
311
320
|
|
|
312
|
-
Two UIs live in this repo: **stock** (the panel you get by default) and **noc-console**, an
|
|
313
|
-
operations console over the same API. `pnpm run build:uis` builds and zips every one of them, and
|
|
314
|
-
each release attaches them as `home-hosted-ui-<name>.zip` — install one from Settings → Interface.
|
|
315
|
-
|
|
316
321
|
## 🎨 Bring your own UI (BYOU)
|
|
317
322
|
|
|
318
|
-
The panel is a static site
|
|
319
|
-
|
|
320
|
-
|
|
323
|
+
The panel is a static site: `$HHOSTED_HOME/.ui` overrides the packaged one, and **Settings →
|
|
324
|
+
Interface** takes a zip. No restart, no fork — and `home-hosted ui-revert` brings back the stock
|
|
325
|
+
panel if yours breaks.
|
|
321
326
|
|
|
322
|
-
|
|
323
|
-
home-hosted
|
|
324
|
-
|
|
327
|
+
Two ship in this repo: `uis/stock`, and `uis/noc-console` for TUI and shortcuts wizards; a release
|
|
328
|
+
attaches both as `home-hosted-ui-<name>.zip`. Yours can be anything that compiles to static files —
|
|
329
|
+
the server never cares what built it. `/openapi/ui` documents the API; [UI_CREATION.md](./UI_CREATION.md)
|
|
330
|
+
has the rules and a worked example.
|
|
331
|
+
|
|
332
|
+
<details>
|
|
333
|
+
<summary><b>🤖 Or have an agent build the UI you actually want</b></summary>
|
|
334
|
+
|
|
335
|
+
The whole contract fits in one file, so a coding agent can do this. Point it at this repo and be
|
|
336
|
+
specific about the result:
|
|
325
337
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
338
|
+
> Help me build an UI for `home-hosted`: a nostalgic game theme — servers as a party menu, health as
|
|
339
|
+
> HP bars, logs in a text-box pane, keyboard navigation, and a save-state corner for backups.
|
|
340
|
+
> Follow `UI_CREATION.md`.
|
|
341
|
+
|
|
342
|
+
Zip the build, install it under **Settings → Interface**, and `home-hosted ui-revert` undoes it if
|
|
343
|
+
you change your mind. [`docs/mockups/`](./docs/mockups) holds example directions to borrow from, or
|
|
344
|
+
ignore all of them.
|
|
345
|
+
|
|
346
|
+
</details>
|
|
330
347
|
|
|
331
348
|
---
|
|
332
349
|
|
|
@@ -337,7 +354,8 @@ The panel is a client of its own API, so everything is scriptable:
|
|
|
337
354
|
| | |
|
|
338
355
|
| --- | --- |
|
|
339
356
|
| `GET /api/state` | the full snapshot: config, live status, host vitals |
|
|
340
|
-
| `GET /api/
|
|
357
|
+
| `GET /api/events` | SSE: the live state, plus logs (`?logs=0`, `?serverId=…`) |
|
|
358
|
+
| `GET /api/servers/:id/stream` | SSE: one server's state and logs |
|
|
341
359
|
| `POST /api/servers/:id/{start,stop,restart}` | lifecycle |
|
|
342
360
|
| `PATCH /api/servers/:id`, `PATCH /api/settings` | edit configuration |
|
|
343
361
|
| `GET /api/logs`, `/api/backups`, `/api/notifications` | logs, archives, Telegram |
|
|
@@ -409,20 +427,25 @@ the holder. The control port itself is checked before the listener is opened.
|
|
|
409
427
|
|
|
410
428
|
---
|
|
411
429
|
|
|
412
|
-
## 🗂
|
|
430
|
+
## 🗂 Working on it
|
|
431
|
+
|
|
432
|
+
<details>
|
|
433
|
+
<summary><b>Layout, scripts and conventions</b></summary>
|
|
413
434
|
|
|
414
435
|
```text
|
|
415
436
|
src/ control plane: config, supervisor, API, providers, services
|
|
416
437
|
src/cli.ts the command line (up/down/status/restart/set-password)
|
|
417
438
|
src/index.ts the control plane itself, used by `up --foreground`
|
|
418
|
-
uis/ UIs: `stock` (shipped) and alternatives
|
|
439
|
+
uis/ UIs: `stock` (shipped) and alternatives — any framework, static output
|
|
419
440
|
bin/ the published entry point
|
|
420
|
-
vite.server.config.ts bundles src/cli.ts into dist/cli.js
|
|
421
441
|
```
|
|
422
442
|
|
|
423
|
-
|
|
424
|
-
`.dev-state/`)
|
|
425
|
-
types
|
|
443
|
+
`pnpm dev` runs the panel with `tsx watch` plus the stock UI's dev server (state goes to
|
|
444
|
+
`.dev-state/`); `pnpm build` produces `dist/` and `uis/stock/dist/`; `pnpm quickcheck` is lint plus
|
|
445
|
+
types; `pnpm test` is vitest; `pnpm run media` regenerates the GIF above. [AGENTS.md](./AGENTS.md)
|
|
446
|
+
has the architecture and the rules worth knowing before changing anything.
|
|
447
|
+
|
|
448
|
+
</details>
|
|
426
449
|
|
|
427
450
|
---
|
|
428
451
|
|
package/UI_CREATION.md
CHANGED
|
@@ -1,77 +1,100 @@
|
|
|
1
1
|
# Building a UI for home-hosted
|
|
2
2
|
|
|
3
|
-
The panel's UI is **a static site, and it is replaceable**. The stock one ships in the
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The panel's UI is **a static site, and it is replaceable**. The stock one ships in the package;
|
|
4
|
+
yours is a folder of files the control plane serves instead — same API, same authentication, no
|
|
5
|
+
server changes and no fork.
|
|
6
|
+
|
|
7
|
+
## Ask an AI to build it
|
|
8
|
+
|
|
9
|
+
The whole contract is this file, so a coding agent can do the work. Point it at this repo and be
|
|
10
|
+
specific about what you want:
|
|
11
|
+
|
|
12
|
+
> Help me build a UI for `home-hosted`: a nostalgic game theme. Servers as a party menu, health as
|
|
13
|
+
> HP bars, logs in a text-box pane, keyboard navigation, and a save-state corner for backups.
|
|
14
|
+
> Follow `UI_CREATION.md`.
|
|
15
|
+
|
|
16
|
+
Then zip the build and install it (below). Useful constraints to include in the prompt: the API is
|
|
17
|
+
same-origin (relative `/api/...`), `401 { code: 'AUTH_REQUIRED' }` means "show the login screen",
|
|
18
|
+
live data should come from SSE, and assets must be self-hosted. Existing directions to borrow from
|
|
19
|
+
live in [`docs/mockups/`](./docs/mockups) — or ask for something else entirely; the server does not
|
|
20
|
+
care what your UI looks like.
|
|
21
|
+
|
|
22
|
+
## Install it
|
|
6
23
|
|
|
7
24
|
```text
|
|
8
|
-
$HHOSTED_HOME/.ui/ ← your build lives
|
|
25
|
+
$HHOSTED_HOME/.ui/ ← where your build lives
|
|
9
26
|
index.html ← required, at the root
|
|
10
27
|
assets/…
|
|
11
28
|
ui.json ← optional: { "name": "my-panel", "version": "2.1.0" }
|
|
12
29
|
```
|
|
13
30
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
one wrapper directory (`zip -r ui.zip dist` also works).
|
|
31
|
+
**Settings → Interface** → pick a `.zip` → *Install UI* (refresh to see it). Or drop the files into
|
|
32
|
+
`$HHOSTED_HOME/.ui` yourself. The zip may hold the files at its root or inside one wrapper directory
|
|
33
|
+
(`zip -r ui.zip dist` works too). `ui.json` is optional; it is what the settings page shows as
|
|
34
|
+
installed.
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
Nothing is built on the server side: whatever you upload is served as-is, so ship plain
|
|
37
|
+
HTML/JS/CSS or the output of your own Vite/Next/Astro build with relative asset paths.
|
|
22
38
|
|
|
23
39
|
**If it breaks:** `home-hosted ui-revert` puts the stock panel back (or *Revert to stock* in
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
Settings). The CLI also prints a reminder at startup while a custom UI is active — a broken UI must
|
|
41
|
+
never lock you out of your own machine.
|
|
26
42
|
|
|
27
43
|
## Rules of the road
|
|
28
44
|
|
|
29
|
-
1. **Static only.** No server code, no
|
|
30
|
-
|
|
31
|
-
2. **Same origin, relative paths.** Call `/api
|
|
32
|
-
|
|
33
|
-
3. **`index.html` at the root** (or
|
|
34
|
-
|
|
35
|
-
4. **Keep the login flow.**
|
|
36
|
-
|
|
37
|
-
5. **Self-host your assets.** An offline home server should not need a CDN
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
1. **Static only.** No server code, no filesystem, no environment variables. Everything comes from
|
|
46
|
+
the API.
|
|
47
|
+
2. **Same origin, relative paths.** Call `/api/...`, never an absolute host: the panel may be
|
|
48
|
+
reached over loopback, a LAN address, TLS or a proxy.
|
|
49
|
+
3. **`index.html` at the root** (or in a single wrapper directory). Unknown paths fall back to it,
|
|
50
|
+
so client-side routing and deep links just work.
|
|
51
|
+
4. **Keep the login flow.** Without a session the API answers `401` with `{"code":"AUTH_REQUIRED"}` —
|
|
52
|
+
show your login screen and `POST /api/auth/login`.
|
|
53
|
+
5. **Self-host your assets.** An offline home server should not need a CDN, and neither should its
|
|
54
|
+
panel.
|
|
55
|
+
6. **Limits:** ≤ 20 000 entries, ≤ 512 MB uncompressed, no absolute paths, no `..`, no symlinks, no
|
|
56
|
+
drive letters.
|
|
57
|
+
7. **Offline and plain-http friendly.** Assume a LAN over `http://`: no `Secure`-only cookies, no
|
|
58
|
+
hard-coded port, no https-only APIs.
|
|
40
59
|
|
|
41
60
|
## The API
|
|
42
61
|
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
Both routes below are generated from the same ArkType schemas the server validates with, so they
|
|
63
|
+
cannot drift:
|
|
45
64
|
|
|
46
|
-
| | |
|
|
47
|
-
| --- | --- |
|
|
48
65
|
| approach | how |
|
|
49
66
|
| --- | --- |
|
|
50
|
-
| **OpenAPI** | `GET /openapi/spec.json` (no session needed); browse it at
|
|
51
|
-
| **Typed RPC** |
|
|
67
|
+
| **OpenAPI** | `GET /openapi/spec.json` (no session needed); browse it at `/openapi/ui` |
|
|
68
|
+
| **Typed RPC** | inside this repo: `import type { AppType } from '@server/app'` + `hc<AppType>()`, as `uis/stock/src/lib/rpc.ts` does |
|
|
52
69
|
| **Generated types** | `npx openapi-typescript http://127.0.0.1:3999/openapi/spec.json -o src/api.d.ts` |
|
|
53
70
|
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
`uis/stock/src/lib/api.ts` is the reference client: plain `fetch`, with ArkType validating the
|
|
72
|
+
responses at runtime. Either style is fine.
|
|
56
73
|
|
|
57
|
-
###
|
|
74
|
+
### Endpoints you will actually use
|
|
58
75
|
|
|
59
76
|
| endpoint | what it gives you |
|
|
60
77
|
| --- | --- |
|
|
61
|
-
| `GET /api/state` | everything: panel settings, servers with live status, host vitals
|
|
62
|
-
| `GET /api/events` | **the live feed
|
|
78
|
+
| `GET /api/state` | everything: panel settings, servers with live status, host vitals |
|
|
79
|
+
| `GET /api/events` | **the live feed**: `hello` carries the full state, then `state`, `server`, `log` |
|
|
63
80
|
| `GET /api/servers/:id/stream` | one server's `server` + `log` frames |
|
|
64
81
|
| `POST /api/servers/:id/{start,stop,restart}`, `/api/servers/{start-all,stop-all}` | lifecycle |
|
|
65
|
-
| `GET /
|
|
66
|
-
| `GET /api/logs`,
|
|
67
|
-
| `GET` / `PATCH /api/settings
|
|
68
|
-
| `POST
|
|
69
|
-
| `POST` / `DELETE /api/
|
|
70
|
-
| `GET /
|
|
71
|
-
| `
|
|
72
|
-
|
|
73
|
-
`GET /healthz`
|
|
74
|
-
|
|
82
|
+
| `GET` / `POST /api/servers`, `PATCH` / `DELETE /api/servers/:id` | the entries themselves |
|
|
83
|
+
| `GET /api/logs`, `/api/logs/:id?tail=&search=`, `/api/logs/:id/download?file=` | persisted logs |
|
|
84
|
+
| `GET` / `PATCH /api/settings` | the panel's own config (`control.label`, host thresholds, backups, …) |
|
|
85
|
+
| `POST` / `DELETE /api/settings/ui` | install or revert a UI — what the settings page calls |
|
|
86
|
+
| `POST` / `DELETE /api/settings/tls` | upload or clear a PEM pair |
|
|
87
|
+
| `GET` / `POST /api/backups`, `/api/backups/restore`, `/api/backups/:name/download` | archives |
|
|
88
|
+
| `POST` / `DELETE /api/notifications/token`, `/api/notifications/test`, `/detect-chats` | Telegram |
|
|
89
|
+
| `POST /api/auth/login`, `GET /api/auth/session`, `POST /api/auth/logout` | the session |
|
|
90
|
+
| `GET /healthz` | liveness — **no session**, and `503` when an autostart server has crashed |
|
|
91
|
+
| `GET /api/metrics` | Prometheus text (needs a session) |
|
|
92
|
+
|
|
93
|
+
`GET /healthz` and `GET /openapi/*` are the only unauthenticated reads; the SPA shell itself is
|
|
94
|
+
public, so your app can load before a session exists.
|
|
95
|
+
|
|
96
|
+
Two settings worth reflecting: `control.label` is the panel's own name (the stock shell shows it),
|
|
97
|
+
and `GET /api/settings` includes `ui` — which UI is being served, and its metadata.
|
|
75
98
|
|
|
76
99
|
### Failures
|
|
77
100
|
|
|
@@ -81,40 +104,39 @@ Every failing request answers with one envelope:
|
|
|
81
104
|
{ "message": "unknown server \"web\"", "code": "UNKNOWN_SERVER", "detail": { "…": "…" } }
|
|
82
105
|
```
|
|
83
106
|
|
|
84
|
-
`code` is stable and machine-readable (`AUTH_REQUIRED` drives the login redirect); `detail`
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
large).
|
|
107
|
+
`code` is stable and machine-readable (`AUTH_REQUIRED` drives the login redirect); `detail` carries
|
|
108
|
+
validation issues when there are any. Status codes are the usual ones: 400 bad input, 401 no
|
|
109
|
+
session, 403 bad token or origin, 404 unknown id, 409 conflict, 413 too large.
|
|
88
110
|
|
|
89
111
|
### SSE frames
|
|
90
112
|
|
|
91
113
|
| `event:` | `data:` |
|
|
92
114
|
| --- | --- |
|
|
93
|
-
| `hello` | `{ ts, state }` — the first frame,
|
|
94
|
-
| `state` | `{ ts, state }` — anything changed: a status, a resource sample,
|
|
115
|
+
| `hello` | `{ ts, state }` — the first frame, the complete snapshot |
|
|
116
|
+
| `state` | `{ ts, state }` — anything changed: a status, a resource sample, host vitals |
|
|
95
117
|
| `server` | `{ ts, serverId, server }` — one entry, after an action or a probe |
|
|
96
118
|
| `log` | `{ ts, serverId, lines }` — new output (dropped under backpressure, never state) |
|
|
97
119
|
| `ping` | the current time, every 15 s |
|
|
98
120
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Send `?logs=0` to skip log frames, or `?serverId=<id>` for one server. The server pings every
|
|
103
|
-
15 s.
|
|
121
|
+
Send `?logs=0` to skip log frames, or `?serverId=<id>` to follow one server. The exact shapes are
|
|
122
|
+
`sseMessageSchema` in `src/shared/contracts.ts` — the server validates against it before writing, so
|
|
123
|
+
that schema is also your best type source.
|
|
104
124
|
|
|
105
|
-
##
|
|
125
|
+
## Building one in this repo
|
|
106
126
|
|
|
107
|
-
|
|
108
|
-
`vite.config.ts` calling `createUiConfig` from
|
|
127
|
+
The repo's own UIs are Vite apps: `uis/<name>/` holds `index.html`, `src/`, a `tsconfig.json`
|
|
128
|
+
(copied from a sibling) and a two-line `vite.config.ts` calling `createUiConfig` from
|
|
129
|
+
`uis/vite.shared.ts`, with `public/ui.json` naming it. Yours does not have to be Vite — only the
|
|
130
|
+
output matters.
|
|
109
131
|
|
|
110
132
|
```sh
|
|
111
|
-
node scripts/build-uis.mjs <name> --zip # builds it
|
|
112
|
-
pnpm run build:uis # every UI, zipped;
|
|
133
|
+
node scripts/build-uis.mjs <name> --zip # builds it, writes uis/dist/home-hosted-ui-<name>.zip
|
|
134
|
+
pnpm run build:uis # every UI, zipped; releases attach them as assets
|
|
113
135
|
```
|
|
114
136
|
|
|
115
|
-
Only `stock` ships inside the npm package
|
|
116
|
-
|
|
117
|
-
|
|
137
|
+
Only `stock` ships inside the npm package; the rest are release assets you install from Settings.
|
|
138
|
+
`pnpm run quickcheck` type-checks every UI, and `pnpm test` picks up any `test/*.test.ts` you add
|
|
139
|
+
(use relative imports — the `@` alias points at `stock`).
|
|
118
140
|
|
|
119
141
|
## A worked example
|
|
120
142
|
|
|
@@ -124,25 +146,23 @@ npm create vite@latest my-panel -- --template vue-ts
|
|
|
124
146
|
cd my-panel && npm install
|
|
125
147
|
npm run build # → dist/
|
|
126
148
|
|
|
127
|
-
# 2.
|
|
149
|
+
# 2. keep the API base relative, then zip the build
|
|
128
150
|
cd dist && zip -r ../my-panel.zip . && cd ..
|
|
129
151
|
|
|
130
|
-
# 3. Settings → Interface → Install UI,
|
|
152
|
+
# 3. Settings → Interface → Install UI, then refresh
|
|
131
153
|
```
|
|
132
154
|
|
|
133
|
-
Your client needs the session cookie, which the browser sends automatically once you log in
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
`http://127.0.0.1:3999` the same way.
|
|
155
|
+
Your client needs the session cookie, which the browser sends automatically once you log in on that
|
|
156
|
+
origin. For local development `pnpm dev` runs the panel on 3999 and a Vite dev server on 3998 with
|
|
157
|
+
`/api` proxied — point your own dev server at `http://127.0.0.1:3999` the same way.
|
|
137
158
|
|
|
138
159
|
## Checklist
|
|
139
160
|
|
|
140
161
|
- [ ] `index.html` at the root of the zip, assets referenced relatively
|
|
141
162
|
- [ ] only `/api/...` calls, no absolute origins, no hard-coded port
|
|
142
163
|
- [ ] `401 { code: 'AUTH_REQUIRED' }` handled with a login screen
|
|
143
|
-
- [ ] live data from SSE
|
|
164
|
+
- [ ] live data from SSE — a panel that only polls feels broken
|
|
144
165
|
- [ ] deep links render (the server falls back to `index.html`)
|
|
145
|
-
- [ ] assets self-hosted
|
|
146
|
-
- [ ] works
|
|
147
|
-
- [ ] `ui.json` with a name and version, so
|
|
148
|
-
installed
|
|
166
|
+
- [ ] assets self-hosted, no CDN
|
|
167
|
+
- [ ] works over plain http on a LAN
|
|
168
|
+
- [ ] `ui.json` with a name and version, so Settings can tell you what is installed
|
package/dist/cli.js
CHANGED
|
@@ -1180,7 +1180,7 @@ function checkExposure(control, passwordSet, usingDefaultPassword = false) {
|
|
|
1180
1180
|
};
|
|
1181
1181
|
if (!passwordSet) return {
|
|
1182
1182
|
exposed,
|
|
1183
|
-
blockedReason: `the control panel is bound to ${control.host} but no password is set (run \`pnpm run
|
|
1183
|
+
blockedReason: `the control panel is bound to ${control.host} but no password is set (run \`pnpm run set-password\`)`
|
|
1184
1184
|
};
|
|
1185
1185
|
if (usingDefaultPassword) return {
|
|
1186
1186
|
exposed,
|