@synap-core/eve 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +251 -0
- package/dist/add-R6RGUYER.js +22 -0
- package/dist/add-R6RGUYER.js.map +1 -0
- package/dist/auth-7FFZNARZ-4OIRGZ7R.js +31 -0
- package/dist/auth-7FFZNARZ-4OIRGZ7R.js.map +1 -0
- package/dist/chunk-22RIHU4J.js +975 -0
- package/dist/chunk-22RIHU4J.js.map +1 -0
- package/dist/chunk-23XQDB7K.js +1773 -0
- package/dist/chunk-23XQDB7K.js.map +1 -0
- package/dist/chunk-5TBV2VHZ.js +6067 -0
- package/dist/chunk-5TBV2VHZ.js.map +1 -0
- package/dist/chunk-D7P3IYYR.js +44 -0
- package/dist/chunk-D7P3IYYR.js.map +1 -0
- package/dist/chunk-EHPC7CKE.js +757 -0
- package/dist/chunk-EHPC7CKE.js.map +1 -0
- package/dist/chunk-FAE5UZFE.js +17 -0
- package/dist/chunk-FAE5UZFE.js.map +1 -0
- package/dist/chunk-HRF6TNXX.js +53 -0
- package/dist/chunk-HRF6TNXX.js.map +1 -0
- package/dist/chunk-HVJEW7TU.js +495 -0
- package/dist/chunk-HVJEW7TU.js.map +1 -0
- package/dist/chunk-JO5H5G6U.js +1529 -0
- package/dist/chunk-JO5H5G6U.js.map +1 -0
- package/dist/chunk-MUEQNBE6.js +1140 -0
- package/dist/chunk-MUEQNBE6.js.map +1 -0
- package/dist/chunk-O5WTCVMZ.js +3391 -0
- package/dist/chunk-O5WTCVMZ.js.map +1 -0
- package/dist/chunk-ROKIQIEX.js +65 -0
- package/dist/chunk-ROKIQIEX.js.map +1 -0
- package/dist/chunk-T7GCAJK5.js +4895 -0
- package/dist/chunk-T7GCAJK5.js.map +1 -0
- package/dist/chunk-UOQWJ3S4.js +138 -0
- package/dist/chunk-UOQWJ3S4.js.map +1 -0
- package/dist/chunk-YQWV5W4U.js +3479 -0
- package/dist/chunk-YQWV5W4U.js.map +1 -0
- package/dist/chunk-ZPDNZRVW.js +272 -0
- package/dist/chunk-ZPDNZRVW.js.map +1 -0
- package/dist/config-store-KTOWIKAS-XVZHNPKW.js +11 -0
- package/dist/config-store-KTOWIKAS-XVZHNPKW.js.map +1 -0
- package/dist/dist-2VRFSXMO.js +40 -0
- package/dist/dist-2VRFSXMO.js.map +1 -0
- package/dist/dist-C2QB4IKT.js +340 -0
- package/dist/dist-C2QB4IKT.js.map +1 -0
- package/dist/dist-DEIUVZ57.js +299 -0
- package/dist/dist-DEIUVZ57.js.map +1 -0
- package/dist/dist-DSKHGECB.js +36 -0
- package/dist/dist-DSKHGECB.js.map +1 -0
- package/dist/dist-GN2DN7CI.js +95 -0
- package/dist/dist-GN2DN7CI.js.map +1 -0
- package/dist/dist-TZL4XW2R.js +58 -0
- package/dist/dist-TZL4XW2R.js.map +1 -0
- package/dist/dist-ZP2APOYV.js +53 -0
- package/dist/dist-ZP2APOYV.js.map +1 -0
- package/dist/index.js +15083 -0
- package/dist/index.js.map +1 -0
- package/dist/operational-5RT6AZJI-ZEMPD32Q.js +12 -0
- package/dist/operational-5RT6AZJI-ZEMPD32Q.js.map +1 -0
- package/dist/reconcile-A4I3Z7VX-GQMEN4GC.js +12 -0
- package/dist/reconcile-A4I3Z7VX-GQMEN4GC.js.map +1 -0
- package/dist/rsshub-GOJBTNOD-UNNVKBFL.js +18 -0
- package/dist/rsshub-GOJBTNOD-UNNVKBFL.js.map +1 -0
- package/dist/synap-image-install-X36REYQ5-UGO4MAOP.js +17 -0
- package/dist/synap-image-install-X36REYQ5-UGO4MAOP.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import { createRequire as __eveCreateRequire } from 'module'; const require = __eveCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
discoverAndBackfillPodConfig,
|
|
4
|
+
ensureSynapLoopbackOverride,
|
|
5
|
+
pruneOldImagesForRepo
|
|
6
|
+
} from "./chunk-5TBV2VHZ.js";
|
|
7
|
+
import {
|
|
8
|
+
readEveSecrets,
|
|
9
|
+
writeEveSecrets
|
|
10
|
+
} from "./chunk-T7GCAJK5.js";
|
|
11
|
+
|
|
12
|
+
// ../@eve/brain/dist/chunk-A3GOG2CO.js
|
|
13
|
+
import { randomBytes } from "crypto";
|
|
14
|
+
import { existsSync as existsSync4, mkdirSync, readdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync4 } from "fs";
|
|
15
|
+
import { join as join3 } from "path";
|
|
16
|
+
import { execSync, spawnSync as spawnSync2 } from "child_process";
|
|
17
|
+
import { spawnSync } from "child_process";
|
|
18
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
19
|
+
import { join as join2 } from "path";
|
|
20
|
+
import { existsSync, readFileSync } from "fs";
|
|
21
|
+
import { join, resolve } from "path";
|
|
22
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
23
|
+
var CANDIDATE_PATHS = [
|
|
24
|
+
"/opt/synap",
|
|
25
|
+
"/opt/synap-backend",
|
|
26
|
+
"/srv/synap",
|
|
27
|
+
"/home/synap/synap-backend",
|
|
28
|
+
"/root/synap-backend"
|
|
29
|
+
];
|
|
30
|
+
function tryPath(root) {
|
|
31
|
+
if (!existsSync(root)) return null;
|
|
32
|
+
const script = join(root, "synap");
|
|
33
|
+
if (!existsSync(script)) return null;
|
|
34
|
+
const deployDir = join(root, "deploy");
|
|
35
|
+
if (!existsSync(join(deployDir, "docker-compose.yml"))) return null;
|
|
36
|
+
return { repoRoot: root, synapScript: script, deployDir };
|
|
37
|
+
}
|
|
38
|
+
function resolveSynapDelegate(cwd) {
|
|
39
|
+
const cliOverride = process.env.SYNAP_CLI?.trim();
|
|
40
|
+
if (cliOverride && existsSync(cliOverride)) {
|
|
41
|
+
const root = resolve(cliOverride, "..");
|
|
42
|
+
const d = tryPath(root);
|
|
43
|
+
if (d) return d;
|
|
44
|
+
}
|
|
45
|
+
const envRoot = process.env.SYNAP_REPO_ROOT?.trim();
|
|
46
|
+
if (envRoot) {
|
|
47
|
+
const d = tryPath(envRoot);
|
|
48
|
+
if (d) return d;
|
|
49
|
+
}
|
|
50
|
+
const statePath = join(cwd ?? process.cwd(), ".eve", "state.json");
|
|
51
|
+
if (existsSync(statePath)) {
|
|
52
|
+
try {
|
|
53
|
+
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
54
|
+
const savedRoot = state?.installed?.synap?.config?.repoRoot;
|
|
55
|
+
if (savedRoot) {
|
|
56
|
+
const d = tryPath(savedRoot);
|
|
57
|
+
if (d) return d;
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const candidate of CANDIDATE_PATHS) {
|
|
63
|
+
const d = tryPath(candidate);
|
|
64
|
+
if (d) return d;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function toPodFqdn(input) {
|
|
69
|
+
const trimmed = input.trim();
|
|
70
|
+
if (!trimmed || trimmed === "localhost") return trimmed;
|
|
71
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(trimmed)) return trimmed;
|
|
72
|
+
if (trimmed.startsWith("pod.")) return trimmed;
|
|
73
|
+
return `pod.${trimmed}`;
|
|
74
|
+
}
|
|
75
|
+
function rewriteEnvDomain(envPath, fqdn) {
|
|
76
|
+
if (!existsSync2(envPath)) return false;
|
|
77
|
+
const current = readFileSync2(envPath, "utf-8");
|
|
78
|
+
const existing = current.match(/^DOMAIN=(.*)$/m)?.[1]?.trim();
|
|
79
|
+
if (existing === fqdn) return false;
|
|
80
|
+
const next = current.match(/^DOMAIN=.*$/m) ? current.replace(/^DOMAIN=.*$/m, `DOMAIN=${fqdn}`) : `${current}${current.endsWith("\n") ? "" : "\n"}DOMAIN=${fqdn}
|
|
81
|
+
`;
|
|
82
|
+
writeFileSync(envPath, next, { encoding: "utf-8", mode: 384 });
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
var DEFAULT_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
86
|
+
function refreshGitCheckout(repoRoot) {
|
|
87
|
+
if (!existsSync2(`${repoRoot}/.git`)) return;
|
|
88
|
+
const git = (args) => spawnSync("git", ["-C", repoRoot, ...args], { stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" });
|
|
89
|
+
git(["fetch", "--quiet"]);
|
|
90
|
+
const ff = git(["pull", "--ff-only"]);
|
|
91
|
+
if (ff.status === 0) return;
|
|
92
|
+
const ref = (git(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]).stdout ?? "").toString().trim();
|
|
93
|
+
const isShallow = (git(["rev-parse", "--is-shallow-repository"]).stdout ?? "").toString().trim() === "true";
|
|
94
|
+
let hasLocalCommits = false;
|
|
95
|
+
if (!isShallow) {
|
|
96
|
+
const aheadOut = ref ? git(["rev-list", "--count", `${ref}..HEAD`]) : null;
|
|
97
|
+
hasLocalCommits = !aheadOut || aheadOut.status !== 0 || parseInt((aheadOut.stdout ?? "").toString().trim(), 10) > 0;
|
|
98
|
+
}
|
|
99
|
+
if (ref && !hasLocalCommits) {
|
|
100
|
+
const reset = git(["reset", "--hard", ref]);
|
|
101
|
+
if (reset.status === 0) {
|
|
102
|
+
console.warn(
|
|
103
|
+
` Note: synap-backend at ${repoRoot} had drifted from ${ref} (couldn't fast-forward); hard-reset to upstream to keep deploy logic + kratos.yml generation current. Gitignored .env/kratos.yml/overrides and docker volumes were untouched.`
|
|
104
|
+
);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const detail = (ff.stderr ?? "").toString().trim() || "rejected (non-fast-forward).";
|
|
109
|
+
console.warn(
|
|
110
|
+
` Warning: could not refresh synap-backend at ${repoRoot} \u2014 ${detail}
|
|
111
|
+
` + (hasLocalCommits ? ` The checkout has local commits; refusing to discard them. To force it to upstream (safe \u2014 data lives in volumes + gitignored files):
|
|
112
|
+
git -C ${repoRoot} fetch && git -C ${repoRoot} reset --hard @{u}
|
|
113
|
+
` : ` Continuing with the on-disk version (may regenerate stale config). Fix: git -C ${repoRoot} fetch && git -C ${repoRoot} reset --hard @{u}
|
|
114
|
+
`)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
function resolveExplicitRepo(repoRoot) {
|
|
118
|
+
const synapScript = `${repoRoot}/synap`;
|
|
119
|
+
const deployDir = `${repoRoot}/deploy`;
|
|
120
|
+
if (!existsSync2(synapScript)) return null;
|
|
121
|
+
if (!existsSync2(`${deployDir}/docker-compose.yml`)) return null;
|
|
122
|
+
return { repoRoot, synapScript, deployDir };
|
|
123
|
+
}
|
|
124
|
+
function runSynapCli(subcommand, args = [], options = {}) {
|
|
125
|
+
const paths = options.repoRoot ? resolveExplicitRepo(options.repoRoot) : resolveSynapDelegate();
|
|
126
|
+
if (!paths) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
exitCode: -1,
|
|
130
|
+
paths: null,
|
|
131
|
+
stdout: "",
|
|
132
|
+
stderr: options.repoRoot ? `synap CLI not found at ${options.repoRoot} \u2014 expected ${options.repoRoot}/synap and ${options.repoRoot}/deploy/docker-compose.yml` : diagnoseMissingSynapCli()
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (options.refreshGit) {
|
|
136
|
+
refreshGitCheckout(paths.repoRoot);
|
|
137
|
+
}
|
|
138
|
+
if (options.domain) {
|
|
139
|
+
const fqdn = toPodFqdn(options.domain);
|
|
140
|
+
rewriteEnvDomain(join2(paths.deployDir, ".env"), fqdn);
|
|
141
|
+
}
|
|
142
|
+
const env = {
|
|
143
|
+
...process.env,
|
|
144
|
+
SYNAP_DEPLOY_DIR: paths.deployDir,
|
|
145
|
+
SYNAP_ASSUME_YES: "1",
|
|
146
|
+
SYNAP_NON_INTERACTIVE: "1",
|
|
147
|
+
// Eve always provides the edge proxy (eve-legs-traefik on port 80/443).
|
|
148
|
+
// Tell the synap CLI to skip its built-in Caddy so it doesn't fight
|
|
149
|
+
// Traefik for port 80 and abort updates with "port already allocated".
|
|
150
|
+
SYNAP_SKIP_EDGE: "1",
|
|
151
|
+
// Belt-and-suspenders: pin the compose project name explicitly so eve
|
|
152
|
+
// never accidentally creates `deploy_*` volumes (the cwd-basename
|
|
153
|
+
// fallback). Pre-Phase-3 synap-backend installs don't have
|
|
154
|
+
// `_resolve_compose_project_name`, so without this override they'd
|
|
155
|
+
// silently use cwd basename → orphan volumes → postgres detonation.
|
|
156
|
+
// Exception: honour an existing pin (operator chose differently).
|
|
157
|
+
COMPOSE_PROJECT_NAME: process.env.COMPOSE_PROJECT_NAME ?? "synap-backend"
|
|
158
|
+
};
|
|
159
|
+
if (options.domain) {
|
|
160
|
+
env.DOMAIN = toPodFqdn(options.domain);
|
|
161
|
+
}
|
|
162
|
+
const inherit = options.inherit !== false;
|
|
163
|
+
const result = spawnSync("bash", [paths.synapScript, subcommand, ...args], {
|
|
164
|
+
cwd: paths.deployDir,
|
|
165
|
+
env,
|
|
166
|
+
stdio: inherit ? "inherit" : "pipe",
|
|
167
|
+
timeout: DEFAULT_TIMEOUT_MS
|
|
168
|
+
});
|
|
169
|
+
return {
|
|
170
|
+
ok: result.status === 0,
|
|
171
|
+
exitCode: result.status ?? -1,
|
|
172
|
+
paths,
|
|
173
|
+
stdout: inherit ? "" : result.stdout?.toString() ?? "",
|
|
174
|
+
stderr: inherit ? "" : result.stderr?.toString() ?? ""
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function diagnoseMissingSynapCli() {
|
|
178
|
+
const candidate = process.env.SYNAP_REPO_ROOT?.trim() || "/opt/synap-backend";
|
|
179
|
+
const hasFlatCompose = existsSync2(`${candidate}/docker-compose.yml`);
|
|
180
|
+
const hasGit = existsSync2(`${candidate}/.git`);
|
|
181
|
+
const hasScript = existsSync2(`${candidate}/synap`);
|
|
182
|
+
if (hasFlatCompose && !hasGit && !hasScript) {
|
|
183
|
+
return [
|
|
184
|
+
"synap CLI not found \u2014 pre-cutover flat-layout install detected at " + candidate + ".",
|
|
185
|
+
"",
|
|
186
|
+
"Eve no longer bundles its own compose file; the canonical synap-backend git checkout is required.",
|
|
187
|
+
"Migrate (preserves docker volumes \u2014 your data is safe):",
|
|
188
|
+
"",
|
|
189
|
+
" docker compose -f " + candidate + "/docker-compose.yml down",
|
|
190
|
+
" sudo mv " + candidate + " " + candidate + ".legacy",
|
|
191
|
+
" sudo git clone --depth 1 https://github.com/synap-core/backend.git " + candidate,
|
|
192
|
+
" sudo mv " + candidate + ".legacy/.env " + candidate + "/deploy/.env",
|
|
193
|
+
" sudo mv " + candidate + ".legacy/docker-compose.override.yml " + candidate + "/deploy/ 2>/dev/null || true",
|
|
194
|
+
" eve update synap # synap CLI now visible; runs canonical update + reconnects eve-network",
|
|
195
|
+
"",
|
|
196
|
+
"After verifying the pod is healthy, you can `sudo rm -rf " + candidate + ".legacy`."
|
|
197
|
+
].join("\n");
|
|
198
|
+
}
|
|
199
|
+
return "synap CLI not found at " + candidate + "/synap \u2014 set SYNAP_REPO_ROOT, or run `eve install synap`.";
|
|
200
|
+
}
|
|
201
|
+
var POD_SECRET_KEYS = [
|
|
202
|
+
"POSTGRES_PASSWORD",
|
|
203
|
+
"JWT_SECRET",
|
|
204
|
+
"KRATOS_SECRETS_COOKIE",
|
|
205
|
+
"KRATOS_SECRETS_CIPHER",
|
|
206
|
+
"KRATOS_WEBHOOK_SECRET",
|
|
207
|
+
"MINIO_ACCESS_KEY",
|
|
208
|
+
"MINIO_SECRET_KEY",
|
|
209
|
+
"TYPESENSE_API_KEY",
|
|
210
|
+
"TYPESENSE_ADMIN_API_KEY",
|
|
211
|
+
"ORY_HYDRA_SECRETS_SYSTEM",
|
|
212
|
+
"SYNAP_SERVICE_ENCRYPTION_KEY",
|
|
213
|
+
"PROVISIONING_TOKEN"
|
|
214
|
+
];
|
|
215
|
+
function readEnvLine(content, key) {
|
|
216
|
+
const m = content.match(new RegExp(`^${key}=(.*)$`, "m"));
|
|
217
|
+
return m?.[1]?.trim() || void 0;
|
|
218
|
+
}
|
|
219
|
+
async function backupPodSecrets(envPath) {
|
|
220
|
+
if (!existsSync3(envPath)) return { captured: [] };
|
|
221
|
+
const content = readFileSync3(envPath, "utf-8");
|
|
222
|
+
const captured = [];
|
|
223
|
+
const podSecrets = {};
|
|
224
|
+
for (const key of POD_SECRET_KEYS) {
|
|
225
|
+
const value = readEnvLine(content, key);
|
|
226
|
+
if (value) {
|
|
227
|
+
podSecrets[key] = value;
|
|
228
|
+
captured.push(key);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (captured.length === 0) return { captured: [] };
|
|
232
|
+
await writeEveSecrets({
|
|
233
|
+
synap: {
|
|
234
|
+
podSecrets: {
|
|
235
|
+
...podSecrets,
|
|
236
|
+
backedUpAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
return { captured };
|
|
241
|
+
}
|
|
242
|
+
async function restorePodSecrets(envPath) {
|
|
243
|
+
if (!existsSync3(envPath)) return { restored: [], reason: ".env does not exist" };
|
|
244
|
+
const secrets = await readEveSecrets();
|
|
245
|
+
const backup = secrets?.synap?.podSecrets;
|
|
246
|
+
if (!backup) return { restored: [], reason: "no eve backup available" };
|
|
247
|
+
let content = readFileSync3(envPath, "utf-8");
|
|
248
|
+
const restored = [];
|
|
249
|
+
for (const key of POD_SECRET_KEYS) {
|
|
250
|
+
const backupValue = backup[key];
|
|
251
|
+
if (!backupValue) continue;
|
|
252
|
+
const currentValue = readEnvLine(content, key);
|
|
253
|
+
if (currentValue) continue;
|
|
254
|
+
const lineExists = new RegExp(`^${key}=.*$`, "m").test(content);
|
|
255
|
+
if (lineExists) {
|
|
256
|
+
content = content.replace(new RegExp(`^${key}=.*$`, "m"), `${key}=${backupValue}`);
|
|
257
|
+
} else {
|
|
258
|
+
const sep = content.endsWith("\n") ? "" : "\n";
|
|
259
|
+
content = `${content}${sep}${key}=${backupValue}
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
restored.push(key);
|
|
263
|
+
}
|
|
264
|
+
if (restored.length > 0) {
|
|
265
|
+
writeFileSync2(envPath, content, { encoding: "utf-8", mode: 384 });
|
|
266
|
+
}
|
|
267
|
+
return { restored };
|
|
268
|
+
}
|
|
269
|
+
var SYNAP_BACKEND_REPO = "https://github.com/synap-core/backend.git";
|
|
270
|
+
function ensureSynapBackendCheckout(repoRoot) {
|
|
271
|
+
if (existsSync4(join3(repoRoot, ".git"))) return;
|
|
272
|
+
mkdirSync(repoRoot, { recursive: true });
|
|
273
|
+
const isEmpty = readdirSafe(repoRoot).length === 0;
|
|
274
|
+
if (!isEmpty) {
|
|
275
|
+
throw new Error(
|
|
276
|
+
`Cannot clone synap-backend into non-empty non-git dir ${repoRoot}. Move existing files away or set SYNAP_REPO_ROOT to a different path.`
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
const result = spawnSync2("git", ["clone", "--depth", "1", SYNAP_BACKEND_REPO, repoRoot], {
|
|
280
|
+
stdio: "inherit"
|
|
281
|
+
});
|
|
282
|
+
if (result.status !== 0) {
|
|
283
|
+
throw new Error(`git clone ${SYNAP_BACKEND_REPO} failed (exit ${result.status})`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function readdirSafe(dir) {
|
|
287
|
+
try {
|
|
288
|
+
return readdirSync(dir);
|
|
289
|
+
} catch {
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function gen(bytes = 32) {
|
|
294
|
+
return randomBytes(bytes).toString("hex");
|
|
295
|
+
}
|
|
296
|
+
function getSynapBackendContainer() {
|
|
297
|
+
return findComposeContainer("backend");
|
|
298
|
+
}
|
|
299
|
+
function getSynapPodAdminContainer() {
|
|
300
|
+
return findComposeContainer("pod-admin");
|
|
301
|
+
}
|
|
302
|
+
function findComposeContainer(service) {
|
|
303
|
+
try {
|
|
304
|
+
const out = execSync(
|
|
305
|
+
`docker ps --filter "label=com.docker.compose.project=synap-backend" --filter "label=com.docker.compose.service=${service}" --format "{{.Names}}"`,
|
|
306
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
|
|
307
|
+
).trim();
|
|
308
|
+
return out.split("\n")[0]?.trim() || null;
|
|
309
|
+
} catch {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function isBackendHealthy() {
|
|
314
|
+
const name = getSynapBackendContainer();
|
|
315
|
+
if (!name) return false;
|
|
316
|
+
try {
|
|
317
|
+
const out = execSync(
|
|
318
|
+
`docker inspect --format "{{.State.Health.Status}}" ${name}`,
|
|
319
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
|
|
320
|
+
).trim();
|
|
321
|
+
return out === "healthy";
|
|
322
|
+
} catch {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function isCaddyOnlyFailure(stderr) {
|
|
327
|
+
if (/synap-backend-caddy.*Bind for [0-9.:]*:80\s+failed: port is already allocated/i.test(stderr)) {
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
const caddy = findComposeContainer("caddy");
|
|
331
|
+
if (!caddy) return false;
|
|
332
|
+
try {
|
|
333
|
+
const status = execSync(
|
|
334
|
+
`docker inspect --format "{{.State.Status}}" ${caddy}`,
|
|
335
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
|
|
336
|
+
).trim();
|
|
337
|
+
return status === "created" || status === "exited";
|
|
338
|
+
} catch {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function connectToEveNetwork(containerName, alias) {
|
|
343
|
+
if (alias) {
|
|
344
|
+
try {
|
|
345
|
+
const inspect = execSync(
|
|
346
|
+
`docker inspect --format "{{json .NetworkSettings.Networks}}" ${containerName}`,
|
|
347
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
|
|
348
|
+
).trim();
|
|
349
|
+
const networks = JSON.parse(inspect);
|
|
350
|
+
const eve = networks["eve-network"];
|
|
351
|
+
if (eve) {
|
|
352
|
+
if ((eve.Aliases ?? []).includes(alias)) return;
|
|
353
|
+
try {
|
|
354
|
+
execSync(`docker network disconnect eve-network ${containerName}`, {
|
|
355
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
356
|
+
});
|
|
357
|
+
} catch {
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const aliasArg = alias ? ` --alias ${alias}` : "";
|
|
364
|
+
try {
|
|
365
|
+
execSync(`docker network connect${aliasArg} eve-network ${containerName}`, {
|
|
366
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
367
|
+
});
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
async function installSynapFromImage(opts = {}) {
|
|
372
|
+
const repoRoot = opts.deployDir ?? "/opt/synap-backend";
|
|
373
|
+
const composeDir = join3(repoRoot, "deploy");
|
|
374
|
+
let bareDomain = opts.domain ?? "localhost";
|
|
375
|
+
if (bareDomain === "localhost") {
|
|
376
|
+
const discovered = await discoverAndBackfillPodConfig(process.cwd());
|
|
377
|
+
if (discovered.domain) {
|
|
378
|
+
bareDomain = discovered.domain;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const podDomain = toPodFqdn(bareDomain);
|
|
382
|
+
const adminBootstrapMode = opts.adminBootstrapMode ?? "token";
|
|
383
|
+
ensureSynapBackendCheckout(repoRoot);
|
|
384
|
+
try {
|
|
385
|
+
const r = ensureSynapLoopbackOverride(composeDir);
|
|
386
|
+
if (r.outcome === "wrote") {
|
|
387
|
+
console.log(` Wrote loopback compose override at ${r.path}`);
|
|
388
|
+
} else {
|
|
389
|
+
console.log(` Kept existing ${r.path} (${r.reason ?? "user-owned"})`);
|
|
390
|
+
}
|
|
391
|
+
} catch (err) {
|
|
392
|
+
console.warn(` Could not write loopback override: ${err instanceof Error ? err.message : String(err)} (continuing \u2014 CLI will use public URL)`);
|
|
393
|
+
}
|
|
394
|
+
const envPath = join3(composeDir, ".env");
|
|
395
|
+
const envExisted = existsSync4(envPath);
|
|
396
|
+
const existingEnv = envExisted ? readFileSync4(envPath, "utf-8") : "";
|
|
397
|
+
const existingToken = existingEnv.match(/^ADMIN_BOOTSTRAP_TOKEN=(.+)$/m)?.[1];
|
|
398
|
+
const existingEmail = existingEnv.match(/^LETSENCRYPT_EMAIL=(.+)$/m)?.[1]?.trim();
|
|
399
|
+
const email = opts.email?.trim() || existingEmail;
|
|
400
|
+
const bootstrapToken = existingToken?.trim() ?? gen(16);
|
|
401
|
+
if (envExisted) {
|
|
402
|
+
reconcileEveEnv(envPath);
|
|
403
|
+
const restored = await restorePodSecrets(envPath);
|
|
404
|
+
if (restored.restored.length > 0) {
|
|
405
|
+
console.log(` Restored ${restored.restored.length} pod secret(s) from eve backup: ${restored.restored.join(", ")}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const cliArgs = [
|
|
409
|
+
"--non-interactive",
|
|
410
|
+
"--dir",
|
|
411
|
+
repoRoot,
|
|
412
|
+
"--domain",
|
|
413
|
+
podDomain,
|
|
414
|
+
"--admin-bootstrap-mode",
|
|
415
|
+
adminBootstrapMode
|
|
416
|
+
];
|
|
417
|
+
if (opts.fromSource === true) cliArgs.push("--from-source");
|
|
418
|
+
else if (opts.fromSource === false) cliArgs.push("--from-image");
|
|
419
|
+
if (email) cliArgs.push("--email", email);
|
|
420
|
+
if (opts.adminEmail) cliArgs.push("--admin-email", opts.adminEmail);
|
|
421
|
+
if (opts.adminPassword) cliArgs.push("--admin-password", opts.adminPassword);
|
|
422
|
+
if (adminBootstrapMode === "token") cliArgs.push("--admin-bootstrap-token", bootstrapToken);
|
|
423
|
+
if (opts.withOpenclaw) cliArgs.push("--with-openclaw");
|
|
424
|
+
if (opts.withRsshub) cliArgs.push("--with-rsshub");
|
|
425
|
+
const cliResult = runSynapCli("install", cliArgs, { repoRoot, refreshGit: true });
|
|
426
|
+
if (!cliResult.ok) {
|
|
427
|
+
if (isBackendHealthy() && isCaddyOnlyFailure(cliResult.stderr)) {
|
|
428
|
+
console.warn(
|
|
429
|
+
`
|
|
430
|
+
\u26A0 Caddy could not bind port 80 (eve-legs-traefik already owns it).
|
|
431
|
+
Backend is healthy; the data plane is up. Synap install proceeding.
|
|
432
|
+
To stop the spurious Caddy container: docker rm -f synap-backend-caddy-1
|
|
433
|
+
`
|
|
434
|
+
);
|
|
435
|
+
} else {
|
|
436
|
+
throw new Error(
|
|
437
|
+
`synap install exited ${cliResult.exitCode}` + (cliResult.stderr ? `: ${cliResult.stderr}` : "")
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
const captured = await backupPodSecrets(envPath);
|
|
443
|
+
if (captured.captured.length > 0) {
|
|
444
|
+
console.log(` Backed up ${captured.captured.length} pod secret(s) to secrets.json:synap.podSecrets`);
|
|
445
|
+
}
|
|
446
|
+
} catch (err) {
|
|
447
|
+
console.warn(` Could not back up pod secrets: ${err instanceof Error ? err.message : String(err)} (continuing \u2014 recovery from .env loss won't be available until next install/update)`);
|
|
448
|
+
}
|
|
449
|
+
const containerName = getSynapBackendContainer();
|
|
450
|
+
if (containerName) {
|
|
451
|
+
connectToEveNetwork(containerName, "eve-brain-synap");
|
|
452
|
+
console.log(` Connected ${containerName} \u2192 eve-network (alias: eve-brain-synap)`);
|
|
453
|
+
} else {
|
|
454
|
+
console.warn(" Backend container not found after synap install \u2014 skipping eve-network attach");
|
|
455
|
+
}
|
|
456
|
+
const podAdminContainer = getSynapPodAdminContainer();
|
|
457
|
+
if (podAdminContainer) {
|
|
458
|
+
connectToEveNetwork(podAdminContainer, "eve-brain-pod-admin");
|
|
459
|
+
console.log(` Connected ${podAdminContainer} \u2192 eve-network (alias: eve-brain-pod-admin)`);
|
|
460
|
+
}
|
|
461
|
+
for (const repo of ["ghcr.io/synap-core/backend", "ghcr.io/synap-core/pod-agent"]) {
|
|
462
|
+
try {
|
|
463
|
+
const r = pruneOldImagesForRepo(repo, 3);
|
|
464
|
+
if (r.removed.length > 0) {
|
|
465
|
+
console.log(` Pruned ${r.removed.length} old ${repo} image(s) (kept latest 3).`);
|
|
466
|
+
}
|
|
467
|
+
} catch {
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const finalEnv = existsSync4(envPath) ? readFileSync4(envPath, "utf-8") : "";
|
|
471
|
+
const finalToken = finalEnv.match(/^ADMIN_BOOTSTRAP_TOKEN=(.+)$/m)?.[1]?.trim() ?? bootstrapToken;
|
|
472
|
+
return { bootstrapToken: finalToken, deployDir: repoRoot, containerName };
|
|
473
|
+
}
|
|
474
|
+
function reconcileEveEnv(envPath) {
|
|
475
|
+
if (!existsSync4(envPath)) return false;
|
|
476
|
+
let content = readFileSync4(envPath, "utf-8");
|
|
477
|
+
const legacy = content.match(/^KRATOS_CONFIG_DIR=\.\/config\/kratos\s*$/m);
|
|
478
|
+
if (!legacy) return false;
|
|
479
|
+
content = content.replace(/^KRATOS_CONFIG_DIR=\.\/config\/kratos\s*\n?/m, "");
|
|
480
|
+
writeFileSync3(envPath, content, { encoding: "utf-8", mode: 384 });
|
|
481
|
+
console.log(" reconcile-env: removed legacy KRATOS_CONFIG_DIR=./config/kratos (canonical layout uses ../kratos)");
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export {
|
|
486
|
+
resolveSynapDelegate,
|
|
487
|
+
toPodFqdn,
|
|
488
|
+
runSynapCli,
|
|
489
|
+
POD_SECRET_KEYS,
|
|
490
|
+
backupPodSecrets,
|
|
491
|
+
restorePodSecrets,
|
|
492
|
+
installSynapFromImage,
|
|
493
|
+
reconcileEveEnv
|
|
494
|
+
};
|
|
495
|
+
//# sourceMappingURL=chunk-HVJEW7TU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../@eve/brain/dist/chunk-A3GOG2CO.js"],"sourcesContent":["// src/lib/synap-image-install.ts\nimport { randomBytes } from \"crypto\";\nimport { existsSync as existsSync4, mkdirSync, readdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync4 } from \"fs\";\nimport { join as join3 } from \"path\";\nimport { execSync, spawnSync as spawnSync2 } from \"child_process\";\nimport {\n ensureSynapLoopbackOverride,\n pruneOldImagesForRepo,\n discoverAndBackfillPodConfig\n} from \"@eve/dna\";\n\n// src/lib/synap-cli-delegate.ts\nimport { spawnSync } from \"child_process\";\nimport { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from \"fs\";\nimport { join as join2 } from \"path\";\n\n// src/lib/synap-delegate.ts\nimport { existsSync, readFileSync } from \"fs\";\nimport { join, resolve } from \"path\";\nvar CANDIDATE_PATHS = [\n \"/opt/synap\",\n \"/opt/synap-backend\",\n \"/srv/synap\",\n \"/home/synap/synap-backend\",\n \"/root/synap-backend\"\n];\nfunction tryPath(root) {\n if (!existsSync(root)) return null;\n const script = join(root, \"synap\");\n if (!existsSync(script)) return null;\n const deployDir = join(root, \"deploy\");\n if (!existsSync(join(deployDir, \"docker-compose.yml\"))) return null;\n return { repoRoot: root, synapScript: script, deployDir };\n}\nfunction resolveSynapDelegate(cwd) {\n const cliOverride = process.env.SYNAP_CLI?.trim();\n if (cliOverride && existsSync(cliOverride)) {\n const root = resolve(cliOverride, \"..\");\n const d = tryPath(root);\n if (d) return d;\n }\n const envRoot = process.env.SYNAP_REPO_ROOT?.trim();\n if (envRoot) {\n const d = tryPath(envRoot);\n if (d) return d;\n }\n const statePath = join(cwd ?? process.cwd(), \".eve\", \"state.json\");\n if (existsSync(statePath)) {\n try {\n const state = JSON.parse(readFileSync(statePath, \"utf-8\"));\n const savedRoot = state?.installed?.synap?.config?.repoRoot;\n if (savedRoot) {\n const d = tryPath(savedRoot);\n if (d) return d;\n }\n } catch {\n }\n }\n for (const candidate of CANDIDATE_PATHS) {\n const d = tryPath(candidate);\n if (d) return d;\n }\n return null;\n}\n\n// src/lib/synap-cli-delegate.ts\nfunction toPodFqdn(input) {\n const trimmed = input.trim();\n if (!trimmed || trimmed === \"localhost\") return trimmed;\n if (/^\\d{1,3}(\\.\\d{1,3}){3}$/.test(trimmed)) return trimmed;\n if (trimmed.startsWith(\"pod.\")) return trimmed;\n return `pod.${trimmed}`;\n}\nfunction rewriteEnvDomain(envPath, fqdn) {\n if (!existsSync2(envPath)) return false;\n const current = readFileSync2(envPath, \"utf-8\");\n const existing = current.match(/^DOMAIN=(.*)$/m)?.[1]?.trim();\n if (existing === fqdn) return false;\n const next = current.match(/^DOMAIN=.*$/m) ? current.replace(/^DOMAIN=.*$/m, `DOMAIN=${fqdn}`) : `${current}${current.endsWith(\"\\n\") ? \"\" : \"\\n\"}DOMAIN=${fqdn}\n`;\n writeFileSync(envPath, next, { encoding: \"utf-8\", mode: 384 });\n return true;\n}\nvar DEFAULT_TIMEOUT_MS = 30 * 60 * 1e3;\nfunction refreshGitCheckout(repoRoot) {\n if (!existsSync2(`${repoRoot}/.git`)) return;\n const git = (args) => spawnSync(\"git\", [\"-C\", repoRoot, ...args], { stdio: [\"ignore\", \"pipe\", \"pipe\"], encoding: \"utf-8\" });\n git([\"fetch\", \"--quiet\"]);\n const ff = git([\"pull\", \"--ff-only\"]);\n if (ff.status === 0) return;\n const ref = (git([\"rev-parse\", \"--abbrev-ref\", \"--symbolic-full-name\", \"@{u}\"]).stdout ?? \"\").toString().trim();\n const isShallow = (git([\"rev-parse\", \"--is-shallow-repository\"]).stdout ?? \"\").toString().trim() === \"true\";\n let hasLocalCommits = false;\n if (!isShallow) {\n const aheadOut = ref ? git([\"rev-list\", \"--count\", `${ref}..HEAD`]) : null;\n hasLocalCommits = !aheadOut || aheadOut.status !== 0 || parseInt((aheadOut.stdout ?? \"\").toString().trim(), 10) > 0;\n }\n if (ref && !hasLocalCommits) {\n const reset = git([\"reset\", \"--hard\", ref]);\n if (reset.status === 0) {\n console.warn(\n ` Note: synap-backend at ${repoRoot} had drifted from ${ref} (couldn't fast-forward); hard-reset to upstream to keep deploy logic + kratos.yml generation current. Gitignored .env/kratos.yml/overrides and docker volumes were untouched.`\n );\n return;\n }\n }\n const detail = (ff.stderr ?? \"\").toString().trim() || \"rejected (non-fast-forward).\";\n console.warn(\n ` Warning: could not refresh synap-backend at ${repoRoot} \\u2014 ${detail}\n` + (hasLocalCommits ? ` The checkout has local commits; refusing to discard them. To force it to upstream (safe \\u2014 data lives in volumes + gitignored files):\n git -C ${repoRoot} fetch && git -C ${repoRoot} reset --hard @{u}\n` : ` Continuing with the on-disk version (may regenerate stale config). Fix: git -C ${repoRoot} fetch && git -C ${repoRoot} reset --hard @{u}\n`)\n );\n}\nfunction resolveExplicitRepo(repoRoot) {\n const synapScript = `${repoRoot}/synap`;\n const deployDir = `${repoRoot}/deploy`;\n if (!existsSync2(synapScript)) return null;\n if (!existsSync2(`${deployDir}/docker-compose.yml`)) return null;\n return { repoRoot, synapScript, deployDir };\n}\nfunction runSynapCli(subcommand, args = [], options = {}) {\n const paths = options.repoRoot ? resolveExplicitRepo(options.repoRoot) : resolveSynapDelegate();\n if (!paths) {\n return {\n ok: false,\n exitCode: -1,\n paths: null,\n stdout: \"\",\n stderr: options.repoRoot ? `synap CLI not found at ${options.repoRoot} \\u2014 expected ${options.repoRoot}/synap and ${options.repoRoot}/deploy/docker-compose.yml` : diagnoseMissingSynapCli()\n };\n }\n if (options.refreshGit) {\n refreshGitCheckout(paths.repoRoot);\n }\n if (options.domain) {\n const fqdn = toPodFqdn(options.domain);\n rewriteEnvDomain(join2(paths.deployDir, \".env\"), fqdn);\n }\n const env = {\n ...process.env,\n SYNAP_DEPLOY_DIR: paths.deployDir,\n SYNAP_ASSUME_YES: \"1\",\n SYNAP_NON_INTERACTIVE: \"1\",\n // Eve always provides the edge proxy (eve-legs-traefik on port 80/443).\n // Tell the synap CLI to skip its built-in Caddy so it doesn't fight\n // Traefik for port 80 and abort updates with \"port already allocated\".\n SYNAP_SKIP_EDGE: \"1\",\n // Belt-and-suspenders: pin the compose project name explicitly so eve\n // never accidentally creates `deploy_*` volumes (the cwd-basename\n // fallback). Pre-Phase-3 synap-backend installs don't have\n // `_resolve_compose_project_name`, so without this override they'd\n // silently use cwd basename → orphan volumes → postgres detonation.\n // Exception: honour an existing pin (operator chose differently).\n COMPOSE_PROJECT_NAME: process.env.COMPOSE_PROJECT_NAME ?? \"synap-backend\"\n };\n if (options.domain) {\n env.DOMAIN = toPodFqdn(options.domain);\n }\n const inherit = options.inherit !== false;\n const result = spawnSync(\"bash\", [paths.synapScript, subcommand, ...args], {\n cwd: paths.deployDir,\n env,\n stdio: inherit ? \"inherit\" : \"pipe\",\n timeout: DEFAULT_TIMEOUT_MS\n });\n return {\n ok: result.status === 0,\n exitCode: result.status ?? -1,\n paths,\n stdout: inherit ? \"\" : result.stdout?.toString() ?? \"\",\n stderr: inherit ? \"\" : result.stderr?.toString() ?? \"\"\n };\n}\nfunction diagnoseMissingSynapCli() {\n const candidate = process.env.SYNAP_REPO_ROOT?.trim() || \"/opt/synap-backend\";\n const hasFlatCompose = existsSync2(`${candidate}/docker-compose.yml`);\n const hasGit = existsSync2(`${candidate}/.git`);\n const hasScript = existsSync2(`${candidate}/synap`);\n if (hasFlatCompose && !hasGit && !hasScript) {\n return [\n \"synap CLI not found \\u2014 pre-cutover flat-layout install detected at \" + candidate + \".\",\n \"\",\n \"Eve no longer bundles its own compose file; the canonical synap-backend git checkout is required.\",\n \"Migrate (preserves docker volumes \\u2014 your data is safe):\",\n \"\",\n \" docker compose -f \" + candidate + \"/docker-compose.yml down\",\n \" sudo mv \" + candidate + \" \" + candidate + \".legacy\",\n \" sudo git clone --depth 1 https://github.com/synap-core/backend.git \" + candidate,\n \" sudo mv \" + candidate + \".legacy/.env \" + candidate + \"/deploy/.env\",\n \" sudo mv \" + candidate + \".legacy/docker-compose.override.yml \" + candidate + \"/deploy/ 2>/dev/null || true\",\n \" eve update synap # synap CLI now visible; runs canonical update + reconnects eve-network\",\n \"\",\n \"After verifying the pod is healthy, you can `sudo rm -rf \" + candidate + \".legacy`.\"\n ].join(\"\\n\");\n }\n return \"synap CLI not found at \" + candidate + \"/synap \\u2014 set SYNAP_REPO_ROOT, or run `eve install synap`.\";\n}\n\n// src/lib/pod-secrets-backup.ts\nimport { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from \"fs\";\nimport { readEveSecrets, writeEveSecrets } from \"@eve/dna\";\nvar POD_SECRET_KEYS = [\n \"POSTGRES_PASSWORD\",\n \"JWT_SECRET\",\n \"KRATOS_SECRETS_COOKIE\",\n \"KRATOS_SECRETS_CIPHER\",\n \"KRATOS_WEBHOOK_SECRET\",\n \"MINIO_ACCESS_KEY\",\n \"MINIO_SECRET_KEY\",\n \"TYPESENSE_API_KEY\",\n \"TYPESENSE_ADMIN_API_KEY\",\n \"ORY_HYDRA_SECRETS_SYSTEM\",\n \"SYNAP_SERVICE_ENCRYPTION_KEY\",\n \"PROVISIONING_TOKEN\"\n];\nfunction readEnvLine(content, key) {\n const m = content.match(new RegExp(`^${key}=(.*)$`, \"m\"));\n return m?.[1]?.trim() || void 0;\n}\nasync function backupPodSecrets(envPath) {\n if (!existsSync3(envPath)) return { captured: [] };\n const content = readFileSync3(envPath, \"utf-8\");\n const captured = [];\n const podSecrets = {};\n for (const key of POD_SECRET_KEYS) {\n const value = readEnvLine(content, key);\n if (value) {\n podSecrets[key] = value;\n captured.push(key);\n }\n }\n if (captured.length === 0) return { captured: [] };\n await writeEveSecrets({\n synap: {\n podSecrets: {\n ...podSecrets,\n backedUpAt: (/* @__PURE__ */ new Date()).toISOString()\n }\n }\n });\n return { captured };\n}\nasync function restorePodSecrets(envPath) {\n if (!existsSync3(envPath)) return { restored: [], reason: \".env does not exist\" };\n const secrets = await readEveSecrets();\n const backup = secrets?.synap?.podSecrets;\n if (!backup) return { restored: [], reason: \"no eve backup available\" };\n let content = readFileSync3(envPath, \"utf-8\");\n const restored = [];\n for (const key of POD_SECRET_KEYS) {\n const backupValue = backup[key];\n if (!backupValue) continue;\n const currentValue = readEnvLine(content, key);\n if (currentValue) continue;\n const lineExists = new RegExp(`^${key}=.*$`, \"m\").test(content);\n if (lineExists) {\n content = content.replace(new RegExp(`^${key}=.*$`, \"m\"), `${key}=${backupValue}`);\n } else {\n const sep = content.endsWith(\"\\n\") ? \"\" : \"\\n\";\n content = `${content}${sep}${key}=${backupValue}\n`;\n }\n restored.push(key);\n }\n if (restored.length > 0) {\n writeFileSync2(envPath, content, { encoding: \"utf-8\", mode: 384 });\n }\n return { restored };\n}\n\n// src/lib/synap-image-install.ts\nvar SYNAP_BACKEND_REPO = \"https://github.com/synap-core/backend.git\";\nfunction ensureSynapBackendCheckout(repoRoot) {\n if (existsSync4(join3(repoRoot, \".git\"))) return;\n mkdirSync(repoRoot, { recursive: true });\n const isEmpty = readdirSafe(repoRoot).length === 0;\n if (!isEmpty) {\n throw new Error(\n `Cannot clone synap-backend into non-empty non-git dir ${repoRoot}. Move existing files away or set SYNAP_REPO_ROOT to a different path.`\n );\n }\n const result = spawnSync2(\"git\", [\"clone\", \"--depth\", \"1\", SYNAP_BACKEND_REPO, repoRoot], {\n stdio: \"inherit\"\n });\n if (result.status !== 0) {\n throw new Error(`git clone ${SYNAP_BACKEND_REPO} failed (exit ${result.status})`);\n }\n}\nfunction readdirSafe(dir) {\n try {\n return readdirSync(dir);\n } catch {\n return [];\n }\n}\nfunction gen(bytes = 32) {\n return randomBytes(bytes).toString(\"hex\");\n}\nfunction getSynapBackendContainer() {\n return findComposeContainer(\"backend\");\n}\nfunction getSynapPodAdminContainer() {\n return findComposeContainer(\"pod-admin\");\n}\nfunction findComposeContainer(service) {\n try {\n const out = execSync(\n `docker ps --filter \"label=com.docker.compose.project=synap-backend\" --filter \"label=com.docker.compose.service=${service}\" --format \"{{.Names}}\"`,\n { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"ignore\"] }\n ).trim();\n return out.split(\"\\n\")[0]?.trim() || null;\n } catch {\n return null;\n }\n}\nfunction isBackendHealthy() {\n const name = getSynapBackendContainer();\n if (!name) return false;\n try {\n const out = execSync(\n `docker inspect --format \"{{.State.Health.Status}}\" ${name}`,\n { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"ignore\"] }\n ).trim();\n return out === \"healthy\";\n } catch {\n return false;\n }\n}\nfunction isCaddyOnlyFailure(stderr) {\n if (/synap-backend-caddy.*Bind for [0-9.:]*:80\\s+failed: port is already allocated/i.test(stderr)) {\n return true;\n }\n const caddy = findComposeContainer(\"caddy\");\n if (!caddy) return false;\n try {\n const status = execSync(\n `docker inspect --format \"{{.State.Status}}\" ${caddy}`,\n { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"ignore\"] }\n ).trim();\n return status === \"created\" || status === \"exited\";\n } catch {\n return false;\n }\n}\nfunction connectToEveNetwork(containerName, alias) {\n if (alias) {\n try {\n const inspect = execSync(\n `docker inspect --format \"{{json .NetworkSettings.Networks}}\" ${containerName}`,\n { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"ignore\"] }\n ).trim();\n const networks = JSON.parse(inspect);\n const eve = networks[\"eve-network\"];\n if (eve) {\n if ((eve.Aliases ?? []).includes(alias)) return;\n try {\n execSync(`docker network disconnect eve-network ${containerName}`, {\n stdio: [\"pipe\", \"pipe\", \"ignore\"]\n });\n } catch {\n }\n }\n } catch {\n }\n }\n const aliasArg = alias ? ` --alias ${alias}` : \"\";\n try {\n execSync(`docker network connect${aliasArg} eve-network ${containerName}`, {\n stdio: [\"pipe\", \"pipe\", \"ignore\"]\n });\n } catch {\n }\n}\nasync function installSynapFromImage(opts = {}) {\n const repoRoot = opts.deployDir ?? \"/opt/synap-backend\";\n const composeDir = join3(repoRoot, \"deploy\");\n let bareDomain = opts.domain ?? \"localhost\";\n if (bareDomain === \"localhost\") {\n const discovered = await discoverAndBackfillPodConfig(process.cwd());\n if (discovered.domain) {\n bareDomain = discovered.domain;\n }\n }\n const podDomain = toPodFqdn(bareDomain);\n const adminBootstrapMode = opts.adminBootstrapMode ?? \"token\";\n ensureSynapBackendCheckout(repoRoot);\n try {\n const r = ensureSynapLoopbackOverride(composeDir);\n if (r.outcome === \"wrote\") {\n console.log(` Wrote loopback compose override at ${r.path}`);\n } else {\n console.log(` Kept existing ${r.path} (${r.reason ?? \"user-owned\"})`);\n }\n } catch (err) {\n console.warn(` Could not write loopback override: ${err instanceof Error ? err.message : String(err)} (continuing \\u2014 CLI will use public URL)`);\n }\n const envPath = join3(composeDir, \".env\");\n const envExisted = existsSync4(envPath);\n const existingEnv = envExisted ? readFileSync4(envPath, \"utf-8\") : \"\";\n const existingToken = existingEnv.match(/^ADMIN_BOOTSTRAP_TOKEN=(.+)$/m)?.[1];\n const existingEmail = existingEnv.match(/^LETSENCRYPT_EMAIL=(.+)$/m)?.[1]?.trim();\n const email = opts.email?.trim() || existingEmail;\n const bootstrapToken = existingToken?.trim() ?? gen(16);\n if (envExisted) {\n reconcileEveEnv(envPath);\n const restored = await restorePodSecrets(envPath);\n if (restored.restored.length > 0) {\n console.log(` Restored ${restored.restored.length} pod secret(s) from eve backup: ${restored.restored.join(\", \")}`);\n }\n }\n const cliArgs = [\n \"--non-interactive\",\n \"--dir\",\n repoRoot,\n \"--domain\",\n podDomain,\n \"--admin-bootstrap-mode\",\n adminBootstrapMode\n ];\n if (opts.fromSource === true) cliArgs.push(\"--from-source\");\n else if (opts.fromSource === false) cliArgs.push(\"--from-image\");\n if (email) cliArgs.push(\"--email\", email);\n if (opts.adminEmail) cliArgs.push(\"--admin-email\", opts.adminEmail);\n if (opts.adminPassword) cliArgs.push(\"--admin-password\", opts.adminPassword);\n if (adminBootstrapMode === \"token\") cliArgs.push(\"--admin-bootstrap-token\", bootstrapToken);\n if (opts.withOpenclaw) cliArgs.push(\"--with-openclaw\");\n if (opts.withRsshub) cliArgs.push(\"--with-rsshub\");\n const cliResult = runSynapCli(\"install\", cliArgs, { repoRoot, refreshGit: true });\n if (!cliResult.ok) {\n if (isBackendHealthy() && isCaddyOnlyFailure(cliResult.stderr)) {\n console.warn(\n `\n \\u26A0 Caddy could not bind port 80 (eve-legs-traefik already owns it).\n Backend is healthy; the data plane is up. Synap install proceeding.\n To stop the spurious Caddy container: docker rm -f synap-backend-caddy-1\n`\n );\n } else {\n throw new Error(\n `synap install exited ${cliResult.exitCode}` + (cliResult.stderr ? `: ${cliResult.stderr}` : \"\")\n );\n }\n }\n try {\n const captured = await backupPodSecrets(envPath);\n if (captured.captured.length > 0) {\n console.log(` Backed up ${captured.captured.length} pod secret(s) to secrets.json:synap.podSecrets`);\n }\n } catch (err) {\n console.warn(` Could not back up pod secrets: ${err instanceof Error ? err.message : String(err)} (continuing \\u2014 recovery from .env loss won't be available until next install/update)`);\n }\n const containerName = getSynapBackendContainer();\n if (containerName) {\n connectToEveNetwork(containerName, \"eve-brain-synap\");\n console.log(` Connected ${containerName} \\u2192 eve-network (alias: eve-brain-synap)`);\n } else {\n console.warn(\" Backend container not found after synap install \\u2014 skipping eve-network attach\");\n }\n const podAdminContainer = getSynapPodAdminContainer();\n if (podAdminContainer) {\n connectToEveNetwork(podAdminContainer, \"eve-brain-pod-admin\");\n console.log(` Connected ${podAdminContainer} \\u2192 eve-network (alias: eve-brain-pod-admin)`);\n }\n for (const repo of [\"ghcr.io/synap-core/backend\", \"ghcr.io/synap-core/pod-agent\"]) {\n try {\n const r = pruneOldImagesForRepo(repo, 3);\n if (r.removed.length > 0) {\n console.log(` Pruned ${r.removed.length} old ${repo} image(s) (kept latest 3).`);\n }\n } catch {\n }\n }\n const finalEnv = existsSync4(envPath) ? readFileSync4(envPath, \"utf-8\") : \"\";\n const finalToken = finalEnv.match(/^ADMIN_BOOTSTRAP_TOKEN=(.+)$/m)?.[1]?.trim() ?? bootstrapToken;\n return { bootstrapToken: finalToken, deployDir: repoRoot, containerName };\n}\nfunction reconcileEveEnv(envPath) {\n if (!existsSync4(envPath)) return false;\n let content = readFileSync4(envPath, \"utf-8\");\n const legacy = content.match(/^KRATOS_CONFIG_DIR=\\.\\/config\\/kratos\\s*$/m);\n if (!legacy) return false;\n content = content.replace(/^KRATOS_CONFIG_DIR=\\.\\/config\\/kratos\\s*\\n?/m, \"\");\n writeFileSync3(envPath, content, { encoding: \"utf-8\", mode: 384 });\n console.log(\" reconcile-env: removed legacy KRATOS_CONFIG_DIR=./config/kratos (canonical layout uses ../kratos)\");\n return true;\n}\n\nexport {\n resolveSynapDelegate,\n toPodFqdn,\n runSynapCli,\n POD_SECRET_KEYS,\n backupPodSecrets,\n restorePodSecrets,\n installSynapFromImage,\n reconcileEveEnv\n};\n"],"mappings":"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","names":[]}
|