@onklave/agent-cli 0.1.48 → 0.1.50
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/main.js +1005 -74
- package/package.json +1 -1
package/main.js
CHANGED
|
@@ -8,9 +8,24 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
8
8
|
|
|
9
9
|
// _apps/@onklave/agent-cli/src/services/credential-store.ts
|
|
10
10
|
import * as fs from "fs";
|
|
11
|
-
import * as
|
|
12
|
-
import * as os from "os";
|
|
11
|
+
import * as path2 from "path";
|
|
13
12
|
import { createRequire } from "module";
|
|
13
|
+
|
|
14
|
+
// _apps/@onklave/agent-cli/src/services/config-paths.ts
|
|
15
|
+
import * as os from "os";
|
|
16
|
+
import * as path from "path";
|
|
17
|
+
function resolveConfigDir() {
|
|
18
|
+
const override = process.env["ONKLAVE_CONFIG_DIR"];
|
|
19
|
+
if (override && override.trim()) {
|
|
20
|
+
return override.trim();
|
|
21
|
+
}
|
|
22
|
+
return path.join(os.homedir(), ".config", "onklave");
|
|
23
|
+
}
|
|
24
|
+
function credentialsFilePath() {
|
|
25
|
+
return path.join(resolveConfigDir(), "credentials.json");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// _apps/@onklave/agent-cli/src/services/credential-store.ts
|
|
14
29
|
var nodeRequire = createRequire(import.meta.url);
|
|
15
30
|
var KEYTAR_SERVICE = "onklave-agent-cli";
|
|
16
31
|
var KEYTAR_ACCOUNT = "default";
|
|
@@ -33,13 +48,13 @@ function warnFallback(reason) {
|
|
|
33
48
|
}
|
|
34
49
|
warnedFallback = true;
|
|
35
50
|
console.warn(
|
|
36
|
-
`[onklave] OS keychain unavailable (${reason}); falling back to file store at
|
|
51
|
+
`[onklave] OS keychain unavailable (${reason}); falling back to file store at ${credentialsFilePath()}`
|
|
37
52
|
);
|
|
38
53
|
}
|
|
39
54
|
var CredentialStore = class {
|
|
40
55
|
constructor() {
|
|
41
|
-
this.configDir =
|
|
42
|
-
this.credentialsPath =
|
|
56
|
+
this.configDir = resolveConfigDir();
|
|
57
|
+
this.credentialsPath = path2.join(this.configDir, "credentials.json");
|
|
43
58
|
}
|
|
44
59
|
/*
|
|
45
60
|
* Read the full credentials object, keychain-first.
|
|
@@ -364,7 +379,7 @@ async function whoamiCommand() {
|
|
|
364
379
|
|
|
365
380
|
// _apps/@onklave/agent-cli/src/services/config-resolver.ts
|
|
366
381
|
import * as fs2 from "fs";
|
|
367
|
-
import * as
|
|
382
|
+
import * as path3 from "path";
|
|
368
383
|
import * as os2 from "os";
|
|
369
384
|
var DEFAULT_MODEL = "claude-sonnet-4-20250514";
|
|
370
385
|
var DEFAULT_TIMEOUT = 120;
|
|
@@ -456,7 +471,7 @@ var ConfigResolver = class {
|
|
|
456
471
|
const fromFlags = {};
|
|
457
472
|
if (typeof flags["task"] === "string") fromFlags.task = flags["task"];
|
|
458
473
|
if (typeof flags["context"] === "string")
|
|
459
|
-
fromFlags.context =
|
|
474
|
+
fromFlags.context = path3.resolve(flags["context"]);
|
|
460
475
|
if (typeof flags["persona"] === "string")
|
|
461
476
|
fromFlags.persona = flags["persona"];
|
|
462
477
|
if (typeof flags["workflow"] === "string")
|
|
@@ -482,7 +497,7 @@ var ConfigResolver = class {
|
|
|
482
497
|
* Load .onklave.json from the given directory.
|
|
483
498
|
*/
|
|
484
499
|
loadOnklaveJson(cwd) {
|
|
485
|
-
const filePath =
|
|
500
|
+
const filePath = path3.join(cwd, ".onklave.json");
|
|
486
501
|
if (!fs2.existsSync(filePath)) {
|
|
487
502
|
return null;
|
|
488
503
|
}
|
|
@@ -500,7 +515,7 @@ var ConfigResolver = class {
|
|
|
500
515
|
* Load global config from ~/.config/onklave/config.json.
|
|
501
516
|
*/
|
|
502
517
|
loadGlobalConfig() {
|
|
503
|
-
const filePath =
|
|
518
|
+
const filePath = path3.join(
|
|
504
519
|
os2.homedir(),
|
|
505
520
|
".config",
|
|
506
521
|
"onklave",
|
|
@@ -520,8 +535,8 @@ var ConfigResolver = class {
|
|
|
520
535
|
* Save a value to the global config file.
|
|
521
536
|
*/
|
|
522
537
|
saveGlobalConfig(key, value) {
|
|
523
|
-
const configDir =
|
|
524
|
-
const filePath =
|
|
538
|
+
const configDir = path3.join(os2.homedir(), ".config", "onklave");
|
|
539
|
+
const filePath = path3.join(configDir, "config.json");
|
|
525
540
|
if (!fs2.existsSync(configDir)) {
|
|
526
541
|
fs2.mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
527
542
|
}
|
|
@@ -774,8 +789,8 @@ var PlatformClient = class {
|
|
|
774
789
|
/*
|
|
775
790
|
* Make an authenticated HTTP request to the platform.
|
|
776
791
|
*/
|
|
777
|
-
async request(method,
|
|
778
|
-
const url = `${this.baseUrl}${GATEWAY_SERVICE_PREFIX}${
|
|
792
|
+
async request(method, path14, body, extraHeaders) {
|
|
793
|
+
const url = `${this.baseUrl}${GATEWAY_SERVICE_PREFIX}${path14}`;
|
|
779
794
|
const headers = {
|
|
780
795
|
Authorization: `Bearer ${this.token}`,
|
|
781
796
|
"Content-Type": "application/json",
|
|
@@ -1250,33 +1265,33 @@ var AuditStreamer = class {
|
|
|
1250
1265
|
|
|
1251
1266
|
// _apps/@onklave/agent-cli/src/services/guardrail-enforcer.ts
|
|
1252
1267
|
import * as fs3 from "fs";
|
|
1253
|
-
import * as
|
|
1268
|
+
import * as path4 from "path";
|
|
1254
1269
|
function safeRealpath(target) {
|
|
1255
|
-
let current =
|
|
1270
|
+
let current = path4.resolve(target);
|
|
1256
1271
|
const tail = [];
|
|
1257
1272
|
for (let i = 0; i < 4096; i++) {
|
|
1258
1273
|
try {
|
|
1259
1274
|
const real = fs3.realpathSync(current);
|
|
1260
|
-
return tail.length ?
|
|
1275
|
+
return tail.length ? path4.join(real, ...tail.reverse()) : real;
|
|
1261
1276
|
} catch {
|
|
1262
|
-
const parent =
|
|
1277
|
+
const parent = path4.dirname(current);
|
|
1263
1278
|
if (parent === current) break;
|
|
1264
|
-
tail.push(
|
|
1279
|
+
tail.push(path4.basename(current));
|
|
1265
1280
|
current = parent;
|
|
1266
1281
|
}
|
|
1267
1282
|
}
|
|
1268
|
-
return
|
|
1283
|
+
return path4.resolve(target);
|
|
1269
1284
|
}
|
|
1270
1285
|
function isPathWithin(target, root) {
|
|
1271
1286
|
const realRoot = safeRealpath(root);
|
|
1272
1287
|
const realTarget = safeRealpath(target);
|
|
1273
1288
|
if (realTarget === realRoot) return true;
|
|
1274
|
-
const rootWithSep = realRoot.endsWith(
|
|
1289
|
+
const rootWithSep = realRoot.endsWith(path4.sep) ? realRoot : realRoot + path4.sep;
|
|
1275
1290
|
return realTarget.startsWith(rootWithSep);
|
|
1276
1291
|
}
|
|
1277
1292
|
function checkWorkspaceAccess(contextPath, allowedRoots) {
|
|
1278
1293
|
const real = safeRealpath(contextPath);
|
|
1279
|
-
if (real ===
|
|
1294
|
+
if (real === path4.parse(real).root) {
|
|
1280
1295
|
return {
|
|
1281
1296
|
allowed: false,
|
|
1282
1297
|
reason: `Refusing to run with the filesystem root "${real}" as the workspace.`
|
|
@@ -1387,8 +1402,8 @@ var GuardrailEnforcer = class {
|
|
|
1387
1402
|
"id_rsa",
|
|
1388
1403
|
"id_ed25519"
|
|
1389
1404
|
];
|
|
1390
|
-
const basename3 =
|
|
1391
|
-
const normForward = realPath.split(
|
|
1405
|
+
const basename3 = path4.basename(realPath);
|
|
1406
|
+
const normForward = realPath.split(path4.sep).join("/");
|
|
1392
1407
|
for (const sensitive of sensitivePatterns) {
|
|
1393
1408
|
if (basename3 === sensitive || normForward.includes(`/${sensitive}`)) {
|
|
1394
1409
|
return {
|
|
@@ -1530,7 +1545,7 @@ var HeartbeatService = class {
|
|
|
1530
1545
|
|
|
1531
1546
|
// _apps/@onklave/agent-cli/src/services/cli-version.ts
|
|
1532
1547
|
import * as fs4 from "node:fs";
|
|
1533
|
-
import * as
|
|
1548
|
+
import * as path5 from "node:path";
|
|
1534
1549
|
import { fileURLToPath } from "node:url";
|
|
1535
1550
|
|
|
1536
1551
|
// _apps/@onklave/agent-cli/src/services/daemon-update-checker.service.ts
|
|
@@ -1621,14 +1636,14 @@ function parseSemverNumeric(v) {
|
|
|
1621
1636
|
// _apps/@onklave/agent-cli/src/services/cli-version.ts
|
|
1622
1637
|
function readPackageVersion() {
|
|
1623
1638
|
try {
|
|
1624
|
-
const moduleDir =
|
|
1639
|
+
const moduleDir = path5.dirname(fileURLToPath(import.meta.url));
|
|
1625
1640
|
const candidates = [
|
|
1626
|
-
|
|
1641
|
+
path5.join(moduleDir, "package.json"),
|
|
1627
1642
|
// bundled: dist root
|
|
1628
|
-
|
|
1629
|
-
|
|
1643
|
+
path5.join(moduleDir, "..", "package.json"),
|
|
1644
|
+
path5.join(moduleDir, "..", "..", "package.json"),
|
|
1630
1645
|
// dev: src/services -> root
|
|
1631
|
-
|
|
1646
|
+
path5.join(moduleDir, "..", "..", "..", "package.json")
|
|
1632
1647
|
];
|
|
1633
1648
|
for (const p of candidates) {
|
|
1634
1649
|
if (!fs4.existsSync(p)) continue;
|
|
@@ -1661,7 +1676,7 @@ function collectPosture() {
|
|
|
1661
1676
|
}
|
|
1662
1677
|
|
|
1663
1678
|
// _apps/@onklave/agent-cli/src/commands/run.command.ts
|
|
1664
|
-
import * as
|
|
1679
|
+
import * as path6 from "path";
|
|
1665
1680
|
|
|
1666
1681
|
// _apps/@onklave/agent-cli/src/tui/render.tsx
|
|
1667
1682
|
import { render } from "ink";
|
|
@@ -2361,7 +2376,7 @@ async function runCommand(args) {
|
|
|
2361
2376
|
}
|
|
2362
2377
|
const addDirs = [
|
|
2363
2378
|
.../* @__PURE__ */ new Set([
|
|
2364
|
-
|
|
2379
|
+
path6.resolve(resolvedConfig.context),
|
|
2365
2380
|
...effectiveAllowedRoots
|
|
2366
2381
|
])
|
|
2367
2382
|
];
|
|
@@ -2825,7 +2840,7 @@ async function denyCommand(args) {
|
|
|
2825
2840
|
|
|
2826
2841
|
// _apps/@onklave/agent-cli/src/commands/doctor.command.ts
|
|
2827
2842
|
import * as fs6 from "fs";
|
|
2828
|
-
import * as
|
|
2843
|
+
import * as path7 from "path";
|
|
2829
2844
|
|
|
2830
2845
|
// _apps/@onklave/agent-cli/src/services/command-exists.ts
|
|
2831
2846
|
import { execSync } from "child_process";
|
|
@@ -2955,7 +2970,7 @@ function checkNodeVersion() {
|
|
|
2955
2970
|
};
|
|
2956
2971
|
}
|
|
2957
2972
|
function checkProjectConfig() {
|
|
2958
|
-
const configPath =
|
|
2973
|
+
const configPath = path7.join(process.cwd(), ".onklave.json");
|
|
2959
2974
|
if (fs6.existsSync(configPath)) {
|
|
2960
2975
|
try {
|
|
2961
2976
|
const raw = fs6.readFileSync(configPath, "utf-8");
|
|
@@ -3009,9 +3024,9 @@ async function checkWebSocket() {
|
|
|
3009
3024
|
|
|
3010
3025
|
// _apps/@onklave/agent-cli/src/commands/init.command.ts
|
|
3011
3026
|
import * as fs7 from "fs";
|
|
3012
|
-
import * as
|
|
3027
|
+
import * as path8 from "path";
|
|
3013
3028
|
async function initCommand() {
|
|
3014
|
-
const configPath =
|
|
3029
|
+
const configPath = path8.join(process.cwd(), ".onklave.json");
|
|
3015
3030
|
if (fs7.existsSync(configPath)) {
|
|
3016
3031
|
console.error("Error: .onklave.json already exists in this directory.");
|
|
3017
3032
|
console.log("To overwrite, delete the existing file first.");
|
|
@@ -3019,7 +3034,7 @@ async function initCommand() {
|
|
|
3019
3034
|
return;
|
|
3020
3035
|
}
|
|
3021
3036
|
const template = {
|
|
3022
|
-
project:
|
|
3037
|
+
project: path8.basename(process.cwd()),
|
|
3023
3038
|
org: "",
|
|
3024
3039
|
description: "",
|
|
3025
3040
|
defaults: {
|
|
@@ -3279,8 +3294,9 @@ async function logsCommand(args) {
|
|
|
3279
3294
|
}
|
|
3280
3295
|
|
|
3281
3296
|
// _apps/@onklave/agent-cli/src/commands/daemon.command.ts
|
|
3282
|
-
import * as
|
|
3283
|
-
import * as
|
|
3297
|
+
import * as fs11 from "fs";
|
|
3298
|
+
import * as path12 from "path";
|
|
3299
|
+
import * as os7 from "os";
|
|
3284
3300
|
|
|
3285
3301
|
// _apps/@onklave/agent-cli/src/services/daemon-comms.service.ts
|
|
3286
3302
|
var DaemonCommsService = class {
|
|
@@ -3707,8 +3723,8 @@ var PlatformBrokerClient = class {
|
|
|
3707
3723
|
params
|
|
3708
3724
|
);
|
|
3709
3725
|
}
|
|
3710
|
-
async request(method,
|
|
3711
|
-
const url = `${this.baseUrl}/agent-orchestration${
|
|
3726
|
+
async request(method, path14, body) {
|
|
3727
|
+
const url = `${this.baseUrl}/agent-orchestration${path14}`;
|
|
3712
3728
|
const response = await fetch(url, {
|
|
3713
3729
|
method,
|
|
3714
3730
|
headers: {
|
|
@@ -3744,20 +3760,28 @@ var PlatformBrokerClient = class {
|
|
|
3744
3760
|
|
|
3745
3761
|
// _apps/@onklave/agent-cli/src/services/work-item-runner.service.ts
|
|
3746
3762
|
import { spawn as spawn2 } from "child_process";
|
|
3763
|
+
import * as crypto from "crypto";
|
|
3747
3764
|
import * as fs8 from "fs";
|
|
3748
3765
|
import * as os5 from "os";
|
|
3749
|
-
import * as
|
|
3766
|
+
import * as path9 from "path";
|
|
3750
3767
|
var WorkItemRunner = class {
|
|
3751
3768
|
constructor(opts = {}) {
|
|
3769
|
+
/**
|
|
3770
|
+
* Per-cache-dir promise chain serialising the short git prepare/cleanup
|
|
3771
|
+
* sections so concurrent tasks on the same repo don't race on the shared
|
|
3772
|
+
* `.git` (worktree add/remove, fetch). Keyed by absolute cache dir.
|
|
3773
|
+
*/
|
|
3774
|
+
this.repoLocks = /* @__PURE__ */ new Map();
|
|
3752
3775
|
this.sessionManager = opts.sessionManager ?? new SessionManager();
|
|
3753
3776
|
this.git = opts.git ?? defaultGit;
|
|
3754
3777
|
this.model = opts.model ?? "claude-sonnet-4-6";
|
|
3755
3778
|
this.timeoutSeconds = opts.timeoutSeconds ?? 1800;
|
|
3779
|
+
this.cacheRoot = opts.cacheRoot ?? path9.join(os5.homedir(), ".onklave", "clones");
|
|
3756
3780
|
}
|
|
3757
3781
|
/**
|
|
3758
|
-
*
|
|
3759
|
-
* failures are mapped to a `failed` result with an error
|
|
3760
|
-
* caller can always report a status back to the broker.
|
|
3782
|
+
* Prepare workspace → run Claude Code → tear down. Returns the run outcome;
|
|
3783
|
+
* never throws — failures are mapped to a `failed` result with an error
|
|
3784
|
+
* summary so the caller can always report a status back to the broker.
|
|
3761
3785
|
*/
|
|
3762
3786
|
async run(sessionId, workItem) {
|
|
3763
3787
|
const repo = workItem.project?.repos?.[0];
|
|
@@ -3768,18 +3792,19 @@ var WorkItemRunner = class {
|
|
|
3768
3792
|
};
|
|
3769
3793
|
}
|
|
3770
3794
|
const workDir = fs8.mkdtempSync(
|
|
3771
|
-
|
|
3795
|
+
path9.join(os5.tmpdir(), `onklave-pickup-${sessionId}-`)
|
|
3772
3796
|
);
|
|
3773
|
-
const repoDir =
|
|
3797
|
+
const repoDir = path9.join(workDir, sanitizeName(repo.name) || "repo");
|
|
3774
3798
|
const branchName = `onklave/work-item/${workItem.id}`;
|
|
3799
|
+
const repoUrl = repo.url;
|
|
3800
|
+
let cacheDir;
|
|
3775
3801
|
try {
|
|
3776
|
-
await this.
|
|
3777
|
-
await this.git(["checkout", "-b", branchName], repoDir);
|
|
3802
|
+
cacheDir = await this.prepareWorkspace(repoUrl, repoDir, branchName);
|
|
3778
3803
|
} catch (err) {
|
|
3779
3804
|
cleanup(workDir);
|
|
3780
3805
|
return {
|
|
3781
3806
|
status: "failed",
|
|
3782
|
-
summary: `Failed to prepare workspace for ${
|
|
3807
|
+
summary: `Failed to prepare workspace for ${repoUrl}: ${err.message}`,
|
|
3783
3808
|
branchName
|
|
3784
3809
|
};
|
|
3785
3810
|
}
|
|
@@ -3792,7 +3817,7 @@ var WorkItemRunner = class {
|
|
|
3792
3817
|
});
|
|
3793
3818
|
const writeCheck = guardrails.checkPathAccess(repoDir, "write");
|
|
3794
3819
|
if (!writeCheck.allowed) {
|
|
3795
|
-
|
|
3820
|
+
await this.cleanupWorkspace(workDir, repoDir, cacheDir, branchName);
|
|
3796
3821
|
return {
|
|
3797
3822
|
status: "failed",
|
|
3798
3823
|
summary: `Guardrails blocked the workspace: ${writeCheck.reason}`,
|
|
@@ -3839,7 +3864,7 @@ var WorkItemRunner = class {
|
|
|
3839
3864
|
}
|
|
3840
3865
|
});
|
|
3841
3866
|
});
|
|
3842
|
-
|
|
3867
|
+
await this.cleanupWorkspace(workDir, repoDir, cacheDir, branchName);
|
|
3843
3868
|
if (exitCode === 0) {
|
|
3844
3869
|
return {
|
|
3845
3870
|
status: "in_review",
|
|
@@ -3854,6 +3879,89 @@ var WorkItemRunner = class {
|
|
|
3854
3879
|
branchName
|
|
3855
3880
|
};
|
|
3856
3881
|
}
|
|
3882
|
+
/**
|
|
3883
|
+
* Lay down a working tree for the task at `repoDir` on a fresh `branchName`.
|
|
3884
|
+
*
|
|
3885
|
+
* Fast path: ensure a persistent clone exists for the repo (clone once, fetch
|
|
3886
|
+
* thereafter) and add a throwaway worktree for this task. Returns the cache
|
|
3887
|
+
* dir so teardown can remove the worktree + branch.
|
|
3888
|
+
*
|
|
3889
|
+
* Fallback: on any failure of the cached path, do the original
|
|
3890
|
+
* fresh-clone-then-branch into `repoDir` and return null (nothing cached to
|
|
3891
|
+
* unwind). This keeps the optimization strictly non-regressing.
|
|
3892
|
+
*/
|
|
3893
|
+
async prepareWorkspace(repoUrl, repoDir, branchName) {
|
|
3894
|
+
const cacheDir = this.cacheDirFor(repoUrl);
|
|
3895
|
+
try {
|
|
3896
|
+
await this.withRepoLock(
|
|
3897
|
+
cacheDir,
|
|
3898
|
+
() => this.prepareViaWorktree(repoUrl, cacheDir, repoDir, branchName)
|
|
3899
|
+
);
|
|
3900
|
+
return cacheDir;
|
|
3901
|
+
} catch {
|
|
3902
|
+
cleanup(repoDir);
|
|
3903
|
+
await this.git(["clone", repoUrl, repoDir], path9.dirname(repoDir));
|
|
3904
|
+
await this.git(["checkout", "-b", branchName], repoDir);
|
|
3905
|
+
return null;
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
/** Ensure the cache clone exists/up-to-date, then add a task worktree. */
|
|
3909
|
+
async prepareViaWorktree(repoUrl, cacheDir, taskDir, branchName) {
|
|
3910
|
+
if (fs8.existsSync(path9.join(cacheDir, ".git"))) {
|
|
3911
|
+
await this.git(["fetch", "--prune", "origin"], cacheDir);
|
|
3912
|
+
} else {
|
|
3913
|
+
cleanup(cacheDir);
|
|
3914
|
+
fs8.mkdirSync(path9.dirname(cacheDir), { recursive: true });
|
|
3915
|
+
await this.git(["clone", repoUrl, cacheDir], path9.dirname(cacheDir));
|
|
3916
|
+
}
|
|
3917
|
+
await this.git(["worktree", "prune"], cacheDir);
|
|
3918
|
+
await this.gitIgnoreError(["branch", "-D", branchName], cacheDir);
|
|
3919
|
+
await this.git(
|
|
3920
|
+
["worktree", "add", "--force", "-b", branchName, taskDir, "origin/HEAD"],
|
|
3921
|
+
cacheDir
|
|
3922
|
+
);
|
|
3923
|
+
}
|
|
3924
|
+
/**
|
|
3925
|
+
* Tear down a task workspace. For the worktree path this removes the linked
|
|
3926
|
+
* worktree and deletes its branch from the cache (keeping the cache clone for
|
|
3927
|
+
* reuse); for the fresh-clone path (`cacheDir === null`) it just removes the
|
|
3928
|
+
* temp dir. Always best-effort — teardown failures never fail the run.
|
|
3929
|
+
*/
|
|
3930
|
+
async cleanupWorkspace(workDir, taskDir, cacheDir, branchName) {
|
|
3931
|
+
if (cacheDir) {
|
|
3932
|
+
await this.withRepoLock(cacheDir, async () => {
|
|
3933
|
+
await this.gitIgnoreError(
|
|
3934
|
+
["worktree", "remove", "--force", taskDir],
|
|
3935
|
+
cacheDir
|
|
3936
|
+
);
|
|
3937
|
+
await this.gitIgnoreError(["worktree", "prune"], cacheDir);
|
|
3938
|
+
await this.gitIgnoreError(["branch", "-D", branchName], cacheDir);
|
|
3939
|
+
});
|
|
3940
|
+
}
|
|
3941
|
+
cleanup(workDir);
|
|
3942
|
+
}
|
|
3943
|
+
/** Absolute cache dir for a repo URL — stable hash, collision-safe enough. */
|
|
3944
|
+
cacheDirFor(repoUrl) {
|
|
3945
|
+
const hash = crypto.createHash("sha1").update(repoUrl).digest("hex").slice(0, 16);
|
|
3946
|
+
return path9.join(this.cacheRoot, hash);
|
|
3947
|
+
}
|
|
3948
|
+
/** Run a git command, swallowing a non-zero exit (best-effort cleanup). */
|
|
3949
|
+
gitIgnoreError(args, cwd) {
|
|
3950
|
+
return this.git(args, cwd).catch(() => void 0);
|
|
3951
|
+
}
|
|
3952
|
+
/** Serialise an async section against others sharing the same key. */
|
|
3953
|
+
withRepoLock(key, fn) {
|
|
3954
|
+
const prev = this.repoLocks.get(key) ?? Promise.resolve();
|
|
3955
|
+
const next = prev.then(fn, fn);
|
|
3956
|
+
this.repoLocks.set(
|
|
3957
|
+
key,
|
|
3958
|
+
next.then(
|
|
3959
|
+
() => void 0,
|
|
3960
|
+
() => void 0
|
|
3961
|
+
)
|
|
3962
|
+
);
|
|
3963
|
+
return next;
|
|
3964
|
+
}
|
|
3857
3965
|
};
|
|
3858
3966
|
function buildTask(workItem) {
|
|
3859
3967
|
const parts = [workItem.title];
|
|
@@ -4195,8 +4303,7 @@ var DaemonUpgradeService = class {
|
|
|
4195
4303
|
|
|
4196
4304
|
// _apps/@onklave/agent-cli/src/services/daemon-state.service.ts
|
|
4197
4305
|
import * as fs9 from "fs";
|
|
4198
|
-
import * as
|
|
4199
|
-
import * as os7 from "os";
|
|
4306
|
+
import * as path10 from "path";
|
|
4200
4307
|
var VALID_TRANSITIONS = {
|
|
4201
4308
|
installing: ["registered"],
|
|
4202
4309
|
registered: ["starting"],
|
|
@@ -4207,10 +4314,10 @@ var VALID_TRANSITIONS = {
|
|
|
4207
4314
|
stopped: ["starting"]
|
|
4208
4315
|
};
|
|
4209
4316
|
function defaultStateFilePath() {
|
|
4210
|
-
return
|
|
4317
|
+
return path10.join(resolveConfigDir(), "daemon.state.json");
|
|
4211
4318
|
}
|
|
4212
4319
|
function defaultPidFilePath() {
|
|
4213
|
-
return
|
|
4320
|
+
return path10.join(resolveConfigDir(), "daemon.pid");
|
|
4214
4321
|
}
|
|
4215
4322
|
var DaemonStateError = class extends Error {
|
|
4216
4323
|
constructor(message) {
|
|
@@ -4285,7 +4392,7 @@ var DaemonStateService = class {
|
|
|
4285
4392
|
}
|
|
4286
4393
|
}
|
|
4287
4394
|
persist(reason) {
|
|
4288
|
-
const dir =
|
|
4395
|
+
const dir = path10.dirname(this.stateFile);
|
|
4289
4396
|
fs9.mkdirSync(dir, { recursive: true });
|
|
4290
4397
|
const payload = {
|
|
4291
4398
|
state: this.current,
|
|
@@ -4428,6 +4535,568 @@ function emitUnitFile(flavour) {
|
|
|
4428
4535
|
}
|
|
4429
4536
|
}
|
|
4430
4537
|
|
|
4538
|
+
// _apps/@onklave/agent-cli/src/services/daemon-installer.service.ts
|
|
4539
|
+
import * as fs10 from "fs";
|
|
4540
|
+
import * as path11 from "path";
|
|
4541
|
+
import { execFileSync } from "child_process";
|
|
4542
|
+
var LAUNCHD_LABEL = "app.onklave.daemon";
|
|
4543
|
+
var SYSTEMD_UNIT_NAME = "onklave-daemon";
|
|
4544
|
+
var WINDOWS_TASK_NAME = "OnklaveDaemon";
|
|
4545
|
+
var SYSTEMD_SYSTEM_UNIT_PATH = `/etc/systemd/system/${SYSTEMD_UNIT_NAME}.service`;
|
|
4546
|
+
var LINUX_SERVICE_USER = "onklave";
|
|
4547
|
+
var WINDOWS_SERVICE_ACCOUNT = "NT AUTHORITY\\LocalService";
|
|
4548
|
+
function xmlEscape(value) {
|
|
4549
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4550
|
+
}
|
|
4551
|
+
function macPlistPath(homeDir) {
|
|
4552
|
+
return path11.join(
|
|
4553
|
+
homeDir,
|
|
4554
|
+
"Library",
|
|
4555
|
+
"LaunchAgents",
|
|
4556
|
+
`${LAUNCHD_LABEL}.plist`
|
|
4557
|
+
);
|
|
4558
|
+
}
|
|
4559
|
+
function macLogDir(homeDir) {
|
|
4560
|
+
return path11.join(homeDir, "Library", "Logs", "onklave");
|
|
4561
|
+
}
|
|
4562
|
+
function macPlist(inp) {
|
|
4563
|
+
const logDir = macLogDir(inp.homeDir);
|
|
4564
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4565
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4566
|
+
<plist version="1.0">
|
|
4567
|
+
<dict>
|
|
4568
|
+
<key>Label</key>
|
|
4569
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
4570
|
+
<key>ProgramArguments</key>
|
|
4571
|
+
<array>
|
|
4572
|
+
<string>${inp.nodePath}</string>
|
|
4573
|
+
<string>${inp.cliPath}</string>
|
|
4574
|
+
<string>daemon</string>
|
|
4575
|
+
</array>
|
|
4576
|
+
<key>RunAtLoad</key>
|
|
4577
|
+
<true/>
|
|
4578
|
+
<key>KeepAlive</key>
|
|
4579
|
+
<dict>
|
|
4580
|
+
<key>SuccessfulExit</key>
|
|
4581
|
+
<false/>
|
|
4582
|
+
<key>Crashed</key>
|
|
4583
|
+
<true/>
|
|
4584
|
+
</dict>
|
|
4585
|
+
<key>ThrottleInterval</key>
|
|
4586
|
+
<integer>10</integer>
|
|
4587
|
+
<key>ProcessType</key>
|
|
4588
|
+
<string>Background</string>
|
|
4589
|
+
<key>StandardOutPath</key>
|
|
4590
|
+
<string>${path11.join(logDir, "daemon.out.log")}</string>
|
|
4591
|
+
<key>StandardErrorPath</key>
|
|
4592
|
+
<string>${path11.join(logDir, "daemon.err.log")}</string>
|
|
4593
|
+
</dict>
|
|
4594
|
+
</plist>
|
|
4595
|
+
`;
|
|
4596
|
+
}
|
|
4597
|
+
function macInstallPlan(inp) {
|
|
4598
|
+
const plistPath = macPlistPath(inp.homeDir);
|
|
4599
|
+
const domainTarget = `gui/${inp.uid ?? ""}`;
|
|
4600
|
+
const serviceTarget = `${domainTarget}/${LAUNCHD_LABEL}`;
|
|
4601
|
+
const commands = [
|
|
4602
|
+
// Tear down any prior agent so bootstrap doesn't fail on a stale label.
|
|
4603
|
+
{ argv: ["launchctl", "bootout", serviceTarget], optional: true }
|
|
4604
|
+
];
|
|
4605
|
+
if (inp.start) {
|
|
4606
|
+
commands.push({
|
|
4607
|
+
argv: ["launchctl", "bootstrap", domainTarget, plistPath]
|
|
4608
|
+
});
|
|
4609
|
+
commands.push({ argv: ["launchctl", "kickstart", "-k", serviceTarget] });
|
|
4610
|
+
}
|
|
4611
|
+
return {
|
|
4612
|
+
platform: "darwin",
|
|
4613
|
+
manager: "launchd",
|
|
4614
|
+
serviceLabel: LAUNCHD_LABEL,
|
|
4615
|
+
files: [{ filePath: plistPath, contents: macPlist(inp) }],
|
|
4616
|
+
commands,
|
|
4617
|
+
ensureDirs: [macLogDir(inp.homeDir)],
|
|
4618
|
+
startsNow: inp.start
|
|
4619
|
+
};
|
|
4620
|
+
}
|
|
4621
|
+
function macUninstallPlan(homeDir, uid) {
|
|
4622
|
+
const serviceTarget = `gui/${uid ?? ""}/${LAUNCHD_LABEL}`;
|
|
4623
|
+
return {
|
|
4624
|
+
platform: "darwin",
|
|
4625
|
+
manager: "launchd",
|
|
4626
|
+
serviceLabel: LAUNCHD_LABEL,
|
|
4627
|
+
removeFiles: [macPlistPath(homeDir)],
|
|
4628
|
+
commands: [
|
|
4629
|
+
{ argv: ["launchctl", "bootout", serviceTarget], optional: true }
|
|
4630
|
+
]
|
|
4631
|
+
};
|
|
4632
|
+
}
|
|
4633
|
+
function systemdUnitPath(homeDir) {
|
|
4634
|
+
return path11.join(
|
|
4635
|
+
homeDir,
|
|
4636
|
+
".config",
|
|
4637
|
+
"systemd",
|
|
4638
|
+
"user",
|
|
4639
|
+
`${SYSTEMD_UNIT_NAME}.service`
|
|
4640
|
+
);
|
|
4641
|
+
}
|
|
4642
|
+
function systemdUnit(inp) {
|
|
4643
|
+
return `[Unit]
|
|
4644
|
+
Description=Onklave Agent CLI Daemon
|
|
4645
|
+
Documentation=https://docs.onklave.app/cli/daemon
|
|
4646
|
+
After=network-online.target
|
|
4647
|
+
Wants=network-online.target
|
|
4648
|
+
|
|
4649
|
+
[Service]
|
|
4650
|
+
Type=simple
|
|
4651
|
+
ExecStart=${inp.nodePath} ${inp.cliPath} daemon
|
|
4652
|
+
ExecStop=${inp.nodePath} ${inp.cliPath} daemon drain
|
|
4653
|
+
Restart=on-failure
|
|
4654
|
+
RestartSec=10s
|
|
4655
|
+
StartLimitIntervalSec=300
|
|
4656
|
+
StartLimitBurst=5
|
|
4657
|
+
KillSignal=SIGTERM
|
|
4658
|
+
TimeoutStopSec=1830s
|
|
4659
|
+
|
|
4660
|
+
[Install]
|
|
4661
|
+
WantedBy=default.target
|
|
4662
|
+
`;
|
|
4663
|
+
}
|
|
4664
|
+
function linuxInstallPlan(inp) {
|
|
4665
|
+
const commands = [
|
|
4666
|
+
{ argv: ["systemctl", "--user", "daemon-reload"] },
|
|
4667
|
+
// enable --now starts + enables; enable (no --now) just enables for login.
|
|
4668
|
+
{
|
|
4669
|
+
argv: inp.start ? ["systemctl", "--user", "enable", "--now", SYSTEMD_UNIT_NAME] : ["systemctl", "--user", "enable", SYSTEMD_UNIT_NAME]
|
|
4670
|
+
}
|
|
4671
|
+
];
|
|
4672
|
+
return {
|
|
4673
|
+
platform: "linux",
|
|
4674
|
+
manager: "systemd (user)",
|
|
4675
|
+
serviceLabel: SYSTEMD_UNIT_NAME,
|
|
4676
|
+
files: [
|
|
4677
|
+
{ filePath: systemdUnitPath(inp.homeDir), contents: systemdUnit(inp) }
|
|
4678
|
+
],
|
|
4679
|
+
commands,
|
|
4680
|
+
ensureDirs: [],
|
|
4681
|
+
startsNow: inp.start
|
|
4682
|
+
};
|
|
4683
|
+
}
|
|
4684
|
+
function linuxUninstallPlan(homeDir) {
|
|
4685
|
+
return {
|
|
4686
|
+
platform: "linux",
|
|
4687
|
+
manager: "systemd (user)",
|
|
4688
|
+
serviceLabel: SYSTEMD_UNIT_NAME,
|
|
4689
|
+
removeFiles: [systemdUnitPath(homeDir)],
|
|
4690
|
+
commands: [
|
|
4691
|
+
{
|
|
4692
|
+
argv: ["systemctl", "--user", "disable", "--now", SYSTEMD_UNIT_NAME],
|
|
4693
|
+
optional: true
|
|
4694
|
+
},
|
|
4695
|
+
{ argv: ["systemctl", "--user", "daemon-reload"], optional: true }
|
|
4696
|
+
]
|
|
4697
|
+
};
|
|
4698
|
+
}
|
|
4699
|
+
function windowsTaskXmlPath(homeDir) {
|
|
4700
|
+
return path11.join(homeDir, ".config", "onklave", `${WINDOWS_TASK_NAME}.xml`);
|
|
4701
|
+
}
|
|
4702
|
+
function windowsLogPath(homeDir) {
|
|
4703
|
+
return path11.join(homeDir, ".config", "onklave", "logs", "daemon.log");
|
|
4704
|
+
}
|
|
4705
|
+
function windowsTaskXml(inp) {
|
|
4706
|
+
const user = inp.windowsUser ?? "";
|
|
4707
|
+
const logPath = windowsLogPath(inp.homeDir);
|
|
4708
|
+
const innerCmd = `""${inp.nodePath}" "${inp.cliPath}" daemon >> "${logPath}" 2>&1"`;
|
|
4709
|
+
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
4710
|
+
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
4711
|
+
<RegistrationInfo>
|
|
4712
|
+
<Description>Onklave Agent CLI Daemon</Description>
|
|
4713
|
+
<URI>\\${WINDOWS_TASK_NAME}</URI>
|
|
4714
|
+
</RegistrationInfo>
|
|
4715
|
+
<Triggers>
|
|
4716
|
+
<LogonTrigger>
|
|
4717
|
+
<Enabled>true</Enabled>
|
|
4718
|
+
<UserId>${xmlEscape(user)}</UserId>
|
|
4719
|
+
</LogonTrigger>
|
|
4720
|
+
</Triggers>
|
|
4721
|
+
<Principals>
|
|
4722
|
+
<Principal id="Author">
|
|
4723
|
+
<UserId>${xmlEscape(user)}</UserId>
|
|
4724
|
+
<LogonType>InteractiveToken</LogonType>
|
|
4725
|
+
<RunLevel>LeastPrivilege</RunLevel>
|
|
4726
|
+
</Principal>
|
|
4727
|
+
</Principals>
|
|
4728
|
+
<Settings>
|
|
4729
|
+
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
4730
|
+
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
4731
|
+
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
4732
|
+
<AllowHardTerminate>true</AllowHardTerminate>
|
|
4733
|
+
<StartWhenAvailable>true</StartWhenAvailable>
|
|
4734
|
+
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
|
4735
|
+
<IdleSettings>
|
|
4736
|
+
<StopOnIdleEnd>false</StopOnIdleEnd>
|
|
4737
|
+
<RestartOnIdle>false</RestartOnIdle>
|
|
4738
|
+
</IdleSettings>
|
|
4739
|
+
<AllowStartOnDemand>true</AllowStartOnDemand>
|
|
4740
|
+
<Enabled>true</Enabled>
|
|
4741
|
+
<Hidden>false</Hidden>
|
|
4742
|
+
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
|
4743
|
+
<RestartOnFailure>
|
|
4744
|
+
<Interval>PT1M</Interval>
|
|
4745
|
+
<Count>3</Count>
|
|
4746
|
+
</RestartOnFailure>
|
|
4747
|
+
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
4748
|
+
</Settings>
|
|
4749
|
+
<Actions Context="Author">
|
|
4750
|
+
<Exec>
|
|
4751
|
+
<Command>cmd.exe</Command>
|
|
4752
|
+
<Arguments>/c ${xmlEscape(innerCmd)}</Arguments>
|
|
4753
|
+
</Exec>
|
|
4754
|
+
</Actions>
|
|
4755
|
+
</Task>
|
|
4756
|
+
`;
|
|
4757
|
+
}
|
|
4758
|
+
function windowsInstallPlan(inp) {
|
|
4759
|
+
const xmlPath = windowsTaskXmlPath(inp.homeDir);
|
|
4760
|
+
const commands = [
|
|
4761
|
+
{
|
|
4762
|
+
argv: [
|
|
4763
|
+
"schtasks",
|
|
4764
|
+
"/create",
|
|
4765
|
+
"/tn",
|
|
4766
|
+
WINDOWS_TASK_NAME,
|
|
4767
|
+
"/xml",
|
|
4768
|
+
xmlPath,
|
|
4769
|
+
"/f"
|
|
4770
|
+
]
|
|
4771
|
+
}
|
|
4772
|
+
];
|
|
4773
|
+
if (inp.start) {
|
|
4774
|
+
commands.push({ argv: ["schtasks", "/run", "/tn", WINDOWS_TASK_NAME] });
|
|
4775
|
+
}
|
|
4776
|
+
return {
|
|
4777
|
+
platform: "win32",
|
|
4778
|
+
manager: "Task Scheduler",
|
|
4779
|
+
serviceLabel: WINDOWS_TASK_NAME,
|
|
4780
|
+
files: [{ filePath: xmlPath, contents: windowsTaskXml(inp) }],
|
|
4781
|
+
commands,
|
|
4782
|
+
ensureDirs: [path11.dirname(windowsLogPath(inp.homeDir))],
|
|
4783
|
+
startsNow: inp.start
|
|
4784
|
+
};
|
|
4785
|
+
}
|
|
4786
|
+
function windowsUninstallPlan(homeDir) {
|
|
4787
|
+
return {
|
|
4788
|
+
platform: "win32",
|
|
4789
|
+
manager: "Task Scheduler",
|
|
4790
|
+
serviceLabel: WINDOWS_TASK_NAME,
|
|
4791
|
+
removeFiles: [windowsTaskXmlPath(homeDir)],
|
|
4792
|
+
commands: [
|
|
4793
|
+
{ argv: ["schtasks", "/end", "/tn", WINDOWS_TASK_NAME], optional: true },
|
|
4794
|
+
{
|
|
4795
|
+
argv: ["schtasks", "/delete", "/tn", WINDOWS_TASK_NAME, "/f"],
|
|
4796
|
+
optional: true
|
|
4797
|
+
}
|
|
4798
|
+
]
|
|
4799
|
+
};
|
|
4800
|
+
}
|
|
4801
|
+
function systemCredFilePath(configDir) {
|
|
4802
|
+
return path11.join(configDir, "credentials.json");
|
|
4803
|
+
}
|
|
4804
|
+
function linuxSystemInstallPlan(inp) {
|
|
4805
|
+
const configDir = inp.systemConfigDir ?? "/var/lib/onklave";
|
|
4806
|
+
const unit = `[Unit]
|
|
4807
|
+
Description=Onklave Agent CLI Daemon
|
|
4808
|
+
Documentation=https://docs.onklave.app/cli/daemon
|
|
4809
|
+
After=network-online.target
|
|
4810
|
+
Wants=network-online.target
|
|
4811
|
+
|
|
4812
|
+
[Service]
|
|
4813
|
+
Type=simple
|
|
4814
|
+
User=${LINUX_SERVICE_USER}
|
|
4815
|
+
Group=${LINUX_SERVICE_USER}
|
|
4816
|
+
Environment=NODE_ENV=production
|
|
4817
|
+
Environment=ONKLAVE_CONFIG_DIR=${configDir}
|
|
4818
|
+
ExecStart=${inp.nodePath} ${inp.cliPath} daemon
|
|
4819
|
+
ExecStop=${inp.nodePath} ${inp.cliPath} daemon drain
|
|
4820
|
+
Restart=on-failure
|
|
4821
|
+
RestartSec=10s
|
|
4822
|
+
StartLimitIntervalSec=300
|
|
4823
|
+
StartLimitBurst=5
|
|
4824
|
+
KillSignal=SIGTERM
|
|
4825
|
+
TimeoutStopSec=1830s
|
|
4826
|
+
|
|
4827
|
+
# Hardening \u2014 the execution plane runs unprivileged (constitution \xA76).
|
|
4828
|
+
NoNewPrivileges=true
|
|
4829
|
+
ProtectSystem=strict
|
|
4830
|
+
ProtectHome=true
|
|
4831
|
+
PrivateTmp=true
|
|
4832
|
+
ReadWritePaths=${configDir}
|
|
4833
|
+
|
|
4834
|
+
[Install]
|
|
4835
|
+
WantedBy=multi-user.target
|
|
4836
|
+
`;
|
|
4837
|
+
const commands = [
|
|
4838
|
+
// Dedicated unprivileged service account. Idempotent: a re-run on a host
|
|
4839
|
+
// that already has the user fails here and is swallowed.
|
|
4840
|
+
{
|
|
4841
|
+
argv: [
|
|
4842
|
+
"useradd",
|
|
4843
|
+
"--system",
|
|
4844
|
+
"--no-create-home",
|
|
4845
|
+
"--shell",
|
|
4846
|
+
"/usr/sbin/nologin",
|
|
4847
|
+
LINUX_SERVICE_USER
|
|
4848
|
+
],
|
|
4849
|
+
optional: true
|
|
4850
|
+
},
|
|
4851
|
+
// chown after the cred file is written (executor writes files before
|
|
4852
|
+
// commands) so the service account owns the dir + credentials.
|
|
4853
|
+
{
|
|
4854
|
+
argv: [
|
|
4855
|
+
"chown",
|
|
4856
|
+
"-R",
|
|
4857
|
+
`${LINUX_SERVICE_USER}:${LINUX_SERVICE_USER}`,
|
|
4858
|
+
configDir
|
|
4859
|
+
]
|
|
4860
|
+
},
|
|
4861
|
+
{ argv: ["chmod", "700", configDir] },
|
|
4862
|
+
{ argv: ["systemctl", "daemon-reload"] },
|
|
4863
|
+
{
|
|
4864
|
+
argv: inp.start ? ["systemctl", "enable", "--now", SYSTEMD_UNIT_NAME] : ["systemctl", "enable", SYSTEMD_UNIT_NAME]
|
|
4865
|
+
}
|
|
4866
|
+
];
|
|
4867
|
+
return {
|
|
4868
|
+
platform: "linux",
|
|
4869
|
+
manager: "systemd (system)",
|
|
4870
|
+
serviceLabel: SYSTEMD_UNIT_NAME,
|
|
4871
|
+
files: [
|
|
4872
|
+
{ filePath: SYSTEMD_SYSTEM_UNIT_PATH, contents: unit, mode: 420 },
|
|
4873
|
+
{
|
|
4874
|
+
filePath: systemCredFilePath(configDir),
|
|
4875
|
+
contents: inp.credentialsJson ?? "",
|
|
4876
|
+
mode: 384
|
|
4877
|
+
}
|
|
4878
|
+
],
|
|
4879
|
+
commands,
|
|
4880
|
+
ensureDirs: [configDir],
|
|
4881
|
+
startsNow: inp.start
|
|
4882
|
+
};
|
|
4883
|
+
}
|
|
4884
|
+
function windowsSystemInstallPlan(inp) {
|
|
4885
|
+
const configDir = inp.systemConfigDir ?? "C:\\ProgramData\\Onklave";
|
|
4886
|
+
const logDir = path11.join(configDir, "logs");
|
|
4887
|
+
const svc = WINDOWS_TASK_NAME;
|
|
4888
|
+
const env = `NODE_ENV=production ONKLAVE_CONFIG_DIR=${configDir}`;
|
|
4889
|
+
const commands = [
|
|
4890
|
+
// Idempotent: drop any prior service before re-installing.
|
|
4891
|
+
{ argv: ["nssm", "stop", svc], optional: true },
|
|
4892
|
+
{ argv: ["nssm", "remove", svc, "confirm"], optional: true },
|
|
4893
|
+
{ argv: ["nssm", "install", svc, inp.nodePath, inp.cliPath, "daemon"] },
|
|
4894
|
+
{ argv: ["nssm", "set", svc, "DisplayName", "Onklave Agent CLI Daemon"] },
|
|
4895
|
+
{
|
|
4896
|
+
argv: [
|
|
4897
|
+
"nssm",
|
|
4898
|
+
"set",
|
|
4899
|
+
svc,
|
|
4900
|
+
"Description",
|
|
4901
|
+
"Long-running agent worker for the Onklave platform"
|
|
4902
|
+
]
|
|
4903
|
+
},
|
|
4904
|
+
{ argv: ["nssm", "set", svc, "Start", "SERVICE_AUTO_START"] },
|
|
4905
|
+
// Unprivileged built-in account (not LocalSystem) — spec §security.
|
|
4906
|
+
{ argv: ["nssm", "set", svc, "ObjectName", WINDOWS_SERVICE_ACCOUNT] },
|
|
4907
|
+
{ argv: ["nssm", "set", svc, "AppEnvironmentExtra", env] },
|
|
4908
|
+
{
|
|
4909
|
+
argv: [
|
|
4910
|
+
"nssm",
|
|
4911
|
+
"set",
|
|
4912
|
+
svc,
|
|
4913
|
+
"AppStdout",
|
|
4914
|
+
path11.join(logDir, "daemon.out.log")
|
|
4915
|
+
]
|
|
4916
|
+
},
|
|
4917
|
+
{
|
|
4918
|
+
argv: [
|
|
4919
|
+
"nssm",
|
|
4920
|
+
"set",
|
|
4921
|
+
svc,
|
|
4922
|
+
"AppStderr",
|
|
4923
|
+
path11.join(logDir, "daemon.err.log")
|
|
4924
|
+
]
|
|
4925
|
+
},
|
|
4926
|
+
{ argv: ["nssm", "set", svc, "AppExit", "Default", "Restart"] },
|
|
4927
|
+
{ argv: ["nssm", "set", svc, "AppRestartDelay", "10000"] },
|
|
4928
|
+
// The daemon writes state + pid into the config dir and reads credentials
|
|
4929
|
+
// there, so LocalService needs modify on it.
|
|
4930
|
+
{
|
|
4931
|
+
argv: [
|
|
4932
|
+
"icacls",
|
|
4933
|
+
configDir,
|
|
4934
|
+
"/grant",
|
|
4935
|
+
`${WINDOWS_SERVICE_ACCOUNT}:(OI)(CI)(M)`
|
|
4936
|
+
]
|
|
4937
|
+
}
|
|
4938
|
+
];
|
|
4939
|
+
if (inp.start) {
|
|
4940
|
+
commands.push({ argv: ["nssm", "start", svc] });
|
|
4941
|
+
}
|
|
4942
|
+
return {
|
|
4943
|
+
platform: "win32",
|
|
4944
|
+
manager: "NSSM (service)",
|
|
4945
|
+
serviceLabel: svc,
|
|
4946
|
+
files: [
|
|
4947
|
+
{
|
|
4948
|
+
filePath: systemCredFilePath(configDir),
|
|
4949
|
+
contents: inp.credentialsJson ?? ""
|
|
4950
|
+
}
|
|
4951
|
+
],
|
|
4952
|
+
commands,
|
|
4953
|
+
ensureDirs: [logDir],
|
|
4954
|
+
startsNow: inp.start
|
|
4955
|
+
};
|
|
4956
|
+
}
|
|
4957
|
+
function linuxSystemUninstallPlan(configDir) {
|
|
4958
|
+
return {
|
|
4959
|
+
platform: "linux",
|
|
4960
|
+
manager: "systemd (system)",
|
|
4961
|
+
serviceLabel: SYSTEMD_UNIT_NAME,
|
|
4962
|
+
removeFiles: [SYSTEMD_SYSTEM_UNIT_PATH, systemCredFilePath(configDir)],
|
|
4963
|
+
commands: [
|
|
4964
|
+
{
|
|
4965
|
+
argv: ["systemctl", "disable", "--now", SYSTEMD_UNIT_NAME],
|
|
4966
|
+
optional: true
|
|
4967
|
+
},
|
|
4968
|
+
{ argv: ["systemctl", "daemon-reload"], optional: true }
|
|
4969
|
+
]
|
|
4970
|
+
};
|
|
4971
|
+
}
|
|
4972
|
+
function windowsSystemUninstallPlan(configDir) {
|
|
4973
|
+
return {
|
|
4974
|
+
platform: "win32",
|
|
4975
|
+
manager: "NSSM (service)",
|
|
4976
|
+
serviceLabel: WINDOWS_TASK_NAME,
|
|
4977
|
+
removeFiles: [systemCredFilePath(configDir)],
|
|
4978
|
+
commands: [
|
|
4979
|
+
{ argv: ["nssm", "stop", WINDOWS_TASK_NAME], optional: true },
|
|
4980
|
+
{
|
|
4981
|
+
argv: ["nssm", "remove", WINDOWS_TASK_NAME, "confirm"],
|
|
4982
|
+
optional: true
|
|
4983
|
+
}
|
|
4984
|
+
]
|
|
4985
|
+
};
|
|
4986
|
+
}
|
|
4987
|
+
function buildInstallPlan(inp) {
|
|
4988
|
+
if (inp.system) {
|
|
4989
|
+
switch (inp.platform) {
|
|
4990
|
+
case "linux":
|
|
4991
|
+
return linuxSystemInstallPlan(inp);
|
|
4992
|
+
case "win32":
|
|
4993
|
+
return windowsSystemInstallPlan(inp);
|
|
4994
|
+
case "darwin":
|
|
4995
|
+
throw new Error(
|
|
4996
|
+
"--system is not supported on macOS; use per-user `onklave daemon install`."
|
|
4997
|
+
);
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
switch (inp.platform) {
|
|
5001
|
+
case "darwin":
|
|
5002
|
+
return macInstallPlan(inp);
|
|
5003
|
+
case "linux":
|
|
5004
|
+
return linuxInstallPlan(inp);
|
|
5005
|
+
case "win32":
|
|
5006
|
+
return windowsInstallPlan(inp);
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
function buildUninstallPlan(inp) {
|
|
5010
|
+
if (inp.system) {
|
|
5011
|
+
const configDir = inp.systemConfigDir ?? "";
|
|
5012
|
+
switch (inp.platform) {
|
|
5013
|
+
case "linux":
|
|
5014
|
+
return linuxSystemUninstallPlan(configDir);
|
|
5015
|
+
case "win32":
|
|
5016
|
+
return windowsSystemUninstallPlan(configDir);
|
|
5017
|
+
case "darwin":
|
|
5018
|
+
throw new Error(
|
|
5019
|
+
"--system is not supported on macOS; use per-user `onklave daemon uninstall`."
|
|
5020
|
+
);
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
switch (inp.platform) {
|
|
5024
|
+
case "darwin":
|
|
5025
|
+
return macUninstallPlan(inp.homeDir, inp.uid);
|
|
5026
|
+
case "linux":
|
|
5027
|
+
return linuxUninstallPlan(inp.homeDir);
|
|
5028
|
+
case "win32":
|
|
5029
|
+
return windowsUninstallPlan(inp.homeDir);
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
function runInstallPlan(plan) {
|
|
5033
|
+
for (const dir of plan.ensureDirs) {
|
|
5034
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
5035
|
+
}
|
|
5036
|
+
for (const file of plan.files) {
|
|
5037
|
+
fs10.mkdirSync(path11.dirname(file.filePath), { recursive: true });
|
|
5038
|
+
fs10.writeFileSync(
|
|
5039
|
+
file.filePath,
|
|
5040
|
+
file.contents,
|
|
5041
|
+
file.mode != null ? { mode: file.mode } : {}
|
|
5042
|
+
);
|
|
5043
|
+
}
|
|
5044
|
+
runCommands(plan.commands);
|
|
5045
|
+
}
|
|
5046
|
+
function runUninstallPlan(plan) {
|
|
5047
|
+
runCommands(plan.commands);
|
|
5048
|
+
for (const filePath of plan.removeFiles) {
|
|
5049
|
+
try {
|
|
5050
|
+
fs10.unlinkSync(filePath);
|
|
5051
|
+
} catch {
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
function runCommands(commands) {
|
|
5056
|
+
for (const cmd of commands) {
|
|
5057
|
+
const [bin, ...rest] = cmd.argv;
|
|
5058
|
+
try {
|
|
5059
|
+
execFileSync(bin, rest, { stdio: "pipe", timeout: 3e4 });
|
|
5060
|
+
} catch (err) {
|
|
5061
|
+
if (cmd.optional) continue;
|
|
5062
|
+
const detail = err.stderr?.toString().trim();
|
|
5063
|
+
throw new Error(
|
|
5064
|
+
`Command failed: ${cmd.argv.join(" ")}${detail ? `
|
|
5065
|
+
${detail}` : ""}`
|
|
5066
|
+
);
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
5069
|
+
}
|
|
5070
|
+
|
|
5071
|
+
// _apps/@onklave/agent-cli/src/services/daemon-elevation.service.ts
|
|
5072
|
+
import { execSync as execSync2, execFileSync as execFileSync2 } from "child_process";
|
|
5073
|
+
function isElevated(platform2 = process.platform) {
|
|
5074
|
+
if (platform2 === "win32") {
|
|
5075
|
+
try {
|
|
5076
|
+
execSync2("net session", { stdio: "ignore", timeout: 5e3 });
|
|
5077
|
+
return true;
|
|
5078
|
+
} catch {
|
|
5079
|
+
return false;
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
return typeof process.getuid === "function" && process.getuid() === 0;
|
|
5083
|
+
}
|
|
5084
|
+
function buildSudoReExec(nodePath, args) {
|
|
5085
|
+
return ["sudo", nodePath, ...args];
|
|
5086
|
+
}
|
|
5087
|
+
function psQuote(value) {
|
|
5088
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
5089
|
+
}
|
|
5090
|
+
function buildRunAsCommand(nodePath, args) {
|
|
5091
|
+
const argList = args.map(psQuote).join(",");
|
|
5092
|
+
const script = `Start-Process -FilePath ${psQuote(nodePath)} ` + (args.length ? `-ArgumentList ${argList} ` : "") + `-Verb RunAs -Wait`;
|
|
5093
|
+
return ["powershell", "-NoProfile", "-NonInteractive", "-Command", script];
|
|
5094
|
+
}
|
|
5095
|
+
function runElevated(argv) {
|
|
5096
|
+
const [bin, ...rest] = argv;
|
|
5097
|
+
execFileSync2(bin, rest, { stdio: "inherit" });
|
|
5098
|
+
}
|
|
5099
|
+
|
|
4431
5100
|
// _apps/@onklave/agent-cli/src/commands/daemon.command.ts
|
|
4432
5101
|
var UNIT_FLAGS = {
|
|
4433
5102
|
"--emit-systemd-unit": "systemd",
|
|
@@ -4446,10 +5115,12 @@ async function daemonCommand(args) {
|
|
|
4446
5115
|
if (sub === "status") return daemonStatus();
|
|
4447
5116
|
if (sub === "stop" || sub === "drain") return daemonStop();
|
|
4448
5117
|
if (sub === "update") return daemonUpdate();
|
|
5118
|
+
if (sub === "install") return daemonInstall(args.slice(1));
|
|
5119
|
+
if (sub === "uninstall") return daemonUninstall(args.slice(1));
|
|
4449
5120
|
if (sub && !sub.startsWith("--")) {
|
|
4450
5121
|
console.error(`Unknown subcommand: ${sub}`);
|
|
4451
5122
|
console.error(
|
|
4452
|
-
"Usage: onklave daemon [status|stop|drain|update] | --emit-systemd-unit | --emit-launchd-plist | --emit-nssm-script"
|
|
5123
|
+
"Usage: onklave daemon [install|uninstall|status|stop|drain|update] [--system] [--no-start] | --emit-systemd-unit | --emit-launchd-plist | --emit-nssm-script"
|
|
4453
5124
|
);
|
|
4454
5125
|
process.exitCode = 1;
|
|
4455
5126
|
return;
|
|
@@ -4628,12 +5299,12 @@ async function daemonStart() {
|
|
|
4628
5299
|
allowAutoUpgrade: !!process.env["ONKLAVE_ALLOW_AUTO_UPGRADE"],
|
|
4629
5300
|
currentVersion: readPackageVersion() ?? "0.0.0",
|
|
4630
5301
|
runInstall: async () => {
|
|
4631
|
-
const { execFileSync } = __require("child_process");
|
|
4632
|
-
|
|
5302
|
+
const { execFileSync: execFileSync3 } = __require("child_process");
|
|
5303
|
+
execFileSync3("npm", ["install", "-g", "@onklave/agent-cli@latest"], {
|
|
4633
5304
|
stdio: "pipe",
|
|
4634
5305
|
timeout: 18e4
|
|
4635
5306
|
});
|
|
4636
|
-
const v =
|
|
5307
|
+
const v = execFileSync3(
|
|
4637
5308
|
"npm",
|
|
4638
5309
|
["view", "@onklave/agent-cli@latest", "version"],
|
|
4639
5310
|
{ stdio: "pipe", timeout: 3e4 }
|
|
@@ -4690,6 +5361,11 @@ async function daemonStart() {
|
|
|
4690
5361
|
console.log(
|
|
4691
5362
|
`Daemon online. machineId=${creds.machineId} pid=${process.pid}. Press Ctrl-C to drain.`
|
|
4692
5363
|
);
|
|
5364
|
+
if (process.stdout.isTTY) {
|
|
5365
|
+
console.log(
|
|
5366
|
+
"Tip: run `onklave daemon install` to run this as a background service that auto-starts at login."
|
|
5367
|
+
);
|
|
5368
|
+
}
|
|
4693
5369
|
await new Promise(() => void 0);
|
|
4694
5370
|
}
|
|
4695
5371
|
async function daemonStatus() {
|
|
@@ -4770,6 +5446,261 @@ async function daemonUpdate() {
|
|
|
4770
5446
|
"Self-replace via npm provenance verification (spec \xA76.3) is deferred \u2014 see V6-CLI-002 Phase 7 in the kanban."
|
|
4771
5447
|
);
|
|
4772
5448
|
}
|
|
5449
|
+
function currentServicePlatform() {
|
|
5450
|
+
const p = process.platform;
|
|
5451
|
+
return p === "darwin" || p === "linux" || p === "win32" ? p : null;
|
|
5452
|
+
}
|
|
5453
|
+
function resolveCliPath() {
|
|
5454
|
+
return __require.main?.filename ?? process.argv[1];
|
|
5455
|
+
}
|
|
5456
|
+
function windowsPrincipalUser() {
|
|
5457
|
+
const domain = process.env["USERDOMAIN"];
|
|
5458
|
+
const user = process.env["USERNAME"] ?? "";
|
|
5459
|
+
return domain ? `${domain}\\${user}` : user;
|
|
5460
|
+
}
|
|
5461
|
+
function systemConfigDir(platform2) {
|
|
5462
|
+
if (platform2 === "win32") {
|
|
5463
|
+
return path12.join(
|
|
5464
|
+
process.env["ProgramData"] || "C:\\ProgramData",
|
|
5465
|
+
"Onklave"
|
|
5466
|
+
);
|
|
5467
|
+
}
|
|
5468
|
+
return "/var/lib/onklave";
|
|
5469
|
+
}
|
|
5470
|
+
function getFlagValue(args, flag) {
|
|
5471
|
+
const i = args.indexOf(flag);
|
|
5472
|
+
return i >= 0 && i + 1 < args.length ? args[i + 1] : void 0;
|
|
5473
|
+
}
|
|
5474
|
+
async function daemonInstall(args) {
|
|
5475
|
+
const platform2 = currentServicePlatform();
|
|
5476
|
+
if (!platform2) {
|
|
5477
|
+
console.error(`Unsupported platform: ${process.platform}`);
|
|
5478
|
+
process.exitCode = 1;
|
|
5479
|
+
return;
|
|
5480
|
+
}
|
|
5481
|
+
const start = !args.includes("--no-start");
|
|
5482
|
+
if (args.includes("--system")) {
|
|
5483
|
+
return daemonInstallSystem(platform2, args, start);
|
|
5484
|
+
}
|
|
5485
|
+
return daemonInstallPerUser(platform2, start);
|
|
5486
|
+
}
|
|
5487
|
+
async function daemonInstallPerUser(platform2, start) {
|
|
5488
|
+
const creds = await new AuthService().getCredentials();
|
|
5489
|
+
if (!creds?.deviceToken || !creds?.machineId) {
|
|
5490
|
+
console.error(
|
|
5491
|
+
"Error: machine is not registered. Run: onklave register --token <bootstrap>"
|
|
5492
|
+
);
|
|
5493
|
+
process.exitCode = 1;
|
|
5494
|
+
return;
|
|
5495
|
+
}
|
|
5496
|
+
const plan = buildInstallPlan({
|
|
5497
|
+
platform: platform2,
|
|
5498
|
+
nodePath: process.execPath,
|
|
5499
|
+
cliPath: resolveCliPath(),
|
|
5500
|
+
homeDir: os7.homedir(),
|
|
5501
|
+
uid: process.getuid ? String(process.getuid()) : void 0,
|
|
5502
|
+
windowsUser: platform2 === "win32" ? windowsPrincipalUser() : void 0,
|
|
5503
|
+
start
|
|
5504
|
+
});
|
|
5505
|
+
try {
|
|
5506
|
+
runInstallPlan(plan);
|
|
5507
|
+
} catch (err) {
|
|
5508
|
+
console.error(`Install failed: ${err.message}`);
|
|
5509
|
+
process.exitCode = 1;
|
|
5510
|
+
return;
|
|
5511
|
+
}
|
|
5512
|
+
console.log(
|
|
5513
|
+
`Installed Onklave daemon as a ${plan.manager} service (${plan.serviceLabel}), running as ${os7.userInfo().username}.`
|
|
5514
|
+
);
|
|
5515
|
+
console.log(
|
|
5516
|
+
plan.startsNow ? "Started now and set to auto-start at login." : "Registered to auto-start at next login (not started now \u2014 re-run without --no-start to start immediately)."
|
|
5517
|
+
);
|
|
5518
|
+
console.log("Check it with: onklave daemon status");
|
|
5519
|
+
}
|
|
5520
|
+
async function daemonInstallSystem(platform2, args, start) {
|
|
5521
|
+
if (platform2 === "darwin") {
|
|
5522
|
+
console.error(
|
|
5523
|
+
"--system is not supported on macOS. Use the per-user installer instead:"
|
|
5524
|
+
);
|
|
5525
|
+
console.error(" onklave daemon install");
|
|
5526
|
+
process.exitCode = 1;
|
|
5527
|
+
return;
|
|
5528
|
+
}
|
|
5529
|
+
const credsFile = getFlagValue(args, "--creds-file");
|
|
5530
|
+
if (credsFile) {
|
|
5531
|
+
let credentialsJson2;
|
|
5532
|
+
try {
|
|
5533
|
+
credentialsJson2 = fs11.readFileSync(credsFile, "utf8");
|
|
5534
|
+
} catch (err) {
|
|
5535
|
+
console.error(
|
|
5536
|
+
`Cannot read handed-over credentials: ${err.message}`
|
|
5537
|
+
);
|
|
5538
|
+
process.exitCode = 1;
|
|
5539
|
+
return;
|
|
5540
|
+
}
|
|
5541
|
+
return runSystemInstall(platform2, credentialsJson2, start);
|
|
5542
|
+
}
|
|
5543
|
+
const creds = await new AuthService().getCredentials();
|
|
5544
|
+
if (!creds?.deviceToken || !creds?.machineId) {
|
|
5545
|
+
console.error(
|
|
5546
|
+
"Error: machine is not registered. Run: onklave register --token <bootstrap>"
|
|
5547
|
+
);
|
|
5548
|
+
process.exitCode = 1;
|
|
5549
|
+
return;
|
|
5550
|
+
}
|
|
5551
|
+
const credentialsJson = JSON.stringify(creds);
|
|
5552
|
+
if (platform2 === "win32") {
|
|
5553
|
+
if (isElevated("win32")) {
|
|
5554
|
+
return runSystemInstall("win32", credentialsJson, start);
|
|
5555
|
+
}
|
|
5556
|
+
console.log("Requesting elevation (a UAC prompt will appear)\u2026");
|
|
5557
|
+
const reArgs = [
|
|
5558
|
+
"daemon",
|
|
5559
|
+
"install",
|
|
5560
|
+
"--system",
|
|
5561
|
+
...start ? [] : ["--no-start"]
|
|
5562
|
+
];
|
|
5563
|
+
try {
|
|
5564
|
+
runElevated(
|
|
5565
|
+
buildRunAsCommand(process.execPath, [resolveCliPath(), ...reArgs])
|
|
5566
|
+
);
|
|
5567
|
+
} catch (err) {
|
|
5568
|
+
console.error(`Elevation failed: ${err.message}`);
|
|
5569
|
+
process.exitCode = 1;
|
|
5570
|
+
return;
|
|
5571
|
+
}
|
|
5572
|
+
console.log(
|
|
5573
|
+
"Elevated install completed. Verify with: onklave daemon status"
|
|
5574
|
+
);
|
|
5575
|
+
return;
|
|
5576
|
+
}
|
|
5577
|
+
if (isElevated("linux")) {
|
|
5578
|
+
return runSystemInstall("linux", credentialsJson, start);
|
|
5579
|
+
}
|
|
5580
|
+
const tmpDir = fs11.mkdtempSync(path12.join(os7.tmpdir(), "onklave-creds-"));
|
|
5581
|
+
const tmpCreds = path12.join(tmpDir, "credentials.json");
|
|
5582
|
+
fs11.writeFileSync(tmpCreds, credentialsJson, { mode: 384 });
|
|
5583
|
+
try {
|
|
5584
|
+
const reArgs = [
|
|
5585
|
+
"daemon",
|
|
5586
|
+
"install",
|
|
5587
|
+
"--system",
|
|
5588
|
+
...start ? [] : ["--no-start"],
|
|
5589
|
+
"--creds-file",
|
|
5590
|
+
tmpCreds
|
|
5591
|
+
];
|
|
5592
|
+
console.log("Requesting elevation via sudo\u2026");
|
|
5593
|
+
runElevated(
|
|
5594
|
+
buildSudoReExec(process.execPath, [resolveCliPath(), ...reArgs])
|
|
5595
|
+
);
|
|
5596
|
+
} catch (err) {
|
|
5597
|
+
console.error(`Elevation failed: ${err.message}`);
|
|
5598
|
+
process.exitCode = 1;
|
|
5599
|
+
} finally {
|
|
5600
|
+
try {
|
|
5601
|
+
fs11.rmSync(tmpDir, { recursive: true, force: true });
|
|
5602
|
+
} catch {
|
|
5603
|
+
}
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
async function runSystemInstall(platform2, credentialsJson, start) {
|
|
5607
|
+
if (platform2 === "win32" && !commandExists("nssm")) {
|
|
5608
|
+
console.error(
|
|
5609
|
+
"NSSM is required to supervise a Node process as a Windows service but was not found on PATH."
|
|
5610
|
+
);
|
|
5611
|
+
console.error("Install it (e.g. `choco install nssm`) and re-run.");
|
|
5612
|
+
process.exitCode = 1;
|
|
5613
|
+
return;
|
|
5614
|
+
}
|
|
5615
|
+
if (platform2 === "linux" && !commandExists("useradd")) {
|
|
5616
|
+
console.error(
|
|
5617
|
+
"`useradd` was not found \u2014 cannot create the dedicated service account."
|
|
5618
|
+
);
|
|
5619
|
+
process.exitCode = 1;
|
|
5620
|
+
return;
|
|
5621
|
+
}
|
|
5622
|
+
const configDir = systemConfigDir(platform2);
|
|
5623
|
+
let plan;
|
|
5624
|
+
try {
|
|
5625
|
+
plan = buildInstallPlan({
|
|
5626
|
+
platform: platform2,
|
|
5627
|
+
nodePath: process.execPath,
|
|
5628
|
+
cliPath: resolveCliPath(),
|
|
5629
|
+
homeDir: os7.homedir(),
|
|
5630
|
+
start,
|
|
5631
|
+
system: true,
|
|
5632
|
+
systemConfigDir: configDir,
|
|
5633
|
+
credentialsJson
|
|
5634
|
+
});
|
|
5635
|
+
runInstallPlan(plan);
|
|
5636
|
+
} catch (err) {
|
|
5637
|
+
console.error(`Install failed: ${err.message}`);
|
|
5638
|
+
process.exitCode = 1;
|
|
5639
|
+
return;
|
|
5640
|
+
}
|
|
5641
|
+
console.log(
|
|
5642
|
+
`Installed Onklave daemon as a ${plan.manager} service (${plan.serviceLabel}).`
|
|
5643
|
+
);
|
|
5644
|
+
console.log(
|
|
5645
|
+
`Config dir: ${configDir} \u2014 credentials copied there for the service account.`
|
|
5646
|
+
);
|
|
5647
|
+
console.log(
|
|
5648
|
+
plan.startsNow ? "Started now and enabled at boot." : "Enabled at boot (not started now \u2014 re-run without --no-start to start immediately)."
|
|
5649
|
+
);
|
|
5650
|
+
console.log(
|
|
5651
|
+
platform2 === "linux" ? "Logs: journalctl -u onklave-daemon -f" : `Logs: ${path12.join(configDir, "logs")}`
|
|
5652
|
+
);
|
|
5653
|
+
}
|
|
5654
|
+
async function daemonUninstall(args) {
|
|
5655
|
+
const platform2 = currentServicePlatform();
|
|
5656
|
+
if (!platform2) {
|
|
5657
|
+
console.error(`Unsupported platform: ${process.platform}`);
|
|
5658
|
+
process.exitCode = 1;
|
|
5659
|
+
return;
|
|
5660
|
+
}
|
|
5661
|
+
if (args.includes("--system")) {
|
|
5662
|
+
if (platform2 === "darwin") {
|
|
5663
|
+
console.error(
|
|
5664
|
+
"--system is not supported on macOS. Use: onklave daemon uninstall"
|
|
5665
|
+
);
|
|
5666
|
+
process.exitCode = 1;
|
|
5667
|
+
return;
|
|
5668
|
+
}
|
|
5669
|
+
if (!isElevated(platform2)) {
|
|
5670
|
+
const reArgs = ["daemon", "uninstall", "--system"];
|
|
5671
|
+
const cmd = platform2 === "win32" ? buildRunAsCommand(process.execPath, [resolveCliPath(), ...reArgs]) : buildSudoReExec(process.execPath, [resolveCliPath(), ...reArgs]);
|
|
5672
|
+
console.log("Requesting elevation\u2026");
|
|
5673
|
+
try {
|
|
5674
|
+
runElevated(cmd);
|
|
5675
|
+
} catch (err) {
|
|
5676
|
+
console.error(`Elevation failed: ${err.message}`);
|
|
5677
|
+
process.exitCode = 1;
|
|
5678
|
+
}
|
|
5679
|
+
return;
|
|
5680
|
+
}
|
|
5681
|
+
const configDir = systemConfigDir(platform2);
|
|
5682
|
+
const plan2 = buildUninstallPlan({
|
|
5683
|
+
platform: platform2,
|
|
5684
|
+
homeDir: os7.homedir(),
|
|
5685
|
+
system: true,
|
|
5686
|
+
systemConfigDir: configDir
|
|
5687
|
+
});
|
|
5688
|
+
runUninstallPlan(plan2);
|
|
5689
|
+
console.log(
|
|
5690
|
+
`Removed Onklave daemon ${plan2.manager} service (${plan2.serviceLabel}).`
|
|
5691
|
+
);
|
|
5692
|
+
return;
|
|
5693
|
+
}
|
|
5694
|
+
const plan = buildUninstallPlan({
|
|
5695
|
+
platform: platform2,
|
|
5696
|
+
homeDir: os7.homedir(),
|
|
5697
|
+
uid: process.getuid ? String(process.getuid()) : void 0
|
|
5698
|
+
});
|
|
5699
|
+
runUninstallPlan(plan);
|
|
5700
|
+
console.log(
|
|
5701
|
+
`Removed Onklave daemon ${plan.manager} service (${plan.serviceLabel}).`
|
|
5702
|
+
);
|
|
5703
|
+
}
|
|
4773
5704
|
async function daemonStop() {
|
|
4774
5705
|
const pidFile = defaultPidFilePath();
|
|
4775
5706
|
const pid = readPid(pidFile);
|
|
@@ -4802,7 +5733,7 @@ function transitionToAction(next) {
|
|
|
4802
5733
|
}
|
|
4803
5734
|
function readPid(pidFile) {
|
|
4804
5735
|
try {
|
|
4805
|
-
const raw =
|
|
5736
|
+
const raw = fs11.readFileSync(pidFile, "utf8").trim();
|
|
4806
5737
|
const parsed = Number.parseInt(raw, 10);
|
|
4807
5738
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
4808
5739
|
} catch {
|
|
@@ -4810,19 +5741,19 @@ function readPid(pidFile) {
|
|
|
4810
5741
|
}
|
|
4811
5742
|
}
|
|
4812
5743
|
function writePid(pidFile) {
|
|
4813
|
-
const dir =
|
|
4814
|
-
|
|
5744
|
+
const dir = path12.dirname(pidFile);
|
|
5745
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
4815
5746
|
try {
|
|
4816
|
-
if (
|
|
4817
|
-
|
|
5747
|
+
if (fs11.statSync(pidFile).isDirectory()) {
|
|
5748
|
+
fs11.rmSync(pidFile, { recursive: true, force: true });
|
|
4818
5749
|
}
|
|
4819
5750
|
} catch {
|
|
4820
5751
|
}
|
|
4821
|
-
|
|
5752
|
+
fs11.writeFileSync(pidFile, String(process.pid), { mode: 384 });
|
|
4822
5753
|
}
|
|
4823
5754
|
function removePid(pidFile) {
|
|
4824
5755
|
try {
|
|
4825
|
-
|
|
5756
|
+
fs11.unlinkSync(pidFile);
|
|
4826
5757
|
} catch {
|
|
4827
5758
|
}
|
|
4828
5759
|
}
|
|
@@ -4866,14 +5797,14 @@ var COMMANDS = {
|
|
|
4866
5797
|
};
|
|
4867
5798
|
|
|
4868
5799
|
// _apps/@onklave/agent-cli/src/services/update-notifier.service.ts
|
|
4869
|
-
import * as
|
|
4870
|
-
import * as
|
|
5800
|
+
import * as fs12 from "node:fs";
|
|
5801
|
+
import * as path13 from "node:path";
|
|
4871
5802
|
import * as os8 from "node:os";
|
|
4872
5803
|
import { createInterface } from "node:readline/promises";
|
|
4873
5804
|
import { spawnSync } from "node:child_process";
|
|
4874
5805
|
var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
4875
5806
|
var FETCH_DEADLINE_MS = 1500;
|
|
4876
|
-
var CACHE_PATH =
|
|
5807
|
+
var CACHE_PATH = path13.join(
|
|
4877
5808
|
os8.homedir(),
|
|
4878
5809
|
".config",
|
|
4879
5810
|
"onklave",
|
|
@@ -4984,16 +5915,16 @@ function isCacheFresh(cache, nowMs) {
|
|
|
4984
5915
|
}
|
|
4985
5916
|
function readCache() {
|
|
4986
5917
|
try {
|
|
4987
|
-
if (!
|
|
4988
|
-
return JSON.parse(
|
|
5918
|
+
if (!fs12.existsSync(CACHE_PATH)) return {};
|
|
5919
|
+
return JSON.parse(fs12.readFileSync(CACHE_PATH, "utf8"));
|
|
4989
5920
|
} catch {
|
|
4990
5921
|
return {};
|
|
4991
5922
|
}
|
|
4992
5923
|
}
|
|
4993
5924
|
function writeCache(cache) {
|
|
4994
5925
|
try {
|
|
4995
|
-
|
|
4996
|
-
|
|
5926
|
+
fs12.mkdirSync(path13.dirname(CACHE_PATH), { recursive: true, mode: 448 });
|
|
5927
|
+
fs12.writeFileSync(CACHE_PATH, JSON.stringify(cache, null, 2), "utf8");
|
|
4997
5928
|
} catch {
|
|
4998
5929
|
}
|
|
4999
5930
|
}
|