@tridha643/hestia 1.1.0 → 1.2.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/dist/cli.js +1186 -521
- package/dist/cli.js.map +22 -15
- package/dist/daemon.js +118 -25
- package/dist/daemon.js.map +6 -5
- package/package.json +1 -1
- package/skills/hestia/SKILL.md +11 -10
package/dist/daemon.js
CHANGED
|
@@ -6947,7 +6947,7 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6947
6947
|
// packages/engine/src/daemon/main.ts
|
|
6948
6948
|
import { chmodSync as chmodSync6, rmSync as rmSync11 } from "fs";
|
|
6949
6949
|
import { randomBytes } from "crypto";
|
|
6950
|
-
import { join as
|
|
6950
|
+
import { join as join25 } from "path";
|
|
6951
6951
|
|
|
6952
6952
|
// packages/session-broker-core/src/limits.ts
|
|
6953
6953
|
var MAX_HTTP_BODY_BYTES = 4 * 1024 * 1024;
|
|
@@ -8221,7 +8221,7 @@ function clearState(worktreeRoot, project) {
|
|
|
8221
8221
|
import { existsSync as existsSync21, rmSync as rmSync10 } from "fs";
|
|
8222
8222
|
import { execFile as execFile10 } from "child_process";
|
|
8223
8223
|
import { promisify as promisify9 } from "util";
|
|
8224
|
-
import { join as
|
|
8224
|
+
import { join as join24, resolve as resolve2 } from "path";
|
|
8225
8225
|
import { createHash as createHash7 } from "crypto";
|
|
8226
8226
|
import { resolve4, resolve6, resolveCname } from "dns/promises";
|
|
8227
8227
|
|
|
@@ -11579,7 +11579,86 @@ import {
|
|
|
11579
11579
|
readFileSync as readFileSync17,
|
|
11580
11580
|
readdirSync as readdirSync7
|
|
11581
11581
|
} from "fs";
|
|
11582
|
+
import { join as join22 } from "path";
|
|
11583
|
+
|
|
11584
|
+
// packages/engine/src/tunnel/orphans.ts
|
|
11585
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
11582
11586
|
import { join as join21 } from "path";
|
|
11587
|
+
function hestiaTunnelMarker(uuid) {
|
|
11588
|
+
return join21(hestiaHome(), "tunnel", uuid);
|
|
11589
|
+
}
|
|
11590
|
+
function parseLocalHestiaConnectors(psOutput, marker) {
|
|
11591
|
+
const rows = [];
|
|
11592
|
+
for (const line of psOutput.split(`
|
|
11593
|
+
`)) {
|
|
11594
|
+
const trimmed = line.trim();
|
|
11595
|
+
if (trimmed === "")
|
|
11596
|
+
continue;
|
|
11597
|
+
const m = trimmed.match(/^(\d+)\s+(\d+)\s+(.*)$/);
|
|
11598
|
+
if (m === null)
|
|
11599
|
+
continue;
|
|
11600
|
+
const command = m[3];
|
|
11601
|
+
if (!command.includes(marker))
|
|
11602
|
+
continue;
|
|
11603
|
+
if (!/(^|[\/\s])cloudflared(\s|$)/.test(command))
|
|
11604
|
+
continue;
|
|
11605
|
+
rows.push({ pid: Number(m[1]), pgid: Number(m[2]), command });
|
|
11606
|
+
}
|
|
11607
|
+
return rows;
|
|
11608
|
+
}
|
|
11609
|
+
function listLocalHestiaConnectors(uuid) {
|
|
11610
|
+
let out;
|
|
11611
|
+
try {
|
|
11612
|
+
out = execFileSync4("ps", ["-Ao", "pid=,pgid=,command="], {
|
|
11613
|
+
encoding: "utf8",
|
|
11614
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
11615
|
+
env: { ...process.env, LC_ALL: "C", LANG: "C" }
|
|
11616
|
+
});
|
|
11617
|
+
} catch {
|
|
11618
|
+
return [];
|
|
11619
|
+
}
|
|
11620
|
+
return parseLocalHestiaConnectors(out, hestiaTunnelMarker(uuid));
|
|
11621
|
+
}
|
|
11622
|
+
var REAP_GRACE_MS = 5000;
|
|
11623
|
+
var REAP_POLL_MS = 100;
|
|
11624
|
+
async function reapOrphanConnectors(uuid, keep) {
|
|
11625
|
+
const procs = listLocalHestiaConnectors(uuid).filter((p) => {
|
|
11626
|
+
if (keep === undefined)
|
|
11627
|
+
return true;
|
|
11628
|
+
return p.pgid !== keep.pgid && p.pid !== keep.pid;
|
|
11629
|
+
});
|
|
11630
|
+
if (procs.length === 0)
|
|
11631
|
+
return { reapedGroups: 0, pids: [] };
|
|
11632
|
+
const groups = new Set(procs.map((p) => p.pgid));
|
|
11633
|
+
const pids = procs.map((p) => p.pid);
|
|
11634
|
+
for (const pgid of groups) {
|
|
11635
|
+
try {
|
|
11636
|
+
process.kill(-pgid, "SIGTERM");
|
|
11637
|
+
} catch {}
|
|
11638
|
+
}
|
|
11639
|
+
const deadline = Date.now() + REAP_GRACE_MS;
|
|
11640
|
+
while (Date.now() < deadline) {
|
|
11641
|
+
const still = listLocalHestiaConnectors(uuid).filter((p) => {
|
|
11642
|
+
if (keep === undefined)
|
|
11643
|
+
return true;
|
|
11644
|
+
return p.pgid !== keep.pgid && p.pid !== keep.pid;
|
|
11645
|
+
});
|
|
11646
|
+
if (still.length === 0)
|
|
11647
|
+
return { reapedGroups: groups.size, pids };
|
|
11648
|
+
await new Promise((r) => setTimeout(r, REAP_POLL_MS));
|
|
11649
|
+
}
|
|
11650
|
+
for (const p of listLocalHestiaConnectors(uuid)) {
|
|
11651
|
+
if (keep !== undefined && (p.pgid === keep.pgid || p.pid === keep.pid))
|
|
11652
|
+
continue;
|
|
11653
|
+
try {
|
|
11654
|
+
process.kill(-p.pgid, "SIGKILL");
|
|
11655
|
+
} catch {}
|
|
11656
|
+
try {
|
|
11657
|
+
process.kill(p.pid, "SIGKILL");
|
|
11658
|
+
} catch {}
|
|
11659
|
+
}
|
|
11660
|
+
return { reapedGroups: groups.size, pids };
|
|
11661
|
+
}
|
|
11583
11662
|
|
|
11584
11663
|
// packages/engine/src/tunnel/verify.ts
|
|
11585
11664
|
var POLL_MS4 = 300;
|
|
@@ -11627,13 +11706,13 @@ async function quickTunnelUrl(metricsPort, timeoutMs) {
|
|
|
11627
11706
|
var CONNECTOR = "connector";
|
|
11628
11707
|
var READY_TIMEOUT_MS2 = 30000;
|
|
11629
11708
|
function tunnelDir(uuid) {
|
|
11630
|
-
return
|
|
11709
|
+
return join22(hestiaHome(), "tunnel", uuid);
|
|
11631
11710
|
}
|
|
11632
11711
|
function configPath(uuid) {
|
|
11633
|
-
return
|
|
11712
|
+
return join22(tunnelDir(uuid), "config.yml");
|
|
11634
11713
|
}
|
|
11635
11714
|
function adoptedMarker(uuid) {
|
|
11636
|
-
return
|
|
11715
|
+
return join22(tunnelDir(uuid), "adopted.json");
|
|
11637
11716
|
}
|
|
11638
11717
|
function isAdopted(uuid) {
|
|
11639
11718
|
return existsSync19(adoptedMarker(uuid));
|
|
@@ -11652,10 +11731,10 @@ function readAdopted(uuid) {
|
|
|
11652
11731
|
return { uuid, name: marker.name, credFile: marker.credFile, reconstructed: false };
|
|
11653
11732
|
}
|
|
11654
11733
|
let name;
|
|
11655
|
-
const stacksDir =
|
|
11734
|
+
const stacksDir = join22(hestiaHome(), "stacks");
|
|
11656
11735
|
if (existsSync19(stacksDir)) {
|
|
11657
11736
|
for (const project of readdirSync7(stacksDir)) {
|
|
11658
|
-
const sp =
|
|
11737
|
+
const sp = join22(stacksDir, project, "stack.json");
|
|
11659
11738
|
if (!existsSync19(sp))
|
|
11660
11739
|
continue;
|
|
11661
11740
|
try {
|
|
@@ -11675,19 +11754,19 @@ function readAdopted(uuid) {
|
|
|
11675
11754
|
};
|
|
11676
11755
|
}
|
|
11677
11756
|
function listAdopted() {
|
|
11678
|
-
const root =
|
|
11757
|
+
const root = join22(hestiaHome(), "tunnel");
|
|
11679
11758
|
if (!existsSync19(root))
|
|
11680
11759
|
return [];
|
|
11681
11760
|
return readdirSync7(root).filter((uuid) => isAdopted(uuid));
|
|
11682
11761
|
}
|
|
11683
11762
|
function collectDynamicRules(uuid) {
|
|
11684
|
-
const stacksDir =
|
|
11763
|
+
const stacksDir = join22(hestiaHome(), "stacks");
|
|
11685
11764
|
const rules = [];
|
|
11686
11765
|
const dropped = [];
|
|
11687
11766
|
if (!existsSync19(stacksDir))
|
|
11688
11767
|
return { rules, dropped };
|
|
11689
11768
|
for (const project of readdirSync7(stacksDir)) {
|
|
11690
|
-
const p =
|
|
11769
|
+
const p = join22(stacksDir, project, "stack.json");
|
|
11691
11770
|
if (!existsSync19(p))
|
|
11692
11771
|
continue;
|
|
11693
11772
|
let record;
|
|
@@ -11732,6 +11811,10 @@ async function reconcileTunnel(ref, opts) {
|
|
|
11732
11811
|
});
|
|
11733
11812
|
const pf = readPidfile(dir, CONNECTOR);
|
|
11734
11813
|
const live = pf !== null && isLive(pf);
|
|
11814
|
+
const { reapedGroups } = await reapOrphanConnectors(ref.uuid, live ? pf : undefined);
|
|
11815
|
+
if (reapedGroups > 0) {
|
|
11816
|
+
warnings.push(`reaped ${reapedGroups} orphan hestia connector process group(s) for this tunnel`);
|
|
11817
|
+
}
|
|
11735
11818
|
const currentConfig = existsSync19(cfgPath) ? readFileSync17(cfgPath, "utf8") : null;
|
|
11736
11819
|
if (live && currentConfig === nextConfig) {
|
|
11737
11820
|
return { restarted: false, metricsPort: pf.port, ready: true, warnings };
|
|
@@ -12439,7 +12522,7 @@ class SharedArbiter {
|
|
|
12439
12522
|
import { execFile as execFile9 } from "child_process";
|
|
12440
12523
|
import { promisify as promisify8 } from "util";
|
|
12441
12524
|
import { existsSync as existsSync20, readFileSync as readFileSync18, readdirSync as readdirSync8 } from "fs";
|
|
12442
|
-
import { join as
|
|
12525
|
+
import { join as join23 } from "path";
|
|
12443
12526
|
var pexec8 = promisify8(execFile9);
|
|
12444
12527
|
var DEFAULT_REFRESH_MS = 1000;
|
|
12445
12528
|
var DEFAULT_HEARTBEAT_MS = 15000;
|
|
@@ -12486,11 +12569,11 @@ class LatestFleetChannel {
|
|
|
12486
12569
|
function readManagedMirrors() {
|
|
12487
12570
|
const records = [];
|
|
12488
12571
|
const warnings = [];
|
|
12489
|
-
const stacksDirectory =
|
|
12572
|
+
const stacksDirectory = join23(hestiaHome(), "stacks");
|
|
12490
12573
|
if (!existsSync20(stacksDirectory))
|
|
12491
12574
|
return { records, warnings };
|
|
12492
12575
|
for (const project of readdirSync8(stacksDirectory).sort()) {
|
|
12493
|
-
const path =
|
|
12576
|
+
const path = join23(stacksDirectory, project, "stack.json");
|
|
12494
12577
|
try {
|
|
12495
12578
|
const source = readFileSync18(path);
|
|
12496
12579
|
if (source.byteLength > MAX_MIRROR_BYTES2) {
|
|
@@ -12968,7 +13051,7 @@ async function assertHestiaStateIgnored(worktreeRoot) {
|
|
|
12968
13051
|
try {
|
|
12969
13052
|
await pexec9("git", ["-C", worktreeRoot, "check-ignore", "-q", ".hestia/"], { timeout: 5000 });
|
|
12970
13053
|
} catch {
|
|
12971
|
-
throw new HestiaError("state-not-ignored", `.hestia is not ignored in ${worktreeRoot}; add the line ".hestia/" to ${
|
|
13054
|
+
throw new HestiaError("state-not-ignored", `.hestia is not ignored in ${worktreeRoot}; add the line ".hestia/" to ${join24(worktreeRoot, ".gitignore")}`, { remedy: `.hestia/`, path: join24(worktreeRoot, ".gitignore") });
|
|
12972
13055
|
}
|
|
12973
13056
|
}
|
|
12974
13057
|
async function prepareCompose(worktreeRoot, project, repo, branch, opts, configuredBaseFile) {
|
|
@@ -12985,7 +13068,7 @@ async function prepareCompose(worktreeRoot, project, repo, branch, opts, configu
|
|
|
12985
13068
|
services
|
|
12986
13069
|
});
|
|
12987
13070
|
ensureDir(hestiaDir(worktreeRoot));
|
|
12988
|
-
const overridePath =
|
|
13071
|
+
const overridePath = join24(hestiaDir(worktreeRoot), OVERRIDE_FILE);
|
|
12989
13072
|
writeAtomicTextFile(overridePath, yaml);
|
|
12990
13073
|
return {
|
|
12991
13074
|
ctx: {
|
|
@@ -13018,7 +13101,7 @@ function writeDockerfileComposeModel(worktreeRoot, workloads, conventionalCompos
|
|
|
13018
13101
|
...conventionalComposeFile === undefined ? {} : { include: [{ path: conventionalComposeFile }] },
|
|
13019
13102
|
services
|
|
13020
13103
|
};
|
|
13021
|
-
const path =
|
|
13104
|
+
const path = join24(hestiaDir(worktreeRoot), DOCKERFILE_COMPOSE_FILE);
|
|
13022
13105
|
writeAtomicTextFile(path, $stringify(model));
|
|
13023
13106
|
return path;
|
|
13024
13107
|
}
|
|
@@ -13038,7 +13121,7 @@ function freshRecord(project, repoId, repo, branch, worktree) {
|
|
|
13038
13121
|
};
|
|
13039
13122
|
}
|
|
13040
13123
|
function assertCurrentStackIdentity(record, current) {
|
|
13041
|
-
const path =
|
|
13124
|
+
const path = join24(hestiaDir(current.worktreeRoot), "stack.json");
|
|
13042
13125
|
assertMutableStackRecord(record, path);
|
|
13043
13126
|
const matches = record.repoId === current.repoId && record.repo === current.repo && record.branch === current.branch && resolve2(record.worktree) === resolve2(current.worktreeRoot) && record.project === projectName(current.repoId, current.repo, current.branch, current.worktreeRoot);
|
|
13044
13127
|
if (!matches) {
|
|
@@ -13218,7 +13301,7 @@ function matchesExpectedStack(record, expected) {
|
|
|
13218
13301
|
return record !== null && (record.repoId === undefined || record.repoId === expected.repoId) && record.worktree === expected.worktree && record.createdAt === expected.createdAt;
|
|
13219
13302
|
}
|
|
13220
13303
|
function projectMutationRoot(project) {
|
|
13221
|
-
return
|
|
13304
|
+
return join24(hestiaHome(), "project-locks", project);
|
|
13222
13305
|
}
|
|
13223
13306
|
async function assertNamedTunnelDns(hostname, tunnelUuid) {
|
|
13224
13307
|
const expected = `${tunnelUuid}.cfargotunnel.com`;
|
|
@@ -13825,7 +13908,7 @@ class ComposeEngine {
|
|
|
13825
13908
|
const composeFile = record?.composeFile ?? tryLoadConfig(worktreeRoot)?.composeFile;
|
|
13826
13909
|
if (composeFile !== undefined) {
|
|
13827
13910
|
const project2 = record?.project ?? projectName(repoId, repo, branch, worktreeRoot);
|
|
13828
|
-
const overrideFile = record?.overrideFile ??
|
|
13911
|
+
const overrideFile = record?.overrideFile ?? join24(hestiaDir(worktreeRoot), OVERRIDE_FILE);
|
|
13829
13912
|
if (existsSync21(overrideFile)) {
|
|
13830
13913
|
await composeDown({ project: project2, baseFile: composeFile, overrideFile, cwd: worktreeRoot }, opts?.destroy ?? false);
|
|
13831
13914
|
} else if (record?.composeFile !== undefined) {
|
|
@@ -14058,7 +14141,7 @@ class ComposeEngine {
|
|
|
14058
14141
|
const alias = selection.endpoint.alias ?? selection.endpoint.name;
|
|
14059
14142
|
const authority = internalEndpointAuthority(record.project, alias);
|
|
14060
14143
|
const name = `aux-quick-${createHash7("sha256").update(alias).digest("hex").slice(0, 10)}`;
|
|
14061
|
-
const quickCfg =
|
|
14144
|
+
const quickCfg = join24(hestiaDir(worktreeRoot), `quick-${name}.yml`);
|
|
14062
14145
|
writeAtomicTextFile(quickCfg, `ingress:
|
|
14063
14146
|
- service: ${JSON.stringify(`unix:${publicGatewaySocketPath()}`)}
|
|
14064
14147
|
` + ` originRequest:
|
|
@@ -14454,8 +14537,13 @@ function startDuties(admission, opts) {
|
|
|
14454
14537
|
for (const uuid of listAdopted()) {
|
|
14455
14538
|
try {
|
|
14456
14539
|
const pf = connectorPidfile(uuid);
|
|
14457
|
-
if (pf !== null && isLive(pf))
|
|
14540
|
+
if (pf !== null && isLive(pf)) {
|
|
14541
|
+
const { reapedGroups } = await reapOrphanConnectors(uuid, pf);
|
|
14542
|
+
if (reapedGroups > 0) {
|
|
14543
|
+
log(`sweep: reaped ${reapedGroups} orphan connector group(s) for ${uuid}`);
|
|
14544
|
+
}
|
|
14458
14545
|
continue;
|
|
14546
|
+
}
|
|
14459
14547
|
const ref = readAdopted(uuid);
|
|
14460
14548
|
if (ref === null)
|
|
14461
14549
|
continue;
|
|
@@ -14465,6 +14553,11 @@ function startDuties(admission, opts) {
|
|
|
14465
14553
|
const outcome = await reconcileTunnel(ref);
|
|
14466
14554
|
if (outcome.restarted) {
|
|
14467
14555
|
log(`sweep: connector for ${ref.name} (${uuid}) revived (ready=${outcome.ready})`);
|
|
14556
|
+
} else {
|
|
14557
|
+
for (const w of outcome.warnings) {
|
|
14558
|
+
if (w.includes("reaped"))
|
|
14559
|
+
log(`sweep: ${w}`);
|
|
14560
|
+
}
|
|
14468
14561
|
}
|
|
14469
14562
|
} catch (err) {
|
|
14470
14563
|
log(`sweep: connector revival for ${uuid} failed: ${err.message}`);
|
|
@@ -14536,11 +14629,11 @@ async function main() {
|
|
|
14536
14629
|
port: "none",
|
|
14537
14630
|
backend: "proc"
|
|
14538
14631
|
}),
|
|
14539
|
-
logPath:
|
|
14632
|
+
logPath: join25(root, "daemon.log"),
|
|
14540
14633
|
signal: "term",
|
|
14541
14634
|
backend: "proc"
|
|
14542
14635
|
});
|
|
14543
|
-
writeAtomicJsonFile(
|
|
14636
|
+
writeAtomicJsonFile(join25(root, "daemon.json"), {
|
|
14544
14637
|
schemaVersion: STATE_SCHEMA_VERSION,
|
|
14545
14638
|
pid: process.pid,
|
|
14546
14639
|
port: server.port,
|
|
@@ -14556,7 +14649,7 @@ async function main() {
|
|
|
14556
14649
|
fleet.stop();
|
|
14557
14650
|
localRouter.stop();
|
|
14558
14651
|
removePidfile(root, PIDFILE_NAME2);
|
|
14559
|
-
rmSync11(
|
|
14652
|
+
rmSync11(join25(root, "daemon.json"), { force: true });
|
|
14560
14653
|
server.stop(true);
|
|
14561
14654
|
process.exit(0);
|
|
14562
14655
|
};
|
|
@@ -14571,4 +14664,4 @@ async function main() {
|
|
|
14571
14664
|
}
|
|
14572
14665
|
main();
|
|
14573
14666
|
|
|
14574
|
-
//# debugId=
|
|
14667
|
+
//# debugId=E5997ABC0405B8AE64756E2164756E21
|