@intentic/cli 1.0.2

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.
Files changed (174) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -0
  3. package/dist/add-app/add-app.command.d.ts +3 -0
  4. package/dist/add-app/add-app.command.d.ts.map +1 -0
  5. package/dist/add-app/add-app.command.js +50 -0
  6. package/dist/add-app/add-app.command.js.map +1 -0
  7. package/dist/adopt/adopt.command.d.ts +3 -0
  8. package/dist/adopt/adopt.command.d.ts.map +1 -0
  9. package/dist/adopt/adopt.command.js +77 -0
  10. package/dist/adopt/adopt.command.js.map +1 -0
  11. package/dist/adopt/adopt.d.ts +21 -0
  12. package/dist/adopt/adopt.d.ts.map +1 -0
  13. package/dist/adopt/adopt.js +31 -0
  14. package/dist/adopt/adopt.js.map +1 -0
  15. package/dist/app.d.ts +2 -0
  16. package/dist/app.d.ts.map +1 -0
  17. package/dist/app.js +46 -0
  18. package/dist/app.js.map +1 -0
  19. package/dist/apply/access.d.ts +16 -0
  20. package/dist/apply/access.d.ts.map +1 -0
  21. package/dist/apply/access.js +72 -0
  22. package/dist/apply/access.js.map +1 -0
  23. package/dist/apply/apply-lock.d.ts +12 -0
  24. package/dist/apply/apply-lock.d.ts.map +1 -0
  25. package/dist/apply/apply-lock.js +102 -0
  26. package/dist/apply/apply-lock.js.map +1 -0
  27. package/dist/apply/apply.command.d.ts +3 -0
  28. package/dist/apply/apply.command.d.ts.map +1 -0
  29. package/dist/apply/apply.command.js +144 -0
  30. package/dist/apply/apply.command.js.map +1 -0
  31. package/dist/apply/migrate.d.ts +20 -0
  32. package/dist/apply/migrate.d.ts.map +1 -0
  33. package/dist/apply/migrate.js +112 -0
  34. package/dist/apply/migrate.js.map +1 -0
  35. package/dist/cli.d.ts +3 -0
  36. package/dist/cli.d.ts.map +1 -0
  37. package/dist/cli.js +5 -0
  38. package/dist/cli.js.map +1 -0
  39. package/dist/demo.d.ts +2 -0
  40. package/dist/demo.d.ts.map +1 -0
  41. package/dist/demo.js +281 -0
  42. package/dist/demo.js.map +1 -0
  43. package/dist/deployments/deployments.command.d.ts +3 -0
  44. package/dist/deployments/deployments.command.d.ts.map +1 -0
  45. package/dist/deployments/deployments.command.js +19 -0
  46. package/dist/deployments/deployments.command.js.map +1 -0
  47. package/dist/deployments/deployments.d.ts +14 -0
  48. package/dist/deployments/deployments.d.ts.map +1 -0
  49. package/dist/deployments/deployments.js +83 -0
  50. package/dist/deployments/deployments.js.map +1 -0
  51. package/dist/env.config.d.ts +25 -0
  52. package/dist/env.config.d.ts.map +1 -0
  53. package/dist/env.config.js +31 -0
  54. package/dist/env.config.js.map +1 -0
  55. package/dist/host-ssh-tunnel/host-ssh-tunnel.command.d.ts +3 -0
  56. package/dist/host-ssh-tunnel/host-ssh-tunnel.command.d.ts.map +1 -0
  57. package/dist/host-ssh-tunnel/host-ssh-tunnel.command.js +39 -0
  58. package/dist/host-ssh-tunnel/host-ssh-tunnel.command.js.map +1 -0
  59. package/dist/host-ssh-tunnel/host-ssh-tunnel.d.ts +14 -0
  60. package/dist/host-ssh-tunnel/host-ssh-tunnel.d.ts.map +1 -0
  61. package/dist/host-ssh-tunnel/host-ssh-tunnel.js +21 -0
  62. package/dist/host-ssh-tunnel/host-ssh-tunnel.js.map +1 -0
  63. package/dist/index.d.ts +5 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +5 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/init/init.command.d.ts +3 -0
  68. package/dist/init/init.command.d.ts.map +1 -0
  69. package/dist/init/init.command.js +45 -0
  70. package/dist/init/init.command.js.map +1 -0
  71. package/dist/init/init.d.ts +7 -0
  72. package/dist/init/init.d.ts.map +1 -0
  73. package/dist/init/init.js +39 -0
  74. package/dist/init/init.js.map +1 -0
  75. package/dist/init/scaffold-app.d.ts +2 -0
  76. package/dist/init/scaffold-app.d.ts.map +1 -0
  77. package/dist/init/scaffold-app.js +27 -0
  78. package/dist/init/scaffold-app.js.map +1 -0
  79. package/dist/lib/artifact.d.ts +10 -0
  80. package/dist/lib/artifact.d.ts.map +1 -0
  81. package/dist/lib/artifact.js +29 -0
  82. package/dist/lib/artifact.js.map +1 -0
  83. package/dist/lib/cf-tunnel.d.ts +11 -0
  84. package/dist/lib/cf-tunnel.d.ts.map +1 -0
  85. package/dist/lib/cf-tunnel.js +30 -0
  86. package/dist/lib/cf-tunnel.js.map +1 -0
  87. package/dist/lib/known-hosts.d.ts +3 -0
  88. package/dist/lib/known-hosts.d.ts.map +1 -0
  89. package/dist/lib/known-hosts.js +18 -0
  90. package/dist/lib/known-hosts.js.map +1 -0
  91. package/dist/lib/output.d.ts +14 -0
  92. package/dist/lib/output.d.ts.map +1 -0
  93. package/dist/lib/output.js +56 -0
  94. package/dist/lib/output.js.map +1 -0
  95. package/dist/lib/run-log.d.ts +3 -0
  96. package/dist/lib/run-log.d.ts.map +1 -0
  97. package/dist/lib/run-log.js +62 -0
  98. package/dist/lib/run-log.js.map +1 -0
  99. package/dist/lib/templates.d.ts +2 -0
  100. package/dist/lib/templates.d.ts.map +1 -0
  101. package/dist/lib/templates.js +6 -0
  102. package/dist/lib/templates.js.map +1 -0
  103. package/dist/lib/version.d.ts +2 -0
  104. package/dist/lib/version.d.ts.map +1 -0
  105. package/dist/lib/version.js +3 -0
  106. package/dist/lib/version.js.map +1 -0
  107. package/dist/logs/logs.command.d.ts +3 -0
  108. package/dist/logs/logs.command.d.ts.map +1 -0
  109. package/dist/logs/logs.command.js +86 -0
  110. package/dist/logs/logs.command.js.map +1 -0
  111. package/dist/monorepo/monorepo.command.d.ts +3 -0
  112. package/dist/monorepo/monorepo.command.d.ts.map +1 -0
  113. package/dist/monorepo/monorepo.command.js +28 -0
  114. package/dist/monorepo/monorepo.command.js.map +1 -0
  115. package/dist/pipelines/adopt-pipelines.d.ts +35 -0
  116. package/dist/pipelines/adopt-pipelines.d.ts.map +1 -0
  117. package/dist/pipelines/adopt-pipelines.js +77 -0
  118. package/dist/pipelines/adopt-pipelines.js.map +1 -0
  119. package/dist/pipelines/control-plane-sync.d.ts +24 -0
  120. package/dist/pipelines/control-plane-sync.d.ts.map +1 -0
  121. package/dist/pipelines/control-plane-sync.js +62 -0
  122. package/dist/pipelines/control-plane-sync.js.map +1 -0
  123. package/dist/plan/plan.command.d.ts +3 -0
  124. package/dist/plan/plan.command.d.ts.map +1 -0
  125. package/dist/plan/plan.command.js +33 -0
  126. package/dist/plan/plan.command.js.map +1 -0
  127. package/dist/resolve/resolve.command.d.ts +3 -0
  128. package/dist/resolve/resolve.command.d.ts.map +1 -0
  129. package/dist/resolve/resolve.command.js +78 -0
  130. package/dist/resolve/resolve.command.js.map +1 -0
  131. package/dist/resolve/resolve.d.ts +4 -0
  132. package/dist/resolve/resolve.d.ts.map +1 -0
  133. package/dist/resolve/resolve.js +33 -0
  134. package/dist/resolve/resolve.js.map +1 -0
  135. package/dist/restore/restore.command.d.ts +3 -0
  136. package/dist/restore/restore.command.d.ts.map +1 -0
  137. package/dist/restore/restore.command.js +68 -0
  138. package/dist/restore/restore.command.js.map +1 -0
  139. package/dist/sandbox-tunnel/sandbox-tunnel.command.d.ts +3 -0
  140. package/dist/sandbox-tunnel/sandbox-tunnel.command.d.ts.map +1 -0
  141. package/dist/sandbox-tunnel/sandbox-tunnel.command.js +62 -0
  142. package/dist/sandbox-tunnel/sandbox-tunnel.command.js.map +1 -0
  143. package/dist/sandbox-tunnel/sandbox-tunnel.d.ts +18 -0
  144. package/dist/sandbox-tunnel/sandbox-tunnel.d.ts.map +1 -0
  145. package/dist/sandbox-tunnel/sandbox-tunnel.js +37 -0
  146. package/dist/sandbox-tunnel/sandbox-tunnel.js.map +1 -0
  147. package/dist/secrets/generated-secrets.d.ts +6 -0
  148. package/dist/secrets/generated-secrets.d.ts.map +1 -0
  149. package/dist/secrets/generated-secrets.js +26 -0
  150. package/dist/secrets/generated-secrets.js.map +1 -0
  151. package/dist/secrets/secret-store.d.ts +14 -0
  152. package/dist/secrets/secret-store.d.ts.map +1 -0
  153. package/dist/secrets/secret-store.js +111 -0
  154. package/dist/secrets/secret-store.js.map +1 -0
  155. package/dist/secrets/secrets.command.d.ts +6 -0
  156. package/dist/secrets/secrets.command.d.ts.map +1 -0
  157. package/dist/secrets/secrets.command.js +106 -0
  158. package/dist/secrets/secrets.command.js.map +1 -0
  159. package/dist/secrets/secrets.d.ts +7 -0
  160. package/dist/secrets/secrets.d.ts.map +1 -0
  161. package/dist/secrets/secrets.js +14 -0
  162. package/dist/secrets/secrets.js.map +1 -0
  163. package/package.json +69 -0
  164. package/templates/access.md.eta +21 -0
  165. package/templates/demo/Dockerfile.eta +5 -0
  166. package/templates/demo/deploy.config.ts.eta +23 -0
  167. package/templates/demo/env.eta +2 -0
  168. package/templates/env-example.eta +6 -0
  169. package/templates/scaffold/Dockerfile.eta +7 -0
  170. package/templates/scaffold/deploy.config.selfhost.ts.eta +18 -0
  171. package/templates/scaffold/deploy.config.ts.eta +22 -0
  172. package/templates/scaffold/server.js.eta +9 -0
  173. package/templates/workflows/apply.yaml.eta +28 -0
  174. package/templates/workflows/resolve.yaml.eta +26 -0
@@ -0,0 +1,3 @@
1
+ import { type CommandContext } from "@stricli/core";
2
+ export declare const apply: import("@stricli/core").Command<CommandContext>;
3
+ //# sourceMappingURL=apply.command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.command.d.ts","sourceRoot":"","sources":["../../src/apply/apply.command.ts"],"names":[],"mappings":"AAMA,OAAO,EAAgB,KAAK,cAAc,EAAgB,MAAM,eAAe,CAAC;AAqBhF,eAAO,MAAM,KAAK,iDAsKhB,CAAC"}
@@ -0,0 +1,144 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { applyMoves, createStore, prune, reconcile, resolveInputs, rewriteGraphForMoves } from "@intentic/engine";
6
+ import { createProviders, createSshExecutor, createSshProbe, hostTarget } from "@intentic/providers";
7
+ import { buildCommand, numberParser } from "@stricli/core";
8
+ import { loadConfig } from "../env.config.js";
9
+ import { ACCESS_FILE, ARTIFACT_PATH, LAST_APPLIED_FILE, loadEnvFile, readArtifact, STATUS_FILE, writeStatus } from "../lib/artifact.js";
10
+ import { createKnownHostsStore } from "../lib/known-hosts.js";
11
+ import { createOutput } from "../lib/output.js";
12
+ import { withRunLog } from "../lib/run-log.js";
13
+ import { ensureGeneratedSecrets } from "../secrets/generated-secrets.js";
14
+ import { generatedSecretStore } from "../secrets/secret-store.js";
15
+ import { collectSecrets } from "../secrets/secrets.js";
16
+ import { collectAccess, formatAccessSummary, writeAccessFile } from "./access.js";
17
+ import { acquireApplyLock } from "./apply-lock.js";
18
+ import { detectHostMoves, migrateHosts } from "./migrate.js";
19
+ const DEFAULT_MAX_ITERATIONS = 5;
20
+ export const apply = buildCommand({
21
+ docs: { brief: "Execute the desired-state artifact until state reads true" },
22
+ parameters: {
23
+ flags: {
24
+ artifact: { kind: "parsed", parse: String, optional: true, brief: `Path to the artifact (default: ${ARTIFACT_PATH})` },
25
+ maxIterations: {
26
+ kind: "parsed",
27
+ parse: numberParser,
28
+ optional: true,
29
+ brief: `Max reconcile iterations (default ${DEFAULT_MAX_ITERATIONS})`,
30
+ },
31
+ previous: {
32
+ kind: "parsed",
33
+ parse: String,
34
+ optional: true,
35
+ brief: "Path to the last successfully-applied artifact; resources absent from the new one are pruned after convergence",
36
+ },
37
+ },
38
+ },
39
+ async func(flags) {
40
+ const out = createOutput(withRunLog(this.process.stdout, "apply"), loadConfig().intenticOutput);
41
+ const artifact = flags.artifact ?? ARTIFACT_PATH;
42
+ const dir = dirname(artifact);
43
+ loadEnvFile(dir);
44
+ const graph = await readArtifact(artifact);
45
+ const ssh = createSshExecutor(createKnownHostsStore(dir));
46
+ const previousPath = flags.previous ?? join(dir, LAST_APPLIED_FILE);
47
+ const previous = existsSync(previousPath) ? await readArtifact(previousPath) : undefined;
48
+ const hostMoves = previous !== undefined ? detectHostMoves(previous, graph) : [];
49
+ const targets = Object.values(graph.resources)
50
+ .filter((node) => node.type === "host")
51
+ .map((node) => hostTarget(resolveInputs(node.inputs, createStore(), process.env, { lenient: false })));
52
+ const probes = targets.map((target) => createSshProbe(target, ssh));
53
+ const probe = probes.length === 0
54
+ ? undefined
55
+ : async (url, status) => {
56
+ for (const p of probes) {
57
+ if (await p(url, status)) {
58
+ return true;
59
+ }
60
+ }
61
+ return false;
62
+ };
63
+ const oldMoveTargets = hostMoves.map((move) => hostTarget(resolveInputs(move.oldNode.inputs, createStore(), process.env, { lenient: false })));
64
+ const lock = await acquireApplyLock(ssh, [...targets, ...oldMoveTargets], { log: out.log });
65
+ const onSignal = () => {
66
+ void Promise.allSettled([lock.release(), ssh.dispose?.()]).finally(() => process.exit(130));
67
+ };
68
+ process.once("SIGINT", onSignal);
69
+ process.once("SIGTERM", onSignal);
70
+ try {
71
+ await ensureGeneratedSecrets(generatedSecretStore(graph, dir, ssh, true, out.log), collectSecrets(graph).generated, process.env);
72
+ if (hostMoves.length > 0) {
73
+ await migrateHosts(hostMoves, { next: graph, ssh, env: process.env, tmpDir: tmpdir(), log: out.log });
74
+ }
75
+ const movedApplied = await applyMoves(graph, {
76
+ providers: createProviders({ ssh }),
77
+ log: out.log,
78
+ onEvent: out.onEvent,
79
+ env: process.env,
80
+ });
81
+ const result = await reconcile(graph, { providers: createProviders({ ssh }), log: out.log, onEvent: out.onEvent, ...(probe !== undefined ? { probe } : {}) }, { maxIterations: flags.maxIterations ?? DEFAULT_MAX_ITERATIONS });
82
+ const access = collectAccess(graph, result.outcome.outputs, process.env);
83
+ await writeStatus(join(dir, STATUS_FILE), {
84
+ converged: result.converged,
85
+ iterations: result.iterations,
86
+ steps: result.outcome.steps,
87
+ access: access.map((entry) => entry.password === undefined ? entry : { ...entry, password: { source: entry.password.source, key: entry.password.key } }),
88
+ });
89
+ let pruned = { deleted: [], skipped: [] };
90
+ if (previous !== undefined) {
91
+ await lock.renew();
92
+ await lock.verify();
93
+ const baseline = rewriteGraphForMoves(previous, movedApplied);
94
+ pruned = await prune(baseline, graph, { providers: createProviders({ ssh }), log: out.log, onEvent: out.onEvent, env: process.env });
95
+ if (pruned.deleted.length > 0 || pruned.skipped.length > 0) {
96
+ out.text(`pruned ${pruned.deleted.length} resource(s)${pruned.skipped.length > 0 ? `, ${pruned.skipped.length} left in place` : ""}`);
97
+ }
98
+ }
99
+ await writeFile(join(dir, LAST_APPLIED_FILE), await readFile(artifact, "utf8"));
100
+ out.text(`${result.converged ? "converged" : "did not converge"} in ${result.iterations} iteration(s)`);
101
+ if (access.length > 0) {
102
+ await writeAccessFile(join(dir, ACCESS_FILE), access);
103
+ out.text(formatAccessSummary(access));
104
+ }
105
+ out.result({
106
+ converged: result.converged,
107
+ iterations: result.iterations,
108
+ steps: result.outcome.steps,
109
+ outputs: result.outcome.outputs,
110
+ orphans: result.outcome.orphans,
111
+ pruned,
112
+ access,
113
+ });
114
+ const reconcileWebhook = result.outcome.outputs["discord"]?.["reconcileWebhook"];
115
+ if (typeof reconcileWebhook === "string" && reconcileWebhook !== "") {
116
+ const creates = result.outcome.steps.filter((s) => s.action === "create").length;
117
+ const updates = result.outcome.steps.filter((s) => s.action === "update").length;
118
+ const noops = result.outcome.steps.filter((s) => s.action === "noop").length;
119
+ const summary = [
120
+ `**intentic apply** — ${result.converged ? "✅ converged" : "⚠️ did not converge"} in ${result.iterations} iteration(s)`,
121
+ `📊 ${result.outcome.steps.length} resources: ${creates} created, ${updates} updated, ${noops} unchanged`,
122
+ ...(result.outcome.orphans.length > 0 ? [`🗑️ ${result.outcome.orphans.length} orphan(s) detected`] : []),
123
+ ].join("\n");
124
+ try {
125
+ await fetch(reconcileWebhook, {
126
+ method: "POST",
127
+ headers: { "Content-Type": "application/json" },
128
+ body: JSON.stringify({ content: summary }),
129
+ });
130
+ }
131
+ catch {
132
+ out.log("discord: failed to post reconcile summary (non-fatal)");
133
+ }
134
+ }
135
+ }
136
+ finally {
137
+ process.removeListener("SIGINT", onSignal);
138
+ process.removeListener("SIGTERM", onSignal);
139
+ await lock.release();
140
+ await ssh.dispose?.();
141
+ }
142
+ },
143
+ });
144
+ //# sourceMappingURL=apply.command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.command.js","sourceRoot":"","sources":["../../src/apply/apply.command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { DesiredStateGraph, ResourceNode } from "@intentic/graph";
2
+ import { type SshExecutor } from "@intentic/providers";
3
+ export interface HostMove {
4
+ readonly id: string;
5
+ readonly oldNode: ResourceNode;
6
+ readonly newNode: ResourceNode;
7
+ readonly oldAddress: string;
8
+ readonly newAddress: string;
9
+ }
10
+ export declare const detectHostMoves: (previous: DesiredStateGraph, next: DesiredStateGraph) => HostMove[];
11
+ interface MigrateArgs {
12
+ readonly next: DesiredStateGraph;
13
+ readonly ssh: SshExecutor;
14
+ readonly env: Record<string, string | undefined>;
15
+ readonly tmpDir: string;
16
+ readonly log: (message: string) => void;
17
+ }
18
+ export declare const migrateHosts: (moves: readonly HostMove[], args: MigrateArgs) => Promise<void>;
19
+ export {};
20
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/apply/migrate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAOH,KAAK,WAAW,EAInB,MAAM,qBAAqB,CAAC;AAK7B,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC/B;AAID,eAAO,MAAM,eAAe,GAAI,UAAU,iBAAiB,EAAE,MAAM,iBAAiB,KAAG,QAAQ,EAqB9F,CAAC;AAEF,UAAU,WAAW;IACjB,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAwGD,eAAO,MAAM,YAAY,GAAU,OAAO,SAAS,QAAQ,EAAE,EAAE,MAAM,WAAW,KAAG,OAAO,CAAC,IAAI,CAI9F,CAAC"}
@@ -0,0 +1,112 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { createStore, resolveInputs } from "@intentic/engine";
4
+ import { hostTarget, isLocalRepo, managedContainers, quiesceHost, restoreBackup, snapshotNow, streamRepoVolume, } from "@intentic/providers";
5
+ export const detectHostMoves = (previous, next) => {
6
+ const moves = [];
7
+ for (const [id, newNode] of Object.entries(next.resources)) {
8
+ if (newNode.type !== "host") {
9
+ continue;
10
+ }
11
+ const oldNode = previous.resources[id];
12
+ if (oldNode === undefined || oldNode.type !== "host") {
13
+ continue;
14
+ }
15
+ const oldAddress = oldNode.inputs["address"];
16
+ const newAddress = newNode.inputs["address"];
17
+ if (typeof oldAddress !== "string" || typeof newAddress !== "string") {
18
+ throw new Error(`host "${id}" address is not a literal string; a host migration needs a literal address on both sides`);
19
+ }
20
+ if (oldAddress === newAddress) {
21
+ continue;
22
+ }
23
+ moves.push({ id, oldNode, newNode, oldAddress, newAddress });
24
+ }
25
+ return moves;
26
+ };
27
+ const backupFor = (move, args) => {
28
+ const node = Object.values(args.next.resources).find((n) => n.type === "backup" && n.inputs["address"] === move.newAddress);
29
+ if (node === undefined) {
30
+ return undefined;
31
+ }
32
+ const resolved = resolveInputs(node.inputs, createStore(), args.env, { lenient: false });
33
+ const { repo, password, image } = resolved;
34
+ if (typeof repo !== "string" || typeof password !== "string" || typeof image !== "string") {
35
+ throw new Error(`backup for host "${move.id}" is missing its repo/password/image inputs`);
36
+ }
37
+ const credentials = {};
38
+ const credsRaw = resolved["credentials"];
39
+ if (typeof credsRaw === "object" && credsRaw !== null) {
40
+ for (const [key, value] of Object.entries(credsRaw)) {
41
+ if (typeof value === "string") {
42
+ credentials[key] = value;
43
+ }
44
+ }
45
+ }
46
+ return { repo, password, image, credentials };
47
+ };
48
+ const migrateHost = async (move, args) => {
49
+ const backup = backupFor(move, args);
50
+ if (backup === undefined) {
51
+ args.log(`host "${move.id}" moved ${move.oldAddress} → ${move.newAddress} but runs no control plane; its data is not migrated (apps are recreated on the new host)`);
52
+ return;
53
+ }
54
+ const newTarget = hostTarget(resolveInputs(move.newNode.inputs, createStore(), args.env, { lenient: false }));
55
+ const oldTarget = hostTarget(resolveInputs(move.oldNode.inputs, createStore(), args.env, { lenient: false }));
56
+ const local = isLocalRepo(backup.repo);
57
+ let oldSession;
58
+ try {
59
+ oldSession = await args.ssh.connect(oldTarget);
60
+ }
61
+ catch (error) {
62
+ if (local) {
63
+ throw new Error(`host "${move.id}" moved to ${move.newAddress}, but its old machine ${move.oldAddress} is unreachable and the restic repo lives on it — bring the old host back to migrate its data: ${String(error)}`, { cause: error });
64
+ }
65
+ args.log(`old host ${move.oldAddress} unreachable; restoring "${move.id}" on ${move.newAddress} from the latest snapshot in ${backup.repo} (data is as of the last scheduled backup)`);
66
+ }
67
+ if (oldSession !== undefined) {
68
+ try {
69
+ const managed = await managedContainers(oldSession);
70
+ if (managed.length === 0) {
71
+ args.log(`old host ${move.oldAddress} has no intentic-managed containers; nothing to migrate for "${move.id}"`);
72
+ return;
73
+ }
74
+ args.log(`migrating host "${move.id}" ${move.oldAddress} → ${move.newAddress} (${managed.length} managed container(s))`);
75
+ await quiesceHost(oldSession);
76
+ await snapshotNow(oldSession, args.log);
77
+ await oldSession.exec("docker stop intentic-backup 2>/dev/null || true");
78
+ if (local) {
79
+ const tarPath = join(args.tmpDir, `intentic-migrate-${move.id}.tgz`);
80
+ const newSession = await args.ssh.connect(newTarget);
81
+ try {
82
+ await streamRepoVolume(oldSession, newSession, backup.image, tarPath, args.log);
83
+ }
84
+ finally {
85
+ await newSession.dispose();
86
+ await rm(tarPath, { force: true });
87
+ }
88
+ }
89
+ }
90
+ finally {
91
+ await oldSession.dispose();
92
+ }
93
+ }
94
+ await restoreBackup({
95
+ target: newTarget,
96
+ image: backup.image,
97
+ repo: backup.repo,
98
+ password: backup.password,
99
+ credentials: backup.credentials,
100
+ snapshot: "latest",
101
+ scope: "all",
102
+ log: args.log,
103
+ executor: args.ssh,
104
+ });
105
+ args.log(`restored "${move.id}" data onto ${move.newAddress}; reconcile will bring its services up next`);
106
+ };
107
+ export const migrateHosts = async (moves, args) => {
108
+ for (const move of moves) {
109
+ await migrateHost(move, args);
110
+ }
111
+ };
112
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/apply/migrate.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { run } from "@stricli/core";
3
+ import { app } from "./app.js";
4
+ await run(app, process.argv.slice(2), { process: process });
5
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAI/B,MAAM,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,OAAyB,EAAE,CAAC,CAAC"}
package/dist/demo.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=demo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../src/demo.ts"],"names":[],"mappings":""}
package/dist/demo.js ADDED
@@ -0,0 +1,281 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { createRequire } from "node:module";
4
+ import { join } from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import { promisify } from "node:util";
7
+ import { cloudflareApi, forgejoApi, sshExecutor } from "@intentic/providers";
8
+ import { deploymentId, deploymentPort } from "@intentic/state-resolver";
9
+ import { loadConfig } from "./env.config.js";
10
+ import { renderTemplate } from "./lib/templates.js";
11
+ import { readGeneratedSecrets } from "./secrets/generated-secrets.js";
12
+ const { utils } = createRequire(import.meta.url)("ssh2");
13
+ const exec = promisify(execFile);
14
+ const repoRoot = fileURLToPath(new URL("../../../", import.meta.url));
15
+ const cliJs = join(repoRoot, "_apps/cli/dist/cli.js");
16
+ const stateDir = join(repoRoot, ".demo");
17
+ const stateFile = join(stateDir, "state.json");
18
+ const CONTAINER = "intentic-demo-host";
19
+ const TUNNEL = "intentic-host";
20
+ const ADMIN = "intentic";
21
+ const APP = "app";
22
+ const ENV = "production";
23
+ const APP_BODY = "intentic-demo-live";
24
+ const appPort = deploymentPort(deploymentId(APP, ENV));
25
+ const config = loadConfig();
26
+ const zone = config.cloudflareZone;
27
+ const sshPort = config.demo.sshPort;
28
+ const forgejoPort = config.demo.forgejoPort;
29
+ const komodoPort = config.demo.komodoPort;
30
+ const GIT_URL = `https://git.${zone}`;
31
+ const KOMODO_URL = `https://deploy.${zone}`;
32
+ const APP_URL = `https://app.${zone}`;
33
+ const log = (message) => {
34
+ process.stdout.write(`${message}\n`);
35
+ };
36
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
37
+ const dohHook = pathToFileURL(join(repoRoot, "_apps/cli/doh-lookup.mjs")).href;
38
+ const cliEnv = {
39
+ ...process.env,
40
+ DEMO_DOH_ZONE: zone,
41
+ NODE_OPTIONS: `${config.nodeOptions} --import=${dohHook}`.trim(),
42
+ };
43
+ const run = (command, args, env = process.env) => new Promise((resolve, reject) => {
44
+ const child = spawn(command, args, { cwd: repoRoot, env, stdio: ["ignore", "inherit", "inherit"] });
45
+ child.on("error", reject);
46
+ child.on("exit", (code) => {
47
+ if (code === 0) {
48
+ resolve();
49
+ }
50
+ else {
51
+ reject(new Error(`${command} ${args.join(" ")} exited ${code}`));
52
+ }
53
+ });
54
+ });
55
+ const runCli = (...args) => run(process.execPath, [cliJs, ...args], cliEnv);
56
+ const quiet = async (command, args) => {
57
+ try {
58
+ await exec(command, args, { maxBuffer: 16 * 1024 * 1024 });
59
+ return true;
60
+ }
61
+ catch {
62
+ return false;
63
+ }
64
+ };
65
+ const readToken = async () => {
66
+ if (config.cloudflareApiToken !== "") {
67
+ return config.cloudflareApiToken.replace(/\\/g, "").trim();
68
+ }
69
+ const envText = await readFile(join(repoRoot, "desired-state/.env"), "utf8");
70
+ const match = /^CLOUDFLARE_API_TOKEN=(.*)$/m.exec(envText);
71
+ if (match?.[1] === undefined) {
72
+ throw new Error("CLOUDFLARE_API_TOKEN is not set and was not found in desired-state/.env");
73
+ }
74
+ return match[1].replace(/\\/g, "").trim();
75
+ };
76
+ const deployConfig = () => renderTemplate("demo/deploy.config.ts", { sshPort, app: APP, env: ENV, domain: `app.${zone}`, appPort: String(appPort) });
77
+ const envFile = (privateKey, apiToken) => renderTemplate("demo/env", { privateKey, apiToken });
78
+ const DOCKERFILE = renderTemplate("demo/Dockerfile", { appBody: APP_BODY });
79
+ const ssh = async (command) => {
80
+ const session = await sshExecutor.connect({ address: "127.0.0.1", port: sshPort, user: "root", privateKey });
81
+ try {
82
+ const result = await session.exec(command);
83
+ return `${result.stdout}${result.stderr}`;
84
+ }
85
+ finally {
86
+ await session.dispose();
87
+ }
88
+ };
89
+ let privateKey = "";
90
+ const up = async () => {
91
+ const apiToken = await readToken();
92
+ const keys = utils.generateKeyPairSync("ed25519");
93
+ privateKey = keys.private;
94
+ let forgejoPassword = "";
95
+ let komodoPassword = "";
96
+ log(`▶ building the demo host image (${CONTAINER}) from test/host …`);
97
+ await run("docker", ["build", "-t", CONTAINER, join(repoRoot, "test/host")]);
98
+ log("▶ starting the persistent Docker-in-Docker host …");
99
+ await quiet("docker", ["rm", "-f", CONTAINER]);
100
+ await run("docker", [
101
+ "run",
102
+ "-d",
103
+ "--privileged",
104
+ "--dns",
105
+ "1.1.1.1",
106
+ "--dns",
107
+ "1.0.0.1",
108
+ "--name",
109
+ CONTAINER,
110
+ "-e",
111
+ "DOCKER_TLS_CERTDIR=",
112
+ "-p",
113
+ `${sshPort}:22`,
114
+ "-p",
115
+ `${forgejoPort}:3000`,
116
+ "-p",
117
+ `${komodoPort}:9120`,
118
+ CONTAINER,
119
+ ]);
120
+ log("▶ injecting the demo SSH key …");
121
+ for (let i = 0; i < 60 && !(await quiet("docker", ["exec", CONTAINER, "true"])); i++) {
122
+ await sleep(1000);
123
+ }
124
+ await exec("docker", [
125
+ "exec",
126
+ CONTAINER,
127
+ "sh",
128
+ "-c",
129
+ `mkdir -p /root/.ssh && chmod 700 /root/.ssh && printf '%s\\n' '${keys.public}' > /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys`,
130
+ ]);
131
+ log(`▶ waiting for SSH on 127.0.0.1:${sshPort} …`);
132
+ for (let i = 0;; i++) {
133
+ try {
134
+ await (await sshExecutor.connect({ address: "127.0.0.1", port: sshPort, user: "root", privateKey })).dispose();
135
+ break;
136
+ }
137
+ catch (error) {
138
+ if (i >= 90) {
139
+ throw new Error(`host SSH never came up: ${String(error)}`, { cause: error });
140
+ }
141
+ await sleep(1000);
142
+ }
143
+ }
144
+ const workspace = repoRoot;
145
+ const configPath = join(workspace, "intent", "deploy.config.ts");
146
+ const artifactPath = join(workspace, "desired-state", "desired-state.json");
147
+ await mkdir(stateDir, { recursive: true });
148
+ await writeFile(stateFile, JSON.stringify({
149
+ zone,
150
+ apiToken,
151
+ container: CONTAINER,
152
+ sshPort,
153
+ urls: { git: GIT_URL, komodo: KOMODO_URL, app: APP_URL },
154
+ local: { git: `http://127.0.0.1:${forgejoPort}`, komodo: `http://127.0.0.1:${komodoPort}` },
155
+ }, null, 2));
156
+ log("▶ scaffolding the intent (init --link) …");
157
+ await runCli("init", "--dir", workspace, "--link");
158
+ await writeFile(configPath, deployConfig());
159
+ await writeFile(join(workspace, "desired-state", ".env"), envFile(privateKey, apiToken));
160
+ log("▶ resolve + apply — Forgejo + Komodo + tunnel + the app's CI/CD wiring …");
161
+ await runCli("resolve", "--config", configPath, "--out", artifactPath);
162
+ await runCli("apply", "--artifact", artifactPath, "--maxIterations", "8");
163
+ const secrets = await readGeneratedSecrets(join(workspace, "desired-state"));
164
+ forgejoPassword = secrets["FORGEJO_ADMIN_PASSWORD"] ?? "";
165
+ komodoPassword = secrets["KOMODO_ADMIN_PASSWORD"] ?? "";
166
+ const running = await ssh("docker ps --format '{{.Names}}'");
167
+ for (const name of ["intentic-forgejo", "intentic-forgejo-runner", "komodo-core"]) {
168
+ if (!running.includes(name)) {
169
+ throw new Error(`platform container "${name}" is not running on the host:\n${running}`);
170
+ }
171
+ }
172
+ log(`✅ platform up — host containers: ${running.split("\n").filter(Boolean).join(", ")}`);
173
+ log("▶ seeding the app repo (intentic/app @ main) with a Dockerfile …");
174
+ let appDeployed = false;
175
+ try {
176
+ await forgejoApi.commitFile({
177
+ baseUrl: `http://127.0.0.1:${forgejoPort}`,
178
+ user: ADMIN,
179
+ password: forgejoPassword,
180
+ owner: ADMIN,
181
+ name: APP,
182
+ branch: "main",
183
+ path: "Dockerfile",
184
+ content: DOCKERFILE,
185
+ message: "seed demo app",
186
+ });
187
+ log(`▶ CI builds + pushes the image and Komodo rolls it out — polling http://127.0.0.1:${appPort} …`);
188
+ const deadline = Date.now() + 5 * 60_000;
189
+ while (!appDeployed && Date.now() < deadline) {
190
+ const hit = (await ssh(`wget -q -T 5 -O- http://127.0.0.1:${appPort} 2>/dev/null || true`)).trim();
191
+ appDeployed = hit.includes(APP_BODY);
192
+ if (!appDeployed) {
193
+ await sleep(2000);
194
+ }
195
+ }
196
+ log(appDeployed
197
+ ? `✅ app deployed by CI/CD — serving "${APP_BODY}" on the host`
198
+ : `⚠ app not live yet on :${appPort} (CI/CD may still be running)`);
199
+ }
200
+ catch (error) {
201
+ log(`⚠ app phase skipped (platform stays up): ${String(error)}`);
202
+ }
203
+ log("");
204
+ log("════════════════════════════════════════════════════════════════════");
205
+ log(" intentic demo is UP and will stay up until you run `pnpm demo:down`");
206
+ log("════════════════════════════════════════════════════════════════════");
207
+ log(" Public (through the Cloudflare tunnel — DNS/edge may take ~1 min):");
208
+ log(` Forgejo : ${GIT_URL}`);
209
+ log(` Komodo : ${KOMODO_URL}`);
210
+ if (appDeployed) {
211
+ log(` App : ${APP_URL}`);
212
+ }
213
+ log(" Local (instant, no DNS needed):");
214
+ log(` Forgejo : http://127.0.0.1:${forgejoPort}`);
215
+ log(` Komodo : http://127.0.0.1:${komodoPort}`);
216
+ log(" Admin login (intentic-generated):");
217
+ log(` user : ${ADMIN}`);
218
+ log(` Forgejo pw: ${forgejoPassword}`);
219
+ log(` Komodo pw : ${komodoPassword}`);
220
+ log(` (saved in ${join(workspace, "desired-state", ".secrets.json")})`);
221
+ log(` SSH into the host: ssh -p ${sshPort} root@127.0.0.1 (key in ${join(workspace, "desired-state", ".env")})`);
222
+ log(" Push the control-plane repos to Forgejo: pnpm intentic adopt (once git DNS is live)");
223
+ log(" Stop (reuses tunnel + DNS, fast re-up) : pnpm demo:down");
224
+ log(" Full teardown (also removes tunnel+DNS): pnpm demo:clear");
225
+ log(" Note: keep this machine + Docker running; the public URLs share the");
226
+ log(" tunnel name 'intentic-host', so don't run the e2e at the same time.");
227
+ log("════════════════════════════════════════════════════════════════════");
228
+ };
229
+ const down = async () => {
230
+ log(`▶ stopping the demo host container (${CONTAINER}) — leaving the Cloudflare tunnel + DNS for a fast re-up …`);
231
+ await quiet("docker", ["rm", "-f", CONTAINER]);
232
+ log("✅ demo stopped. `pnpm demo:up` reconnects in seconds (tunnel + DNS reused); `pnpm demo:clear` tears down Cloudflare too.");
233
+ };
234
+ const clear = async () => {
235
+ const state = await readFile(stateFile, "utf8")
236
+ .then((text) => JSON.parse(text))
237
+ .catch(() => ({}));
238
+ const zoneName = state.zone ?? zone;
239
+ const apiToken = state.apiToken ?? (await readToken());
240
+ log(`▶ removing the demo host container (${CONTAINER}) …`);
241
+ await quiet("docker", ["rm", "-f", CONTAINER]);
242
+ log("▶ deleting the Cloudflare tunnel + DNS records …");
243
+ const cfZone = await cloudflareApi.getZone({ apiToken, zone: zoneName }).catch(() => undefined);
244
+ if (cfZone !== undefined) {
245
+ const tunnel = await cloudflareApi.findTunnel({ accountId: cfZone.accountId, apiToken, name: TUNNEL }).catch(() => undefined);
246
+ if (tunnel !== undefined) {
247
+ await fetch(`https://api.cloudflare.com/client/v4/accounts/${cfZone.accountId}/cfd_tunnel/${tunnel.id}/connections`, {
248
+ method: "DELETE",
249
+ headers: { Authorization: `Bearer ${apiToken}` },
250
+ }).catch(() => { });
251
+ await cloudflareApi
252
+ .deleteTunnel({ accountId: cfZone.accountId, apiToken, tunnelId: tunnel.id })
253
+ .catch((error) => log(` tunnel: ${String(error)}`));
254
+ }
255
+ for (const name of [`git.${zoneName}`, `deploy.${zoneName}`, `app.${zoneName}`]) {
256
+ const record = await cloudflareApi.findDnsRecord({ apiToken, zoneId: cfZone.id, name }).catch(() => undefined);
257
+ if (record !== undefined) {
258
+ await cloudflareApi
259
+ .deleteDnsRecord({ apiToken, zoneId: cfZone.id, recordId: record.id })
260
+ .catch((error) => log(` dns ${name}: ${String(error)}`));
261
+ }
262
+ }
263
+ }
264
+ await rm(stateDir, { recursive: true, force: true }).catch(() => { });
265
+ log("✅ demo cleared — host container, tunnel, and DNS records removed (intent/ + desired-state/ kept).");
266
+ };
267
+ const mode = process.argv[2];
268
+ if (mode === "up") {
269
+ await up();
270
+ }
271
+ else if (mode === "down") {
272
+ await down();
273
+ }
274
+ else if (mode === "clear" || mode === "clean") {
275
+ await clear();
276
+ }
277
+ else {
278
+ log("usage: demo <up|down|clear>");
279
+ process.exit(1);
280
+ }
281
+ //# sourceMappingURL=demo.js.map