@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/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 join24 } from "path";
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 join23, resolve as resolve2 } from "path";
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 join21(hestiaHome(), "tunnel", uuid);
11709
+ return join22(hestiaHome(), "tunnel", uuid);
11631
11710
  }
11632
11711
  function configPath(uuid) {
11633
- return join21(tunnelDir(uuid), "config.yml");
11712
+ return join22(tunnelDir(uuid), "config.yml");
11634
11713
  }
11635
11714
  function adoptedMarker(uuid) {
11636
- return join21(tunnelDir(uuid), "adopted.json");
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 = join21(hestiaHome(), "stacks");
11734
+ const stacksDir = join22(hestiaHome(), "stacks");
11656
11735
  if (existsSync19(stacksDir)) {
11657
11736
  for (const project of readdirSync7(stacksDir)) {
11658
- const sp = join21(stacksDir, project, "stack.json");
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 = join21(hestiaHome(), "tunnel");
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 = join21(hestiaHome(), "stacks");
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 = join21(stacksDir, project, "stack.json");
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 join22 } from "path";
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 = join22(hestiaHome(), "stacks");
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 = join22(stacksDirectory, project, "stack.json");
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 ${join23(worktreeRoot, ".gitignore")}`, { remedy: `.hestia/`, path: join23(worktreeRoot, ".gitignore") });
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 = join23(hestiaDir(worktreeRoot), OVERRIDE_FILE);
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 = join23(hestiaDir(worktreeRoot), DOCKERFILE_COMPOSE_FILE);
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 = join23(hestiaDir(current.worktreeRoot), "stack.json");
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 join23(hestiaHome(), "project-locks", project);
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 ?? join23(hestiaDir(worktreeRoot), OVERRIDE_FILE);
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 = join23(hestiaDir(worktreeRoot), `quick-${name}.yml`);
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: join24(root, "daemon.log"),
14632
+ logPath: join25(root, "daemon.log"),
14540
14633
  signal: "term",
14541
14634
  backend: "proc"
14542
14635
  });
14543
- writeAtomicJsonFile(join24(root, "daemon.json"), {
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(join24(root, "daemon.json"), { force: true });
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=9176C6E8002FF49E64756E2164756E21
14667
+ //# debugId=E5997ABC0405B8AE64756E2164756E21