home-hosted 0.7.10 → 0.7.12
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 +14 -0
- package/README.md +20 -0
- package/dist/cli.js +219 -47
- package/dist/cli.js.map +1 -1
- package/package.json +4 -2
- package/uis/stock/dist/ui.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -24,6 +24,7 @@ seed entry, or a path that names one.
|
|
|
24
24
|
```sh
|
|
25
25
|
pnpm run up|down|restart|status # detached; `down` asks /_hh/shutdown, signals are the fallback
|
|
26
26
|
pnpm run start # up --foreground (systemd, docker, a foreground shell)
|
|
27
|
+
pnpm exec tsx src/cli.ts logs # the panel's own console output (--lines, --follow, --json)
|
|
27
28
|
pnpm dev # tsx-watch panel :6000 + the stock UI's Vite :6001 (proxies /api), state in .dev-state/
|
|
28
29
|
pnpm dev --ui noc-console # the same, for another UI; --port/--ui-port override
|
|
29
30
|
pnpm run build # dist/cli.js + the stock UI (uis/stock/dist)
|
|
@@ -53,6 +54,19 @@ UI and every UI zip, lets changelogen write the changelog, bump `package.json`,
|
|
|
53
54
|
trusted publishing (OIDC, no token). npm only offers a trusted publisher for a package that already
|
|
54
55
|
exists, so the first release has to be published by hand.
|
|
55
56
|
|
|
57
|
+
Four workflows, and the split is deliberate:
|
|
58
|
+
|
|
59
|
+
| workflow | runs on | what it proves |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| `quickcheck.yml` / `test.yml` | every push and PR | lint, types, the whole suite with coverage — Linux only |
|
|
62
|
+
| `cross-platform.yml` | the release gate, or by hand | the same suite on macOS and Windows |
|
|
63
|
+
| `release.yml` | dispatched with a version | the platform gate, then tag, publish, release assets |
|
|
64
|
+
|
|
65
|
+
The platform gate is not a per-push job on purpose: it is the expensive half, and it is what makes a
|
|
66
|
+
platform-specific break fail before shipping rather than on someone's machine. It also runs on a
|
|
67
|
+
`dry-run`, because that is exactly what a rehearsal needs to exercise. Every job carries a
|
|
68
|
+
`timeout-minutes`, so a wedged runner is reported instead of sitting out GitHub's 6-hour default.
|
|
69
|
+
|
|
56
70
|
### Which version to dispatch
|
|
57
71
|
|
|
58
72
|
Below 1.0 the **minor is the breaking channel**: a fix or a non-breaking feature is a **patch**
|
package/README.md
CHANGED
|
@@ -344,6 +344,7 @@ restarts itself), and how hand-edits are validated: [SERVERS.md](./docs/SERVERS.
|
|
|
344
344
|
| `home-hosted status` | pid, URL, health, uptime, state and log paths (`--json` for scripts) |
|
|
345
345
|
| `home-hosted start <id>` | start one server in the default workspace — and anything it `dependsOn` (`--workspace <id>`) |
|
|
346
346
|
| `home-hosted stop <id>` | stop one server, nothing else (`--workspace <id>`) |
|
|
347
|
+
| `home-hosted logs` | read the panel's own console output — the file `up` redirects it into (`--lines`, `--follow`, `--json`) |
|
|
347
348
|
| `home-hosted set-password` | set the panel password without opening a browser |
|
|
348
349
|
| `home-hosted set-token` | set the API token scripts and agents use (`--generate`, `--clear`) |
|
|
349
350
|
| `home-hosted migrate` | relocate a pre-workspaces state directory and stamp every config for this release (`--dry-run`, `--yes`) |
|
|
@@ -362,6 +363,7 @@ up, restart -c/--config -p/--port --host --open --no-autostart --foregroun
|
|
|
362
363
|
|
|
363
364
|
down (no flags)
|
|
364
365
|
status --json
|
|
366
|
+
logs --lines <n> --follow --json
|
|
365
367
|
start, stop <id> [-w/--workspace <id>] (both need the panel up)
|
|
366
368
|
init --dir --name --pm --no-install -y/--yes
|
|
367
369
|
set-password --clear
|
|
@@ -507,6 +509,24 @@ specific:
|
|
|
507
509
|
|
|
508
510
|
## ❓ FAQ
|
|
509
511
|
|
|
512
|
+
<details>
|
|
513
|
+
<summary><b>The panel is up but something is wrong — where do I look?</b></summary>
|
|
514
|
+
|
|
515
|
+
`home-hosted logs` prints the panel's own console output, which is where it reports what it is
|
|
516
|
+
doing and anything it could not finish:
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
home-hosted logs # the last 50 lines
|
|
520
|
+
home-hosted logs --follow # keep printing, like tail -f
|
|
521
|
+
home-hosted logs --lines all # the whole thing, including the rotation
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
It is the same file `status` names, and reading it needs no session or API token — a panel that
|
|
525
|
+
is answering badly is exactly when you cannot get one. `up` prints the tail automatically when
|
|
526
|
+
the panel fails to start.
|
|
527
|
+
|
|
528
|
+
</details>
|
|
529
|
+
|
|
510
530
|
<details>
|
|
511
531
|
<summary><b>Is it a systemd replacement?</b></summary>
|
|
512
532
|
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { defineCommand, runCommand } from "citty";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import readline from "node:readline";
|
|
7
7
|
import { exec, execFile, spawn, spawnSync } from "node:child_process";
|
|
8
|
+
import { Buffer as Buffer$1 } from "node:buffer";
|
|
8
9
|
import os from "node:os";
|
|
9
10
|
import { describeRoute, openAPIRouteHandler, resolver, validator } from "hono-openapi";
|
|
10
11
|
import { createFactory } from "hono/factory";
|
|
@@ -13,7 +14,6 @@ import { isDevelopment } from "std-env";
|
|
|
13
14
|
import { DetailedError } from "@namesmt/utils";
|
|
14
15
|
import { type } from "arktype";
|
|
15
16
|
import crypto, { X509Certificate, createPrivateKey, createPublicKey, randomBytes } from "node:crypto";
|
|
16
|
-
import { Buffer as Buffer$1 } from "node:buffer";
|
|
17
17
|
import { streamSSE } from "hono/streaming";
|
|
18
18
|
import { autoRetry } from "@grammyjs/auto-retry";
|
|
19
19
|
import { Bot } from "grammy";
|
|
@@ -306,6 +306,112 @@ var init_runtime = __esmMin((() => {
|
|
|
306
306
|
NANNY_COMMAND = "__nanny";
|
|
307
307
|
}));
|
|
308
308
|
//#endregion
|
|
309
|
+
//#region src/helpers/daemon-log.ts
|
|
310
|
+
var daemon_log_exports = /* @__PURE__ */ __exportAll({
|
|
311
|
+
followLog: () => followLog,
|
|
312
|
+
readLog: () => readLog,
|
|
313
|
+
rotateLog: () => rotateLog,
|
|
314
|
+
tailLog: () => tailLog
|
|
315
|
+
});
|
|
316
|
+
function rotateLog(file) {
|
|
317
|
+
try {
|
|
318
|
+
if (fs.statSync(file).size < LOG_ROTATE_BYTES) return;
|
|
319
|
+
fs.rmSync(`${file}.1`, { force: true });
|
|
320
|
+
fs.renameSync(file, `${file}.1`);
|
|
321
|
+
} catch {}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* The last `lines` lines, or `''` when there is no log to read.
|
|
325
|
+
*
|
|
326
|
+
* Split on the trailing newline rather than after it: a log file ends with one, so a plain
|
|
327
|
+
* `split('\n').slice(-lines)` counts that empty last element as a line and quietly returns
|
|
328
|
+
* one fewer than asked for.
|
|
329
|
+
*/
|
|
330
|
+
function tailLog(file, lines = 15) {
|
|
331
|
+
return withoutTrailingBlank(readLogLines(file)).slice(-lines).join("\n");
|
|
332
|
+
}
|
|
333
|
+
function readLogLines(file) {
|
|
334
|
+
try {
|
|
335
|
+
return fs.readFileSync(file, "utf8").split("\n");
|
|
336
|
+
} catch {
|
|
337
|
+
return [];
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/** A file ending in a newline splits into a final empty element; it is not a line. */
|
|
341
|
+
function withoutTrailingBlank(lines) {
|
|
342
|
+
return lines.at(-1) === "" ? lines.slice(0, -1) : lines;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* One rotation boundary's worth of lines, oldest first.
|
|
346
|
+
*
|
|
347
|
+
* The current file plus its `.1` ancestor, so `logs` shows what happened just before a
|
|
348
|
+
* rotation rather than starting blank. `lines <= 0` means the whole thing.
|
|
349
|
+
*/
|
|
350
|
+
function readLog(file, lines) {
|
|
351
|
+
const parts = [];
|
|
352
|
+
for (const candidate of [`${file}.1`, file]) try {
|
|
353
|
+
parts.push(fs.readFileSync(candidate, "utf8"));
|
|
354
|
+
} catch {}
|
|
355
|
+
const all = withoutTrailingBlank(parts.join("").split("\n"));
|
|
356
|
+
return lines > 0 ? all.slice(-lines) : all;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Prints the last `lines` of the log, then keeps printing whatever is appended.
|
|
360
|
+
*
|
|
361
|
+
* Polled rather than watched on purpose. `config-watch` documents why: an editor's save
|
|
362
|
+
* replaces the inode, and `fs.watch` is undependable on some filesystems. A foreground
|
|
363
|
+
* command also has to notice the rotation `up` performs, which shows up simply as the file
|
|
364
|
+
* getting *smaller* — and a poll sees that without any special case.
|
|
365
|
+
*
|
|
366
|
+
* Resolves when the caller's signal arrives, so the command exits the way a pager would.
|
|
367
|
+
*/
|
|
368
|
+
async function followLog(file, lines, write, pollMs = 250) {
|
|
369
|
+
const initial = readLog(file, lines);
|
|
370
|
+
if (initial.length > 0) write(`${initial.join("\n")}\n`);
|
|
371
|
+
let offset = sizeOf(file);
|
|
372
|
+
let stopped = false;
|
|
373
|
+
const stop = () => {
|
|
374
|
+
stopped = true;
|
|
375
|
+
};
|
|
376
|
+
process.once("SIGINT", stop);
|
|
377
|
+
process.once("SIGTERM", stop);
|
|
378
|
+
try {
|
|
379
|
+
for (;;) {
|
|
380
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
381
|
+
if (stopped) break;
|
|
382
|
+
const size = sizeOf(file);
|
|
383
|
+
if (size < offset) offset = 0;
|
|
384
|
+
if (size === offset) continue;
|
|
385
|
+
try {
|
|
386
|
+
const handle = fs.openSync(file, "r");
|
|
387
|
+
try {
|
|
388
|
+
const length = size - offset;
|
|
389
|
+
const buffer = Buffer$1.alloc(length);
|
|
390
|
+
fs.readSync(handle, buffer, 0, length, offset);
|
|
391
|
+
write(buffer.toString("utf8").replace(/\n+$/, "\n"));
|
|
392
|
+
} finally {
|
|
393
|
+
fs.closeSync(handle);
|
|
394
|
+
}
|
|
395
|
+
offset = size;
|
|
396
|
+
} catch {}
|
|
397
|
+
}
|
|
398
|
+
} finally {
|
|
399
|
+
process.removeListener("SIGINT", stop);
|
|
400
|
+
process.removeListener("SIGTERM", stop);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function sizeOf(file) {
|
|
404
|
+
try {
|
|
405
|
+
return fs.statSync(file).size;
|
|
406
|
+
} catch {
|
|
407
|
+
return 0;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
var LOG_ROTATE_BYTES;
|
|
411
|
+
var init_daemon_log = __esmMin((() => {
|
|
412
|
+
LOG_ROTATE_BYTES = 5242880;
|
|
413
|
+
}));
|
|
414
|
+
//#endregion
|
|
309
415
|
//#region src/helpers/version.ts
|
|
310
416
|
var version_exports = /* @__PURE__ */ __exportAll({
|
|
311
417
|
appVersion: () => appVersion,
|
|
@@ -6018,7 +6124,7 @@ function createLogsRoute(deps) {
|
|
|
6018
6124
|
});
|
|
6019
6125
|
}
|
|
6020
6126
|
var MIN_TAIL, MAX_TAIL, DEFAULT_TAIL, idParam$2, downloadQuery, historyQuery;
|
|
6021
|
-
var init_logs = __esmMin((() => {
|
|
6127
|
+
var init_logs$1 = __esmMin((() => {
|
|
6022
6128
|
init_factory();
|
|
6023
6129
|
init_openapi$1();
|
|
6024
6130
|
init_validator();
|
|
@@ -8372,20 +8478,7 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
8372
8478
|
return;
|
|
8373
8479
|
}
|
|
8374
8480
|
await this.assertPortsFree(this.config.httpPort, this.config.httpsPort);
|
|
8375
|
-
|
|
8376
|
-
this.lastError = `the engine did not accept the configuration: ${error instanceof Error ? error.message : String(error)}`;
|
|
8377
|
-
throw new DetailedError(this.lastError, {
|
|
8378
|
-
statusCode: 502,
|
|
8379
|
-
code: "ENGINE_UNREACHABLE"
|
|
8380
|
-
});
|
|
8381
|
-
});
|
|
8382
|
-
if (result.status >= 400) {
|
|
8383
|
-
this.lastError = engineMessage(result.body) ?? `the engine rejected the configuration (HTTP ${result.status})`;
|
|
8384
|
-
throw new DetailedError(this.lastError, {
|
|
8385
|
-
statusCode: 400,
|
|
8386
|
-
code: "PROXY_CONFIG_REJECTED"
|
|
8387
|
-
});
|
|
8388
|
-
}
|
|
8481
|
+
await this.postConfiguration(rendered);
|
|
8389
8482
|
this.rememberConfig(text);
|
|
8390
8483
|
this.rememberApplied(this.config.httpPort, this.config.httpsPort);
|
|
8391
8484
|
const swept = this.sweepUnroutedCertificates();
|
|
@@ -8431,22 +8524,34 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
8431
8524
|
}
|
|
8432
8525
|
return removed;
|
|
8433
8526
|
}
|
|
8434
|
-
/**
|
|
8435
|
-
|
|
8527
|
+
/**
|
|
8528
|
+
* Posts one rendered configuration to the engine's admin endpoint and reports a refusal.
|
|
8529
|
+
*
|
|
8530
|
+
* The three call sites used to spell this out themselves, and one of them forgot to record
|
|
8531
|
+
* `lastError` — which is what `status()` shows as the proxy's state, so a configuration the
|
|
8532
|
+
* engine refused could leave the panel reporting the proxy as healthy. Keeping both the
|
|
8533
|
+
* message and the state in one place is the point.
|
|
8534
|
+
*/
|
|
8535
|
+
async postConfiguration(rendered) {
|
|
8436
8536
|
const result = await this.request("POST", "/load", rendered).catch((error) => {
|
|
8437
|
-
|
|
8537
|
+
this.lastError = `the engine did not accept the configuration: ${error instanceof Error ? error.message : String(error)}`;
|
|
8538
|
+
throw new DetailedError(this.lastError, {
|
|
8438
8539
|
statusCode: 502,
|
|
8439
8540
|
code: "ENGINE_UNREACHABLE"
|
|
8440
8541
|
});
|
|
8441
8542
|
});
|
|
8442
8543
|
if (result.status >= 400) {
|
|
8443
|
-
|
|
8444
|
-
throw new DetailedError(
|
|
8544
|
+
this.lastError = engineMessage(result.body) ?? `the engine rejected the configuration (HTTP ${result.status})`;
|
|
8545
|
+
throw new DetailedError(this.lastError, {
|
|
8445
8546
|
statusCode: 400,
|
|
8446
8547
|
code: "PROXY_CONFIG_REJECTED"
|
|
8447
8548
|
});
|
|
8448
8549
|
}
|
|
8449
8550
|
}
|
|
8551
|
+
/** Hands a configuration to the running engine without touching the file on disk. */
|
|
8552
|
+
async loadIntoEngine(rendered) {
|
|
8553
|
+
await this.postConfiguration(rendered);
|
|
8554
|
+
}
|
|
8450
8555
|
/** Advances `previous.json` only for a configuration the engine accepted. */
|
|
8451
8556
|
rememberConfig(text) {
|
|
8452
8557
|
if (fs.existsSync(this.options.configPath)) writeFileAtomic(this.options.previousConfigPath, fs.readFileSync(this.options.configPath, "utf8"), { mode: 384 });
|
|
@@ -8511,14 +8616,7 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
8511
8616
|
});
|
|
8512
8617
|
const current = fs.existsSync(this.options.configPath) ? fs.readFileSync(this.options.configPath, "utf8") : null;
|
|
8513
8618
|
const previous = fs.readFileSync(this.options.previousConfigPath, "utf8");
|
|
8514
|
-
|
|
8515
|
-
if (result.status >= 400) {
|
|
8516
|
-
this.lastError = engineMessage(result.body) ?? `the engine rejected the configuration (HTTP ${result.status})`;
|
|
8517
|
-
throw new DetailedError(this.lastError, {
|
|
8518
|
-
statusCode: 400,
|
|
8519
|
-
code: "PROXY_CONFIG_REJECTED"
|
|
8520
|
-
});
|
|
8521
|
-
}
|
|
8619
|
+
await this.postConfiguration(JSON.parse(previous));
|
|
8522
8620
|
writeFileAtomic(this.options.configPath, previous, { mode: 384 });
|
|
8523
8621
|
if (current === null) fs.rmSync(this.options.previousConfigPath, { force: true });
|
|
8524
8622
|
else writeFileAtomic(this.options.previousConfigPath, current, { mode: 384 });
|
|
@@ -10314,7 +10412,7 @@ var init_app = __esmMin((() => {
|
|
|
10314
10412
|
init_ddns$1();
|
|
10315
10413
|
init_events();
|
|
10316
10414
|
init_health();
|
|
10317
|
-
init_logs();
|
|
10415
|
+
init_logs$1();
|
|
10318
10416
|
init_metrics();
|
|
10319
10417
|
init_notifications$1();
|
|
10320
10418
|
init_proxy();
|
|
@@ -14991,6 +15089,7 @@ function stateSignature(state) {
|
|
|
14991
15089
|
state.host.alerts.join(","),
|
|
14992
15090
|
state.backups.files.length,
|
|
14993
15091
|
state.backups.entries.length,
|
|
15092
|
+
`${state.ui.custom}:${state.ui.meta?.name ?? ""}:${state.ui.meta?.version ?? ""}`,
|
|
14994
15093
|
state.proxy === void 0 ? "" : [
|
|
14995
15094
|
state.proxy.config.enabled,
|
|
14996
15095
|
state.proxy.status.state,
|
|
@@ -16446,21 +16545,6 @@ function toUpFlags(args) {
|
|
|
16446
16545
|
printConfig: args.printConfig === true
|
|
16447
16546
|
};
|
|
16448
16547
|
}
|
|
16449
|
-
/** One rotation is enough for a console log. */
|
|
16450
|
-
function rotateLog(file) {
|
|
16451
|
-
try {
|
|
16452
|
-
if (fs.statSync(file).size < LOG_ROTATE_BYTES) return;
|
|
16453
|
-
fs.rmSync(`${file}.1`, { force: true });
|
|
16454
|
-
fs.renameSync(file, `${file}.1`);
|
|
16455
|
-
} catch {}
|
|
16456
|
-
}
|
|
16457
|
-
function tailLog(file, lines = 15) {
|
|
16458
|
-
try {
|
|
16459
|
-
return fs.readFileSync(file, "utf8").split("\n").slice(-lines).join("\n").trimEnd();
|
|
16460
|
-
} catch {
|
|
16461
|
-
return "";
|
|
16462
|
-
}
|
|
16463
|
-
}
|
|
16464
16548
|
async function runUp(flags, entry) {
|
|
16465
16549
|
const { runControlPlane } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
16466
16550
|
if (flags.foreground || flags.printConfig) {
|
|
@@ -16545,14 +16629,14 @@ function upCommand(entry) {
|
|
|
16545
16629
|
}
|
|
16546
16630
|
});
|
|
16547
16631
|
}
|
|
16548
|
-
var DEFAULT_PORT$1,
|
|
16632
|
+
var DEFAULT_PORT$1, upArgs;
|
|
16549
16633
|
var init_up = __esmMin((() => {
|
|
16550
16634
|
init_args();
|
|
16551
16635
|
init_io();
|
|
16636
|
+
init_daemon_log();
|
|
16552
16637
|
init_runtime();
|
|
16553
16638
|
init_version();
|
|
16554
16639
|
DEFAULT_PORT$1 = 3999;
|
|
16555
|
-
LOG_ROTATE_BYTES = 5242880;
|
|
16556
16640
|
upArgs = {
|
|
16557
16641
|
config: {
|
|
16558
16642
|
type: "string",
|
|
@@ -16798,6 +16882,82 @@ var init_status = __esmMin((() => {
|
|
|
16798
16882
|
});
|
|
16799
16883
|
}));
|
|
16800
16884
|
//#endregion
|
|
16885
|
+
//#region src/cli/logs.ts
|
|
16886
|
+
var logs_exports = /* @__PURE__ */ __exportAll({
|
|
16887
|
+
logsArgs: () => logsArgs,
|
|
16888
|
+
logsCommand: () => logsCommand,
|
|
16889
|
+
runLogs: () => runLogs
|
|
16890
|
+
});
|
|
16891
|
+
/** `all` or a negative count means the whole log; a bad number is not worth a hard failure. */
|
|
16892
|
+
function parseLines(raw) {
|
|
16893
|
+
if (raw === void 0) return DEFAULT_LINES;
|
|
16894
|
+
if (raw.trim().toLowerCase() === "all") return 0;
|
|
16895
|
+
const parsed = Number.parseInt(raw, 10);
|
|
16896
|
+
if (!Number.isFinite(parsed)) return DEFAULT_LINES;
|
|
16897
|
+
return parsed < 0 ? 0 : parsed;
|
|
16898
|
+
}
|
|
16899
|
+
async function runLogs(input) {
|
|
16900
|
+
const { daemonLogPath } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
16901
|
+
const { readLog } = await Promise.resolve().then(() => (init_daemon_log(), daemon_log_exports));
|
|
16902
|
+
const lines = parseLines(input.lines);
|
|
16903
|
+
if (input.follow === true) {
|
|
16904
|
+
const { followLog } = await Promise.resolve().then(() => (init_daemon_log(), daemon_log_exports));
|
|
16905
|
+
await followLog(daemonLogPath, lines === 0 ? DEFAULT_LINES : lines, (chunk) => process.stdout.write(chunk));
|
|
16906
|
+
return;
|
|
16907
|
+
}
|
|
16908
|
+
const output = readLog(daemonLogPath, lines);
|
|
16909
|
+
if (input.json === true) {
|
|
16910
|
+
process.stdout.write(`${JSON.stringify({
|
|
16911
|
+
path: daemonLogPath,
|
|
16912
|
+
lines: output
|
|
16913
|
+
}, null, 2)}\n`);
|
|
16914
|
+
return;
|
|
16915
|
+
}
|
|
16916
|
+
if (output.length === 0) {
|
|
16917
|
+
process.stdout.write(`${dim(`no output yet — ${daemonLogPath}`)}\n`);
|
|
16918
|
+
return;
|
|
16919
|
+
}
|
|
16920
|
+
process.stdout.write(`${bold(daemonLogPath)}\n`);
|
|
16921
|
+
for (const line of output) process.stdout.write(`${line}\n`);
|
|
16922
|
+
}
|
|
16923
|
+
var logsArgs, DEFAULT_LINES, logsCommand;
|
|
16924
|
+
var init_logs = __esmMin((() => {
|
|
16925
|
+
init_io();
|
|
16926
|
+
logsArgs = {
|
|
16927
|
+
lines: {
|
|
16928
|
+
type: "string",
|
|
16929
|
+
description: "how many lines to show (default 50, `all` for everything)"
|
|
16930
|
+
},
|
|
16931
|
+
follow: {
|
|
16932
|
+
type: "boolean",
|
|
16933
|
+
description: "keep printing as the panel writes (like tail -f)"
|
|
16934
|
+
},
|
|
16935
|
+
json: {
|
|
16936
|
+
type: "boolean",
|
|
16937
|
+
description: "print machine-readable JSON"
|
|
16938
|
+
}
|
|
16939
|
+
};
|
|
16940
|
+
DEFAULT_LINES = 50;
|
|
16941
|
+
logsCommand = defineCommand({
|
|
16942
|
+
meta: {
|
|
16943
|
+
name: "logs",
|
|
16944
|
+
description: "show the panel's own console output"
|
|
16945
|
+
},
|
|
16946
|
+
args: logsArgs,
|
|
16947
|
+
async run({ args }) {
|
|
16948
|
+
try {
|
|
16949
|
+
await runLogs({
|
|
16950
|
+
lines: args.lines,
|
|
16951
|
+
follow: args.follow,
|
|
16952
|
+
json: args.json
|
|
16953
|
+
});
|
|
16954
|
+
} catch (error) {
|
|
16955
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
16956
|
+
}
|
|
16957
|
+
}
|
|
16958
|
+
});
|
|
16959
|
+
}));
|
|
16960
|
+
//#endregion
|
|
16801
16961
|
//#region src/cli/server.ts
|
|
16802
16962
|
var server_exports = /* @__PURE__ */ __exportAll({
|
|
16803
16963
|
runServerAction: () => runServerAction,
|
|
@@ -17973,6 +18133,7 @@ var SYNOPSIS = {
|
|
|
17973
18133
|
"down": "home-hosted down",
|
|
17974
18134
|
"restart": "home-hosted restart [options]",
|
|
17975
18135
|
"status": "home-hosted status [--json]",
|
|
18136
|
+
"logs": "home-hosted logs [--lines <n>] [--follow] [--json]",
|
|
17976
18137
|
"start": "home-hosted start <id> [--workspace <id>]",
|
|
17977
18138
|
"stop": "home-hosted stop <id> [--workspace <id>]",
|
|
17978
18139
|
"set-password": "home-hosted set-password",
|
|
@@ -17988,6 +18149,7 @@ var SUMMARIES = {
|
|
|
17988
18149
|
"down": "stop it, and everything it supervises",
|
|
17989
18150
|
"restart": "down, then up",
|
|
17990
18151
|
"status": "is it running, where, and how to reach it",
|
|
18152
|
+
"logs": "show the panel's own console output",
|
|
17991
18153
|
"start": "start one server, leaving the panel up",
|
|
17992
18154
|
"stop": "stop one server, leaving the panel up",
|
|
17993
18155
|
"set-password": "set the panel password without the API",
|
|
@@ -18016,6 +18178,14 @@ var STATUS_SECTION = {
|
|
|
18016
18178
|
heading: "Options for status",
|
|
18017
18179
|
lines: [["--json", "print machine-readable JSON"]]
|
|
18018
18180
|
};
|
|
18181
|
+
var LOGS_SECTION = {
|
|
18182
|
+
heading: "Options for logs",
|
|
18183
|
+
lines: [
|
|
18184
|
+
["--lines <n>", "how many lines to show (default 50, `all` for everything)"],
|
|
18185
|
+
["--follow", "keep printing as the panel writes (like tail -f)"],
|
|
18186
|
+
["--json", "print machine-readable JSON"]
|
|
18187
|
+
]
|
|
18188
|
+
};
|
|
18019
18189
|
var SERVER_SECTION = {
|
|
18020
18190
|
heading: "Arguments for start/stop",
|
|
18021
18191
|
lines: [["<id>", "the server id from servers.config.json"]]
|
|
@@ -18138,6 +18308,7 @@ var USAGE = [
|
|
|
18138
18308
|
var UP_COMMANDS = /* @__PURE__ */ new Set(["up", "restart"]);
|
|
18139
18309
|
var SECTIONS = {
|
|
18140
18310
|
"status": STATUS_SECTION,
|
|
18311
|
+
"logs": LOGS_SECTION,
|
|
18141
18312
|
"start": SERVER_SECTION,
|
|
18142
18313
|
"stop": SERVER_SECTION,
|
|
18143
18314
|
"set-password": SET_PASSWORD_SECTION,
|
|
@@ -18186,6 +18357,7 @@ var COMMANDS = {
|
|
|
18186
18357
|
"down": () => Promise.resolve().then(() => (init_down(), down_exports)).then((module) => module.downCommand),
|
|
18187
18358
|
"restart": () => Promise.resolve().then(() => (init_restart(), restart_exports)).then((module) => module.restartCommand(CLI_ENTRY)),
|
|
18188
18359
|
"status": () => Promise.resolve().then(() => (init_status(), status_exports)).then((module) => module.statusCommand),
|
|
18360
|
+
"logs": () => Promise.resolve().then(() => (init_logs(), logs_exports)).then((module) => module.logsCommand),
|
|
18189
18361
|
"start": () => Promise.resolve().then(() => (init_server(), server_exports)).then((module) => module.startCommand),
|
|
18190
18362
|
"stop": () => Promise.resolve().then(() => (init_server(), server_exports)).then((module) => module.stopCommand),
|
|
18191
18363
|
"set-password": () => Promise.resolve().then(() => (init_set_password(), set_password_exports)).then((module) => module.setPasswordCommand),
|