@intentic/machine 1.326.0 → 1.328.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 +387 -33
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +2 -0
- package/dist/commands.js.map +1 -1
- package/dist/daemon-base.d.ts.map +1 -1
- package/dist/daemon-base.js +2 -3
- package/dist/daemon-base.js.map +1 -1
- package/dist/device/audit.d.ts +2 -0
- package/dist/device/audit.d.ts.map +1 -1
- package/dist/device/audit.js +23 -3
- package/dist/device/audit.js.map +1 -1
- package/dist/device/commands.d.ts +4 -0
- package/dist/device/commands.d.ts.map +1 -1
- package/dist/device/commands.js +7 -2
- package/dist/device/commands.js.map +1 -1
- package/dist/device/connection.d.ts +12 -1
- package/dist/device/connection.d.ts.map +1 -1
- package/dist/device/connection.js +77 -3
- package/dist/device/connection.js.map +1 -1
- package/dist/device/grant.d.ts +52 -0
- package/dist/device/grant.d.ts.map +1 -1
- package/dist/device/loopback-catch.d.ts +1 -1
- package/dist/device/loopback-catch.d.ts.map +1 -1
- package/dist/device/loopback-catch.js +5 -30
- package/dist/device/loopback-catch.js.map +1 -1
- package/dist/device/mcp.d.ts.map +1 -1
- package/dist/device/mcp.js +190 -30
- package/dist/device/mcp.js.map +1 -1
- package/dist/device/router.d.ts +52 -0
- package/dist/device/router.d.ts.map +1 -1
- package/dist/device/router.js +21 -0
- package/dist/device/router.js.map +1 -1
- package/dist/device/sandbox-rounds/auto-backup.d.ts +3 -1
- package/dist/device/sandbox-rounds/auto-backup.d.ts.map +1 -1
- package/dist/device/sandbox-rounds/auto-backup.js +14 -5
- package/dist/device/sandbox-rounds/auto-backup.js.map +1 -1
- package/dist/device/sandbox-rounds/auto-prepare.d.ts +3 -1
- package/dist/device/sandbox-rounds/auto-prepare.d.ts.map +1 -1
- package/dist/device/sandbox-rounds/auto-prepare.js +3 -3
- package/dist/device/sandbox-rounds/auto-prepare.js.map +1 -1
- package/dist/device/sandbox-rounds/in-flight.d.ts.map +1 -0
- package/dist/device/sandbox-rounds/in-flight.js.map +1 -0
- package/dist/device/sandbox-rounds/keeper.d.ts +26 -4
- package/dist/device/sandbox-rounds/keeper.d.ts.map +1 -1
- package/dist/device/sandbox-rounds/keeper.js +129 -31
- package/dist/device/sandbox-rounds/keeper.js.map +1 -1
- package/dist/device/sandbox-rounds/probation-watch.d.ts.map +1 -1
- package/dist/device/sandbox-rounds/probation-watch.js.map +1 -1
- package/dist/device/tools/android-parse.d.ts +69 -0
- package/dist/device/tools/android-parse.d.ts.map +1 -0
- package/dist/device/tools/android-parse.js +391 -0
- package/dist/device/tools/android-parse.js.map +1 -0
- package/dist/device/tools/android.d.ts +71 -0
- package/dist/device/tools/android.d.ts.map +1 -0
- package/dist/device/tools/android.js +534 -0
- package/dist/device/tools/android.js.map +1 -0
- package/dist/device/tools/apps.d.ts +3 -3
- package/dist/device/tools/apps.d.ts.map +1 -1
- package/dist/device/tools/apps.js +16 -6
- package/dist/device/tools/apps.js.map +1 -1
- package/dist/device/tools/describe.d.ts.map +1 -1
- package/dist/device/tools/describe.js +10 -5
- package/dist/device/tools/describe.js.map +1 -1
- package/dist/device/tools/device.d.ts +9 -15
- package/dist/device/tools/device.d.ts.map +1 -1
- package/dist/device/tools/device.js +34 -58
- package/dist/device/tools/device.js.map +1 -1
- package/dist/device/tools/elements.d.ts +13 -0
- package/dist/device/tools/elements.d.ts.map +1 -0
- package/dist/device/tools/elements.js +84 -0
- package/dist/device/tools/elements.js.map +1 -0
- package/dist/device/tools/ic-binary.d.ts +1 -0
- package/dist/device/tools/ic-binary.d.ts.map +1 -1
- package/dist/device/tools/ic-binary.js +40 -17
- package/dist/device/tools/ic-binary.js.map +1 -1
- package/dist/device/tools/sandboxes.d.ts +1 -1
- package/dist/device/tools/sandboxes.d.ts.map +1 -1
- package/dist/device/tools/sandboxes.js +9 -8
- package/dist/device/tools/sandboxes.js.map +1 -1
- package/dist/device/tools/shell.js +1 -1
- package/dist/device/tools/shell.js.map +1 -1
- package/dist/device/tools/view.d.ts +4 -0
- package/dist/device/tools/view.d.ts.map +1 -0
- package/dist/device/tools/view.js +4 -0
- package/dist/device/tools/view.js.map +1 -0
- package/dist/environments/children.js +1 -1
- package/dist/environments/children.js.map +1 -1
- package/dist/environments/commands.js +1 -1
- package/dist/environments/commands.js.map +1 -1
- package/dist/environments/crossing.d.ts +1 -1
- package/dist/environments/crossing.d.ts.map +1 -1
- package/dist/environments/machine.js +1 -1
- package/dist/environments/machine.js.map +1 -1
- package/dist/environments/wsl.d.ts.map +1 -0
- package/dist/environments/wsl.js.map +1 -0
- package/dist/resident.d.ts.map +1 -1
- package/dist/resident.js +14 -8
- package/dist/resident.js.map +1 -1
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +16 -13
- package/dist/status.js.map +1 -1
- package/dist/sync/attach-commands.d.ts +39 -0
- package/dist/sync/attach-commands.d.ts.map +1 -0
- package/dist/sync/attach-commands.js +152 -0
- package/dist/sync/attach-commands.js.map +1 -0
- package/dist/sync/commands.d.ts +8 -4
- package/dist/sync/commands.d.ts.map +1 -1
- package/dist/sync/commands.js +137 -79
- package/dist/sync/commands.js.map +1 -1
- package/dist/sync/config.d.ts +43 -9
- package/dist/sync/config.d.ts.map +1 -1
- package/dist/sync/config.js +86 -41
- package/dist/sync/config.js.map +1 -1
- package/dist/sync/endpoint.d.ts +8 -1
- package/dist/sync/endpoint.d.ts.map +1 -1
- package/dist/sync/endpoint.js +41 -1
- package/dist/sync/endpoint.js.map +1 -1
- package/dist/sync/environment.d.ts +11 -0
- package/dist/sync/environment.d.ts.map +1 -0
- package/dist/sync/environment.js +48 -0
- package/dist/sync/environment.js.map +1 -0
- package/dist/sync/exec.d.ts +1 -0
- package/dist/sync/exec.d.ts.map +1 -1
- package/dist/sync/exec.js +4 -5
- package/dist/sync/exec.js.map +1 -1
- package/dist/sync/folders.d.ts +3 -2
- package/dist/sync/folders.d.ts.map +1 -1
- package/dist/sync/folders.js +55 -2
- package/dist/sync/folders.js.map +1 -1
- package/dist/sync/forget-command.d.ts +22 -0
- package/dist/sync/forget-command.d.ts.map +1 -0
- package/dist/sync/forget-command.js +95 -0
- package/dist/sync/forget-command.js.map +1 -0
- package/dist/sync/git-bridge.d.ts +6 -0
- package/dist/sync/git-bridge.d.ts.map +1 -1
- package/dist/sync/git-bridge.js +64 -1
- package/dist/sync/git-bridge.js.map +1 -1
- package/dist/sync/gone-watch.d.ts +18 -0
- package/dist/sync/gone-watch.d.ts.map +1 -0
- package/dist/sync/gone-watch.js +142 -0
- package/dist/sync/gone-watch.js.map +1 -0
- package/dist/sync/gone.d.ts +22 -0
- package/dist/sync/gone.d.ts.map +1 -0
- package/dist/sync/gone.js +60 -0
- package/dist/sync/gone.js.map +1 -0
- package/dist/sync/local-sandboxes.d.ts +6 -0
- package/dist/sync/local-sandboxes.d.ts.map +1 -0
- package/dist/sync/local-sandboxes.js +26 -0
- package/dist/sync/local-sandboxes.js.map +1 -0
- package/dist/sync/mirror.d.ts +7 -0
- package/dist/sync/mirror.d.ts.map +1 -1
- package/dist/sync/mirror.js +434 -111
- package/dist/sync/mirror.js.map +1 -1
- package/dist/sync/mutagen.d.ts +50 -18
- package/dist/sync/mutagen.d.ts.map +1 -1
- package/dist/sync/mutagen.js +294 -80
- package/dist/sync/mutagen.js.map +1 -1
- package/dist/sync/project/adopt.d.ts +18 -0
- package/dist/sync/project/adopt.d.ts.map +1 -0
- package/dist/sync/project/adopt.js +86 -0
- package/dist/sync/project/adopt.js.map +1 -0
- package/dist/sync/{project-commands.d.ts → project/project-commands.d.ts} +4 -1
- package/dist/sync/project/project-commands.d.ts.map +1 -0
- package/dist/sync/{project-commands.js → project/project-commands.js} +10 -10
- package/dist/sync/project/project-commands.js.map +1 -0
- package/dist/sync/project/project-delivery.d.ts +23 -0
- package/dist/sync/project/project-delivery.d.ts.map +1 -0
- package/dist/sync/project/project-delivery.js +315 -0
- package/dist/sync/project/project-delivery.js.map +1 -0
- package/dist/sync/{project-files.d.ts → project/project-files.d.ts} +1 -1
- package/dist/sync/project/project-files.d.ts.map +1 -0
- package/dist/sync/{project-files.js → project/project-files.js} +5 -2
- package/dist/sync/project/project-files.js.map +1 -0
- package/dist/sync/{project-local.d.ts → project/project-local.d.ts} +1 -1
- package/dist/sync/project/project-local.d.ts.map +1 -0
- package/dist/sync/{project-local.js → project/project-local.js} +1 -1
- package/dist/sync/project/project-local.js.map +1 -0
- package/dist/sync/{project-remote.d.ts → project/project-remote.d.ts} +1 -1
- package/dist/sync/project/project-remote.d.ts.map +1 -0
- package/dist/sync/{project-remote.js → project/project-remote.js} +1 -1
- package/dist/sync/project/project-remote.js.map +1 -0
- package/dist/sync/{project-transfer.d.ts → project/project-transfer.d.ts} +20 -6
- package/dist/sync/project/project-transfer.d.ts.map +1 -0
- package/dist/sync/{project-transfer.js → project/project-transfer.js} +26 -23
- package/dist/sync/project/project-transfer.js.map +1 -0
- package/dist/sync/report.d.ts.map +1 -1
- package/dist/sync/report.js +13 -7
- package/dist/sync/report.js.map +1 -1
- package/dist/sync/restore-points.d.ts +15 -11
- package/dist/sync/restore-points.d.ts.map +1 -1
- package/dist/sync/restore-points.js +25 -24
- package/dist/sync/restore-points.js.map +1 -1
- package/dist/sync/retire.d.ts +14 -0
- package/dist/sync/retire.d.ts.map +1 -0
- package/dist/sync/retire.js +66 -0
- package/dist/sync/retire.js.map +1 -0
- package/dist/sync/siblings.d.ts +27 -0
- package/dist/sync/siblings.d.ts.map +1 -0
- package/dist/sync/siblings.js +112 -0
- package/dist/sync/siblings.js.map +1 -0
- package/dist/sync/ssh.d.ts +1 -0
- package/dist/sync/ssh.d.ts.map +1 -1
- package/dist/sync/ssh.js +37 -23
- package/dist/sync/ssh.js.map +1 -1
- package/dist/sync/tunnel.d.ts +6 -2
- package/dist/sync/tunnel.d.ts.map +1 -1
- package/dist/sync/tunnel.js +45 -48
- package/dist/sync/tunnel.js.map +1 -1
- package/dist/upkeep/doctor.d.ts +5 -0
- package/dist/upkeep/doctor.d.ts.map +1 -0
- package/dist/upkeep/doctor.js +73 -0
- package/dist/upkeep/doctor.js.map +1 -0
- package/dist/upkeep/entry.d.ts +26 -0
- package/dist/upkeep/entry.d.ts.map +1 -0
- package/dist/upkeep/entry.js +2 -0
- package/dist/upkeep/entry.js.map +1 -0
- package/dist/upkeep/login.d.ts +15 -0
- package/dist/upkeep/login.d.ts.map +1 -0
- package/dist/upkeep/login.js +128 -0
- package/dist/upkeep/login.js.map +1 -0
- package/dist/upkeep/manifest.d.ts +3 -0
- package/dist/upkeep/manifest.d.ts.map +1 -0
- package/dist/upkeep/manifest.js +6 -0
- package/dist/upkeep/manifest.js.map +1 -0
- package/dist/upkeep/reconcile.d.ts +45 -0
- package/dist/upkeep/reconcile.d.ts.map +1 -0
- package/dist/upkeep/reconcile.js +159 -0
- package/dist/upkeep/reconcile.js.map +1 -0
- package/dist/upkeep/retention.d.ts +10 -0
- package/dist/upkeep/retention.d.ts.map +1 -0
- package/dist/upkeep/retention.js +71 -0
- package/dist/upkeep/retention.js.map +1 -0
- package/dist/upkeep/retired.d.ts +12 -0
- package/dist/upkeep/retired.d.ts.map +1 -0
- package/dist/upkeep/retired.js +125 -0
- package/dist/upkeep/retired.js.map +1 -0
- package/dist/upkeep/second-ic.d.ts +4 -0
- package/dist/upkeep/second-ic.d.ts.map +1 -0
- package/dist/upkeep/second-ic.js +36 -0
- package/dist/upkeep/second-ic.js.map +1 -0
- package/dist/upkeep/trash.d.ts +9 -0
- package/dist/upkeep/trash.d.ts.map +1 -0
- package/dist/upkeep/trash.js +77 -0
- package/dist/upkeep/trash.js.map +1 -0
- package/dist/watchdog.d.ts +13 -0
- package/dist/watchdog.d.ts.map +1 -0
- package/dist/watchdog.js +38 -0
- package/dist/watchdog.js.map +1 -0
- package/package.json +9 -9
- package/dist/autostart/legacy-autostart.d.ts +0 -4
- package/dist/autostart/legacy-autostart.d.ts.map +0 -1
- package/dist/autostart/legacy-autostart.js +0 -33
- package/dist/autostart/legacy-autostart.js.map +0 -1
- package/dist/device/tools/in-flight.d.ts.map +0 -1
- package/dist/device/tools/in-flight.js.map +0 -1
- package/dist/sync/project-commands.d.ts.map +0 -1
- package/dist/sync/project-commands.js.map +0 -1
- package/dist/sync/project-files.d.ts.map +0 -1
- package/dist/sync/project-files.js.map +0 -1
- package/dist/sync/project-local.d.ts.map +0 -1
- package/dist/sync/project-local.js.map +0 -1
- package/dist/sync/project-remote.d.ts.map +0 -1
- package/dist/sync/project-remote.js.map +0 -1
- package/dist/sync/project-transfer.d.ts.map +0 -1
- package/dist/sync/project-transfer.js.map +0 -1
- package/dist/wsl.d.ts.map +0 -1
- package/dist/wsl.js.map +0 -1
- /package/dist/device/{tools → sandbox-rounds}/in-flight.d.ts +0 -0
- /package/dist/device/{tools → sandbox-rounds}/in-flight.js +0 -0
- /package/dist/{wsl.d.ts → environments/wsl.d.ts} +0 -0
- /package/dist/{wsl.js → environments/wsl.js} +0 -0
package/dist/sync/mutagen.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import { chmod, rm } from "node:fs/promises";
|
|
4
5
|
import { join } from "node:path";
|
|
@@ -8,62 +9,149 @@ import { STATE_DIR, WORKSPACE_ROOT } from "@intentic/constants";
|
|
|
8
9
|
import { clearWindowsRunValue, quotedCommandLine, setWindowsRunValue, stubCommand, windowsLaunchStub, } from "@intentic/local-agent";
|
|
9
10
|
import { binDir } from "../config.js";
|
|
10
11
|
import { archToken, download, exe, osToken, renameIfPresent } from "../release.js";
|
|
11
|
-
import {
|
|
12
|
+
import { adoptFolder } from "./project/adopt.js";
|
|
13
|
+
import { clearAdoptFolder, isProjectPairing, mutagenDaemonLogPath, pairingKey, pairingRemoteDir, projectDirection } from "./config.js";
|
|
12
14
|
import { dockerEndpointAnswers, liveIdentity, mutagenForwardUrl, mutagenUrl, pairingEndpoint } from "./endpoint.js";
|
|
15
|
+
import { labelValue, sessionOwner } from "./environment.js";
|
|
13
16
|
import { runProcess } from "./exec.js";
|
|
17
|
+
import { projectShell, realProjectRunner, sandboxCopy } from "./project/project-remote.js";
|
|
14
18
|
import { clearConflictResidue, sweepDerivedResidue } from "./residue.js";
|
|
15
19
|
import { BACKUP_IGNORES, ignoresFor, mutagenSshPath, sanitizeId, sshTransportAnswers } from "./ssh.js";
|
|
16
20
|
import { deviceSymlinks } from "./symlinks.js";
|
|
17
21
|
const MUTAGEN_VERSION = "0.18.1";
|
|
22
|
+
export const MUTAGEN_CALL_TIMEOUT_MS = 60_000;
|
|
23
|
+
export const MUTAGEN_CREATE_TIMEOUT_MS = 5 * 60_000;
|
|
18
24
|
const SESSION_PREFIX = "intentic-";
|
|
19
|
-
export const
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
export const OWNER_LABEL = "intentic-owner";
|
|
26
|
+
const SANDBOX_LABEL = "intentic-sandbox";
|
|
27
|
+
const KIND_LABEL = "intentic-kind";
|
|
28
|
+
export const sessionLabels = (sandboxId, kind, owner = sessionOwner()) => ({
|
|
29
|
+
owner,
|
|
30
|
+
sandbox: labelValue(sanitizeId(sandboxId)),
|
|
31
|
+
kind,
|
|
32
|
+
});
|
|
33
|
+
export const labelArgs = (labels) => labels === undefined
|
|
34
|
+
? []
|
|
35
|
+
: ["--label", `${OWNER_LABEL}=${labels.owner}`, "--label", `${SANDBOX_LABEL}=${labels.sandbox}`, "--label", `${KIND_LABEL}=${labels.kind}`];
|
|
36
|
+
export const ownershipOf = (labels, owner) => {
|
|
37
|
+
const said = labels?.[OWNER_LABEL];
|
|
38
|
+
return said === undefined || said === "" ? "unlabelled" : said === owner ? "ours" : "theirs";
|
|
39
|
+
};
|
|
40
|
+
export const sessionName = (key) => {
|
|
41
|
+
const at = key.lastIndexOf("~");
|
|
42
|
+
if (at === -1) {
|
|
43
|
+
return `${SESSION_PREFIX}${sanitizeId(key)}`;
|
|
44
|
+
}
|
|
45
|
+
const name = key.slice(at + 1);
|
|
46
|
+
const hash = createHash("sha256").update(name).digest("hex").slice(0, 8);
|
|
47
|
+
return `${SESSION_PREFIX}${sanitizeId(key.slice(0, at))}--${sanitizeId(name)}-${hash}`;
|
|
48
|
+
};
|
|
49
|
+
export const backupSessionName = (key) => `${sessionName(key)}-state`;
|
|
50
|
+
export const syncSessionNames = (pairing) => {
|
|
51
|
+
if (pairing.projectsHost === true) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const key = pairingKey(pairing);
|
|
55
|
+
return isProjectPairing(pairing) ? [sessionName(key)] : [sessionName(key), backupSessionName(key)];
|
|
56
|
+
};
|
|
22
57
|
const FORWARD_PREFIX = "intentic-fwd-";
|
|
23
58
|
export const forwardSessionName = (sandboxId, port) => `${FORWARD_PREFIX}${sanitizeId(sandboxId)}-${port}`;
|
|
24
|
-
const
|
|
59
|
+
const present = (text) => (text === undefined || text === "" ? undefined : text);
|
|
60
|
+
export const listedSessions = (listed) => listed
|
|
61
|
+
.split(/\s+/)
|
|
62
|
+
.filter((token) => token !== "")
|
|
63
|
+
.map((token) => {
|
|
64
|
+
const [name = "", owner, identifier] = token.split("@");
|
|
65
|
+
return { name, owner: present(owner), identifier: present(identifier) };
|
|
66
|
+
});
|
|
67
|
+
const isOurs = (session, prefix, owner) => session.name.startsWith(prefix) && (session.owner === undefined || owner === undefined || session.owner === owner);
|
|
68
|
+
const oursIn = (listed, prefix, owner) => listedSessions(listed)
|
|
69
|
+
.filter((session) => isOurs(session, prefix, owner))
|
|
70
|
+
.map((session) => session.name);
|
|
71
|
+
export const terminateTargetsIn = (listed, names, prefix, owner) => {
|
|
72
|
+
const wanted = new Set(names);
|
|
73
|
+
return listedSessions(listed)
|
|
74
|
+
.filter((session) => wanted.has(session.name) && isOurs(session, prefix, owner))
|
|
75
|
+
.map((session) => session.identifier ?? session.name)
|
|
76
|
+
.filter((target, index, all) => all.indexOf(target) === index);
|
|
77
|
+
};
|
|
25
78
|
const FORWARD_NAME = new RegExp(`^${FORWARD_PREFIX}(.+)-(\\d+)$`);
|
|
26
|
-
export const parseForwardNames = (listed, sandboxId) => {
|
|
27
|
-
const names = oursIn(listed, FORWARD_PREFIX);
|
|
79
|
+
export const parseForwardNames = (listed, sandboxId, owner) => {
|
|
80
|
+
const names = oursIn(listed, FORWARD_PREFIX, owner);
|
|
28
81
|
if (sandboxId === undefined) {
|
|
29
82
|
return names;
|
|
30
83
|
}
|
|
31
84
|
const wanted = sanitizeId(sandboxId);
|
|
32
85
|
return names.filter((name) => FORWARD_NAME.exec(name)?.[1] === wanted);
|
|
33
86
|
};
|
|
34
|
-
export const parseOrphanForwardNames = (listed, keptSandboxIds) => {
|
|
87
|
+
export const parseOrphanForwardNames = (listed, keptSandboxIds, owner) => {
|
|
35
88
|
const kept = new Set(keptSandboxIds.map(sanitizeId));
|
|
36
|
-
return parseForwardNames(listed).filter((name) => {
|
|
37
|
-
const
|
|
38
|
-
return
|
|
89
|
+
return parseForwardNames(listed, undefined, owner).filter((name) => {
|
|
90
|
+
const held = FORWARD_NAME.exec(name)?.[1];
|
|
91
|
+
return held === undefined || !kept.has(held);
|
|
39
92
|
});
|
|
40
93
|
};
|
|
41
|
-
export const parseOrphanSyncNames = (listed, keep) => {
|
|
94
|
+
export const parseOrphanSyncNames = (listed, keep, owner) => {
|
|
42
95
|
const kept = new Set(keep);
|
|
43
|
-
return oursIn(listed, SESSION_PREFIX).filter((name) => !kept.has(name));
|
|
96
|
+
return oursIn(listed, SESSION_PREFIX, owner).filter((name) => !kept.has(name));
|
|
97
|
+
};
|
|
98
|
+
const LIST_TEMPLATE = `{{range .}}{{.Name}}@{{index .Labels "${OWNER_LABEL}"}}@{{.Identifier}} {{end}}`;
|
|
99
|
+
const VERSION_MISMATCH = /version mismatch/i;
|
|
100
|
+
const DAEMON_RESTART_MS = 30 * 60_000;
|
|
101
|
+
let daemonRestartedAt = Number.NEGATIVE_INFINITY;
|
|
102
|
+
let daemonLog;
|
|
103
|
+
export const sayDaemonEventsTo = (log) => {
|
|
104
|
+
daemonLog = log;
|
|
105
|
+
};
|
|
106
|
+
export const restartsDaemonFor = (stderr, now, lastRestart) => VERSION_MISMATCH.test(stderr) && now - lastRestart >= DAEMON_RESTART_MS;
|
|
107
|
+
const recoverDaemon = (mutagen, stderr) => {
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
if (!restartsDaemonFor(stderr, now, daemonRestartedAt)) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
daemonRestartedAt = now;
|
|
113
|
+
spawnSync(mutagen, ["daemon", "stop"], { stdio: "ignore", windowsHide: true, timeout: MUTAGEN_CALL_TIMEOUT_MS });
|
|
114
|
+
const started = spawnSync(mutagen, ["daemon", "start"], { stdio: "ignore", windowsHide: true, timeout: MUTAGEN_CALL_TIMEOUT_MS });
|
|
115
|
+
daemonLog?.(`the Mutagen daemon running here is another version than this agent's (${mutagen}); ${started.status === 0 ? "restarted it with this agent's copy" : "could not restart it"}. Its sessions are kept on disk and come back with it.`);
|
|
116
|
+
return started.status === 0;
|
|
117
|
+
};
|
|
118
|
+
const listCall = (mutagen, args) => {
|
|
119
|
+
const run = () => spawnSync(mutagen, [...args], { encoding: "utf8", windowsHide: true, timeout: MUTAGEN_CALL_TIMEOUT_MS });
|
|
120
|
+
const first = run();
|
|
121
|
+
if (first.status === 0) {
|
|
122
|
+
return first.stdout;
|
|
123
|
+
}
|
|
124
|
+
if (!recoverDaemon(mutagen, first.stderr ?? "")) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
const again = run();
|
|
128
|
+
return again.status === 0 ? again.stdout : undefined;
|
|
44
129
|
};
|
|
45
|
-
const listSessionNames = (mutagen, kind) =>
|
|
46
|
-
|
|
47
|
-
|
|
130
|
+
const listSessionNames = (mutagen, kind) => listCall(mutagen, [kind, "list", "--template", LIST_TEMPLATE]);
|
|
131
|
+
export const ourForwardSessions = (mutagen, sandboxId) => {
|
|
132
|
+
const listed = listSessionNames(mutagen, "forward");
|
|
133
|
+
return listed === undefined ? undefined : parseForwardNames(listed, sandboxId, sessionOwner());
|
|
48
134
|
};
|
|
49
|
-
export const
|
|
50
|
-
export const parseForwardPorts = (listed, sandboxId) => parseForwardNames(listed, sandboxId).flatMap((name) => {
|
|
135
|
+
export const parseForwardPorts = (listed, sandboxId, owner) => parseForwardNames(listed, sandboxId, owner).flatMap((name) => {
|
|
51
136
|
const port = Number(FORWARD_NAME.exec(name)?.[2]);
|
|
52
137
|
return Number.isInteger(port) ? [port] : [];
|
|
53
138
|
});
|
|
54
|
-
export const forwardedPorts = (mutagen, sandboxId) =>
|
|
55
|
-
const
|
|
139
|
+
export const forwardedPorts = (mutagen, sandboxId) => {
|
|
140
|
+
const listed = listSessionNames(mutagen, "forward");
|
|
141
|
+
return listed === undefined ? undefined : parseForwardPorts(listed, sandboxId, sessionOwner());
|
|
142
|
+
};
|
|
56
143
|
export const mutagenForwardArgs = (args) => [
|
|
57
144
|
"forward",
|
|
58
145
|
"create",
|
|
59
146
|
"--name",
|
|
60
147
|
args.name,
|
|
148
|
+
...labelArgs(args.labels),
|
|
61
149
|
`tcp:127.0.0.1:${args.port}`,
|
|
62
150
|
mutagenForwardUrl(args.remote, `tcp:${args.host.includes(":") ? `[${args.host}]` : args.host}:${args.port}`),
|
|
63
151
|
];
|
|
64
152
|
export const syncMode = (pairing) => isProjectPairing(pairing) && projectDirection(pairing) === "to-sandbox" ? "one-way-safe" : "two-way-safe";
|
|
65
|
-
export const sessionSpec = (pairing, symlinks) => ({
|
|
66
|
-
name: sessionName(pairing
|
|
153
|
+
export const sessionSpec = (pairing, symlinks, owner) => ({
|
|
154
|
+
name: sessionName(pairingKey(pairing)),
|
|
67
155
|
localDir: pairing.localDir,
|
|
68
156
|
remote: pairingEndpoint(pairing),
|
|
69
157
|
remoteDir: pairingRemoteDir(pairing),
|
|
@@ -72,9 +160,10 @@ export const sessionSpec = (pairing, symlinks) => ({
|
|
|
72
160
|
from: "local",
|
|
73
161
|
symlinks,
|
|
74
162
|
pollSeconds: SANDBOX_POLL_SECONDS,
|
|
163
|
+
...(owner === undefined ? {} : { labels: sessionLabels(pairing.sandboxId, "sync", owner) }),
|
|
75
164
|
});
|
|
76
|
-
const backupSpec = (pairing, symlinks) => ({
|
|
77
|
-
name: backupSessionName(pairing
|
|
165
|
+
const backupSpec = (pairing, symlinks, owner) => ({
|
|
166
|
+
name: backupSessionName(pairingKey(pairing)),
|
|
78
167
|
localDir: join(pairing.localDir, STATE_DIR),
|
|
79
168
|
remote: pairingEndpoint(pairing),
|
|
80
169
|
remoteDir: `${WORKSPACE_ROOT}/${STATE_DIR}`,
|
|
@@ -83,6 +172,7 @@ const backupSpec = (pairing, symlinks) => ({
|
|
|
83
172
|
from: "sandbox",
|
|
84
173
|
symlinks,
|
|
85
174
|
pollSeconds: BACKUP_POLL_SECONDS,
|
|
175
|
+
...(owner === undefined ? {} : { labels: sessionLabels(pairing.sandboxId, "state", owner) }),
|
|
86
176
|
});
|
|
87
177
|
const SANDBOX_POLL_SECONDS = 2;
|
|
88
178
|
const SANDBOX_WATCH_MODE = "force-poll";
|
|
@@ -99,6 +189,7 @@ export const mutagenCreateArgs = (spec, paused) => {
|
|
|
99
189
|
spec.name,
|
|
100
190
|
"--sync-mode",
|
|
101
191
|
spec.mode,
|
|
192
|
+
...labelArgs(spec.labels),
|
|
102
193
|
"--symlink-mode",
|
|
103
194
|
spec.symlinks,
|
|
104
195
|
...(paused ? ["--paused"] : []),
|
|
@@ -117,8 +208,18 @@ export const liveProtocol = (end) => end.protocol ?? (end.host === undefined ? "
|
|
|
117
208
|
const readSessions = (mutagen, name) => listSessions(mutagen, [name]);
|
|
118
209
|
export const readAllSessions = (mutagen) => listSessions(mutagen, []);
|
|
119
210
|
const listSessions = (mutagen, names) => {
|
|
120
|
-
const
|
|
121
|
-
|
|
211
|
+
const listed = listCall(mutagen, ["sync", "list", "--template", "{{json .}}"]);
|
|
212
|
+
if (listed === undefined) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
let sessions;
|
|
216
|
+
try {
|
|
217
|
+
sessions = JSON.parse(listed) ?? [];
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
return names.length === 0 ? sessions : sessions.filter((session) => session.name !== undefined && names.includes(session.name));
|
|
122
223
|
};
|
|
123
224
|
export const sessionsByName = (sessions) => {
|
|
124
225
|
const byName = new Map();
|
|
@@ -168,11 +269,15 @@ const conflictedPath = (conflict) => {
|
|
|
168
269
|
};
|
|
169
270
|
};
|
|
170
271
|
export const readSessionConflicts = (mutagen, name) => {
|
|
171
|
-
const
|
|
272
|
+
const sessions = readSessions(mutagen, name);
|
|
273
|
+
if (sessions === undefined) {
|
|
274
|
+
return "unanswered";
|
|
275
|
+
}
|
|
276
|
+
const session = sessions[0];
|
|
172
277
|
return session === undefined ? undefined : { ignores: session.ignore.paths ?? [], conflicts: (session.conflicts ?? []).map(conflictedPath) };
|
|
173
278
|
};
|
|
174
279
|
export const flushSession = async (mutagen, name) => {
|
|
175
|
-
await runProcess(mutagen, ["sync", "flush", "--skip-wait", name]);
|
|
280
|
+
await runProcess(mutagen, ["sync", "flush", "--skip-wait", name], { timeoutMs: MUTAGEN_CALL_TIMEOUT_MS });
|
|
176
281
|
};
|
|
177
282
|
export const conflictsFrom = (session) => {
|
|
178
283
|
const listed = session.conflicts ?? [];
|
|
@@ -180,7 +285,7 @@ export const conflictsFrom = (session) => {
|
|
|
180
285
|
const count = listed.length + (Number.isFinite(excluded) ? excluded : 0);
|
|
181
286
|
return count === 0 ? undefined : { count, paths: listed.slice(0, CONFLICT_PATHS_MAX).map(conflictedPath) };
|
|
182
287
|
};
|
|
183
|
-
export const readSessionState = (mutagen, name) => sessionStateOf(readSessions(mutagen, name)[0]);
|
|
288
|
+
export const readSessionState = (mutagen, name) => sessionStateOf(readSessions(mutagen, name)?.[0]);
|
|
184
289
|
export const sessionStateOf = (session) => {
|
|
185
290
|
if (session === undefined) {
|
|
186
291
|
return { exists: false };
|
|
@@ -195,41 +300,50 @@ export const sessionStateOf = (session) => {
|
|
|
195
300
|
};
|
|
196
301
|
};
|
|
197
302
|
export const existingSyncSessions = (mutagen, names) => {
|
|
198
|
-
const listed =
|
|
199
|
-
|
|
303
|
+
const listed = listSessionNames(mutagen, "sync");
|
|
304
|
+
if (listed === undefined) {
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
const held = new Set(oursIn(listed, SESSION_PREFIX, sessionOwner()));
|
|
308
|
+
return names.filter((name) => held.has(name));
|
|
200
309
|
};
|
|
201
310
|
export const pauseRunningSync = (mutagen, pairing) => {
|
|
202
311
|
if (pairing.mode !== "sync") {
|
|
203
312
|
return false;
|
|
204
313
|
}
|
|
205
314
|
const names = existingSyncSessions(mutagen, syncSessionNames(pairing));
|
|
206
|
-
if (names.every((name) => readSessionState(mutagen, name).paused === true)) {
|
|
315
|
+
if (names === undefined || names.every((name) => readSessionState(mutagen, name).paused === true)) {
|
|
207
316
|
return false;
|
|
208
317
|
}
|
|
209
|
-
const result = spawnSync(mutagen, ["sync", "pause", ...names], { stdio: "ignore", windowsHide: true });
|
|
318
|
+
const result = spawnSync(mutagen, ["sync", "pause", ...names], { stdio: "ignore", windowsHide: true, timeout: MUTAGEN_CALL_TIMEOUT_MS });
|
|
210
319
|
return result.status === 0;
|
|
211
320
|
};
|
|
212
321
|
export const pauseUnreachableSync = pauseRunningSync;
|
|
213
322
|
export const resumeSwapPausedSync = (mutagen, pairing) => {
|
|
214
323
|
const names = existingSyncSessions(mutagen, syncSessionNames(pairing));
|
|
215
|
-
if (names.length === 0) {
|
|
324
|
+
if (names === undefined || names.length === 0) {
|
|
216
325
|
return false;
|
|
217
326
|
}
|
|
218
|
-
return spawnSync(mutagen, ["sync", "resume", ...names], { stdio: "ignore", windowsHide: true }).status === 0;
|
|
327
|
+
return spawnSync(mutagen, ["sync", "resume", ...names], { stdio: "ignore", windowsHide: true, timeout: MUTAGEN_CALL_TIMEOUT_MS }).status === 0;
|
|
219
328
|
};
|
|
220
329
|
export const resumeAutoPausedSync = (mutagen, pairing) => {
|
|
221
330
|
if (pairing.mode !== "sync" || pairing.fileSyncAutoPaused !== true) {
|
|
222
331
|
return false;
|
|
223
332
|
}
|
|
224
333
|
const names = existingSyncSessions(mutagen, syncSessionNames(pairing));
|
|
225
|
-
if (names.length === 0) {
|
|
334
|
+
if (names === undefined || names.length === 0) {
|
|
226
335
|
return false;
|
|
227
336
|
}
|
|
228
|
-
const result = spawnSync(mutagen, ["sync", "resume", ...names], { stdio: "ignore", windowsHide: true });
|
|
337
|
+
const result = spawnSync(mutagen, ["sync", "resume", ...names], { stdio: "ignore", windowsHide: true, timeout: MUTAGEN_CALL_TIMEOUT_MS });
|
|
229
338
|
return result.status === 0;
|
|
230
339
|
};
|
|
340
|
+
export const resumeWatcherPausedSync = (mutagen, pairing) => resumeSwapPausedSync(mutagen, pairing);
|
|
231
341
|
export const sameEnds = (session, spec) => {
|
|
232
|
-
const local = {
|
|
342
|
+
const local = {
|
|
343
|
+
protocol: "local",
|
|
344
|
+
host: undefined,
|
|
345
|
+
path: spec.localDir,
|
|
346
|
+
};
|
|
233
347
|
const remote = { ...liveIdentity(spec.remote), path: spec.remoteDir };
|
|
234
348
|
const [alpha, beta] = spec.from === "local" ? [local, remote] : [remote, local];
|
|
235
349
|
const matches = (live, wanted) => live.path === wanted.path && live.host === wanted.host && liveProtocol(live) === wanted.protocol;
|
|
@@ -248,41 +362,83 @@ export const sessionMatchesSpec = (session, spec) => {
|
|
|
248
362
|
(session.ignore.paths ?? []).join("\n") === spec.ignores.join("\n") &&
|
|
249
363
|
(session.symlink?.mode ?? "portable") === spec.symlinks);
|
|
250
364
|
};
|
|
251
|
-
export const
|
|
365
|
+
export const relabelBudget = (count) => {
|
|
366
|
+
let left = count;
|
|
367
|
+
return {
|
|
368
|
+
take: () => {
|
|
369
|
+
if (left <= 0) {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
left -= 1;
|
|
373
|
+
return true;
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
};
|
|
377
|
+
const NO_RELABELS = { take: () => false };
|
|
378
|
+
export const ensureSyncSession = async (mutagen, pairing, log, relabels = NO_RELABELS) => {
|
|
252
379
|
if (pairing.mode !== "sync" || pairing.localDir === undefined) {
|
|
253
380
|
return true;
|
|
254
381
|
}
|
|
255
382
|
const held = { ...pairing, localDir: pairing.localDir };
|
|
383
|
+
if (pairing.adoptFolder === true && !(await adoptBeforeFirstSession(mutagen, held, log))) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
256
386
|
const symlinks = await deviceSymlinks();
|
|
257
387
|
if (symlinks.refusal !== undefined) {
|
|
258
|
-
log(`${pairing
|
|
388
|
+
log(`${pairingKey(pairing)}: this device cannot create symbolic links (${symlinks.refusal}), so its file sync leaves them out instead of failing on each one every cycle. On Windows, turning on Developer Mode lets the next start of this agent carry them.`);
|
|
259
389
|
}
|
|
260
390
|
let converged = true;
|
|
261
391
|
const answers = transportAnswers(pairing);
|
|
262
|
-
for (const spec of sessionSpecs(held, symlinks.mode)) {
|
|
263
|
-
converged = (await convergeSession(mutagen, spec, log, answers)) && converged;
|
|
392
|
+
for (const spec of sessionSpecs(held, symlinks.mode, sessionOwner())) {
|
|
393
|
+
converged = (await convergeSession(mutagen, spec, log, answers, relabels)) && converged;
|
|
264
394
|
}
|
|
265
395
|
retireStrayBackup(mutagen, pairing, log);
|
|
266
396
|
return converged;
|
|
267
397
|
};
|
|
268
|
-
|
|
269
|
-
|
|
398
|
+
const adoptBeforeFirstSession = async (mutagen, pairing, log) => {
|
|
399
|
+
const key = pairingKey(pairing);
|
|
400
|
+
const existing = existingSyncSessions(mutagen, [sessionName(key)]);
|
|
401
|
+
if (existing === undefined) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
if (existing.length === 0) {
|
|
405
|
+
try {
|
|
406
|
+
await adoptFolder({
|
|
407
|
+
localDir: pairing.localDir,
|
|
408
|
+
sandbox: sandboxCopy(realProjectRunner, projectShell(pairingEndpoint(pairing), mutagenSshPath(process.platform, process.env["MUTAGEN_SSH_PATH"])), pairingRemoteDir(pairing)),
|
|
409
|
+
ignores: ignoresFor(pairing),
|
|
410
|
+
log: (line) => log(`${key}: ${line}`),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
log(`${key}: ${pairing.localDir} could not be compared with the sandbox's copy (${errorMessage(error)}), so its file sync waits rather than start over two copies that disagree. Retrying later.`);
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
await clearAdoptFolder(key);
|
|
419
|
+
return true;
|
|
420
|
+
};
|
|
421
|
+
export const sessionSpecs = (pairing, symlinks, owner) => isProjectPairing(pairing)
|
|
422
|
+
? [sessionSpec(pairing, symlinks, owner)]
|
|
423
|
+
: [sessionSpec(pairing, symlinks, owner), backupSpec(pairing, symlinks, owner)];
|
|
424
|
+
export const strayBackupSessions = (pairing, live) => isProjectPairing(pairing) ? live.filter((name) => name === backupSessionName(pairingKey(pairing))) : [];
|
|
270
425
|
const retireStrayBackup = (mutagen, pairing, log) => {
|
|
271
|
-
const
|
|
426
|
+
const live = existingSyncSessions(mutagen, [backupSessionName(pairingKey(pairing))]);
|
|
427
|
+
const stray = live === undefined ? [] : strayBackupSessions(pairing, live);
|
|
272
428
|
if (stray.length === 0) {
|
|
273
429
|
return;
|
|
274
430
|
}
|
|
275
|
-
spawnSync(mutagen, ["sync", "terminate", ...stray], { stdio: "ignore", windowsHide: true });
|
|
276
|
-
log(`${pairing
|
|
431
|
+
spawnSync(mutagen, ["sync", "terminate", ...stray], { stdio: "ignore", windowsHide: true, timeout: MUTAGEN_CALL_TIMEOUT_MS });
|
|
432
|
+
log(`${pairingKey(pairing)}: its folder is a project of yours, which carries no copy of the sandbox's state; terminated the state backup that was writing one into it.`);
|
|
277
433
|
};
|
|
278
434
|
export const surplusSessions = (sessions) => sessions.slice(1).flatMap((session) => (session.identifier === undefined ? [] : [session.identifier]));
|
|
279
435
|
const retireSurplus = (mutagen, name, log) => {
|
|
280
436
|
const sessions = readSessions(mutagen, name);
|
|
281
|
-
const surplus = surplusSessions(sessions);
|
|
282
|
-
if (surplus.length === 0) {
|
|
437
|
+
const surplus = sessions === undefined ? [] : surplusSessions(sessions);
|
|
438
|
+
if (sessions === undefined || surplus.length === 0) {
|
|
283
439
|
return sessions;
|
|
284
440
|
}
|
|
285
|
-
spawnSync(mutagen, ["sync", "terminate", ...surplus], { stdio: "ignore", windowsHide: true });
|
|
441
|
+
spawnSync(mutagen, ["sync", "terminate", ...surplus], { stdio: "ignore", windowsHide: true, timeout: MUTAGEN_CALL_TIMEOUT_MS });
|
|
286
442
|
log(`${name}: ${sessions.length} sync sessions shared this name and flagged each other's writes as conflicts; kept the oldest and terminated the rest.`);
|
|
287
443
|
return readSessions(mutagen, name);
|
|
288
444
|
};
|
|
@@ -291,7 +447,10 @@ export const convergePlan = (sessions, spec) => {
|
|
|
291
447
|
return "create";
|
|
292
448
|
}
|
|
293
449
|
const only = sessions.length === 1 ? sessions[0] : undefined;
|
|
294
|
-
|
|
450
|
+
if (only === undefined || !sessionMatchesSpec(only, spec)) {
|
|
451
|
+
return "replace";
|
|
452
|
+
}
|
|
453
|
+
return spec.labels !== undefined && ownershipOf(only.labels, spec.labels.owner) === "unlabelled" ? "relabel" : "keep";
|
|
295
454
|
};
|
|
296
455
|
export const settlesFirst = (spec, live) => spec.mode === "two-way-safe" && live.every((session) => liveMode(session) === "two-way-safe");
|
|
297
456
|
const readyForReplacement = async (mutagen, spec, live, log) => {
|
|
@@ -300,6 +459,9 @@ const readyForReplacement = async (mutagen, spec, live, log) => {
|
|
|
300
459
|
}
|
|
301
460
|
await flushSession(mutagen, spec.name);
|
|
302
461
|
const held = readSessionConflicts(mutagen, spec.name);
|
|
462
|
+
if (held === "unanswered") {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
303
465
|
const cleared = held === undefined || held.conflicts.length === 0
|
|
304
466
|
? { standing: 0 }
|
|
305
467
|
: await clearConflictResidue({ root: spec.localDir, conflicts: held.conflicts, ignores: held.ignores, log });
|
|
@@ -330,12 +492,31 @@ export const transportAnswers = (pairing) => {
|
|
|
330
492
|
? async () => await dockerEndpointAnswers(endpoint.container, pairing.sandboxUrl)
|
|
331
493
|
: async () => await sshTransportAnswers(mutagenSshPath(process.platform, process.env["MUTAGEN_SSH_PATH"]), endpoint.alias);
|
|
332
494
|
};
|
|
333
|
-
const
|
|
495
|
+
export const terminationTargets = (sessions, name) => sessions.length > 0 && sessions.every((session) => session.identifier !== undefined)
|
|
496
|
+
? sessions.flatMap((session) => session.identifier ?? [])
|
|
497
|
+
: [name];
|
|
498
|
+
const convergeSession = async (mutagen, spec, log, answers, relabels) => {
|
|
499
|
+
const listed = readSessions(mutagen, spec.name);
|
|
500
|
+
if (listed === undefined) {
|
|
501
|
+
log(`${spec.name}: Mutagen did not answer a listing, so this session is left as it is this pass. Retrying later.`);
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
const owner = spec.labels?.owner;
|
|
505
|
+
if (owner !== undefined && listed.some((session) => ownershipOf(session.labels, owner) === "theirs")) {
|
|
506
|
+
log(`${spec.name}: a session of this name belongs to another owner (${OWNER_LABEL} says so), so this one is neither replaced nor made beside it.`);
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
334
509
|
const sessions = retireSurplus(mutagen, spec.name, log);
|
|
510
|
+
if (sessions === undefined) {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
335
513
|
const plan = convergePlan(sessions, spec);
|
|
336
514
|
if (plan === "keep") {
|
|
337
515
|
return true;
|
|
338
516
|
}
|
|
517
|
+
if (plan === "relabel" && !relabels.take()) {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
339
520
|
const live = sessions.length === 1 ? sessions[0] : undefined;
|
|
340
521
|
if (sessions.length > 1) {
|
|
341
522
|
log(`${spec.name}: ${sessions.length} sync sessions share this name, which conflict with each other; replacing them with one.`);
|
|
@@ -344,7 +525,7 @@ const convergeSession = async (mutagen, spec, log, answers) => {
|
|
|
344
525
|
log(`${spec.name}: ${spec.remote.container} is not running on this machine's Docker engine as this sandbox, so no file sync is made into it. Retrying later.`);
|
|
345
526
|
return false;
|
|
346
527
|
}
|
|
347
|
-
if (plan === "replace") {
|
|
528
|
+
if (plan === "replace" || plan === "relabel") {
|
|
348
529
|
if (!(await answers())) {
|
|
349
530
|
log(`${spec.name}: the sandbox is not answering, so its existing file sync is left running as it is rather than terminated for a replacement that cannot be created. Retrying later.`);
|
|
350
531
|
return false;
|
|
@@ -352,8 +533,14 @@ const convergeSession = async (mutagen, spec, log, answers) => {
|
|
|
352
533
|
if (!(await readyForReplacement(mutagen, spec, sessions, log))) {
|
|
353
534
|
return false;
|
|
354
535
|
}
|
|
355
|
-
log(
|
|
356
|
-
|
|
536
|
+
log(plan === "relabel"
|
|
537
|
+
? `${spec.name}: made before this agent labelled its sessions, and Mutagen cannot label a session that exists: recreating it with this machine's ownership labels, as ${spec.mode}. Both sides are rescanned; nothing that already matches is copied.`
|
|
538
|
+
: `${spec.name}: the running sync session does not match this build's rules (its ignores, its folder, which way it syncs, or what it does with symbolic links): recreating it so they apply, as ${spec.mode}. This starts the comparison from scratch.`);
|
|
539
|
+
spawnSync(mutagen, ["sync", "terminate", ...terminationTargets(sessions, spec.name)], {
|
|
540
|
+
stdio: "ignore",
|
|
541
|
+
windowsHide: true,
|
|
542
|
+
timeout: MUTAGEN_CALL_TIMEOUT_MS,
|
|
543
|
+
});
|
|
357
544
|
}
|
|
358
545
|
await runMutagenAsync(mutagen, mutagenCreateArgs(spec, live?.paused === true), log);
|
|
359
546
|
return true;
|
|
@@ -367,47 +554,61 @@ export const healDerivedConflicts = async (mutagen, pairing, log, asked = false)
|
|
|
367
554
|
if (!asked && (pairing.autoHealOff === true || pairing.fileSyncAutoPaused === true)) {
|
|
368
555
|
return idle;
|
|
369
556
|
}
|
|
370
|
-
const held = readSessionConflicts(mutagen, sessionName(pairing
|
|
371
|
-
if (held === undefined || held.conflicts.length === 0) {
|
|
557
|
+
const held = readSessionConflicts(mutagen, sessionName(pairingKey(pairing)));
|
|
558
|
+
if (held === undefined || held === "unanswered" || held.conflicts.length === 0) {
|
|
372
559
|
return idle;
|
|
373
560
|
}
|
|
374
561
|
const outcome = await clearConflictResidue({ root: pairing.localDir, conflicts: held.conflicts, ignores: held.ignores, log });
|
|
375
562
|
if (outcome.removed.length > 0) {
|
|
376
|
-
await flushSession(mutagen, sessionName(pairing
|
|
563
|
+
await flushSession(mutagen, sessionName(pairingKey(pairing)));
|
|
377
564
|
}
|
|
378
565
|
return outcome;
|
|
379
566
|
};
|
|
380
|
-
export const retireOrphanSessions = (mutagen, pairings, log) => {
|
|
381
|
-
const
|
|
382
|
-
const sessions =
|
|
383
|
-
|
|
384
|
-
|
|
567
|
+
export const retireOrphanSessions = (mutagen, pairings, log, owner = sessionOwner()) => {
|
|
568
|
+
const listedSync = listSessionNames(mutagen, "sync");
|
|
569
|
+
const sessions = listedSync === undefined
|
|
570
|
+
? []
|
|
571
|
+
: parseOrphanSyncNames(listedSync, pairings.flatMap((pairing) => syncSessionNames(pairing)), owner);
|
|
572
|
+
if (listedSync !== undefined && sessions.length > 0) {
|
|
573
|
+
spawnSync(mutagen, ["sync", "terminate", ...terminateTargetsIn(listedSync, sessions, SESSION_PREFIX, owner)], {
|
|
574
|
+
stdio: "ignore",
|
|
575
|
+
windowsHide: true,
|
|
576
|
+
timeout: MUTAGEN_CALL_TIMEOUT_MS,
|
|
577
|
+
});
|
|
385
578
|
log(`retired ${plural(sessions.length, "file-sync session")} belonging to sandboxes this machine no longer pairs.`);
|
|
386
579
|
}
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
580
|
+
const listedForwards = listSessionNames(mutagen, "forward");
|
|
581
|
+
const forwards = listedForwards === undefined
|
|
582
|
+
? []
|
|
583
|
+
: parseOrphanForwardNames(listedForwards, pairings.map((pairing) => pairing.sandboxId), owner);
|
|
584
|
+
if (listedForwards !== undefined && forwards.length > 0) {
|
|
585
|
+
spawnSync(mutagen, ["forward", "terminate", ...terminateTargetsIn(listedForwards, forwards, FORWARD_PREFIX, owner)], {
|
|
586
|
+
stdio: "ignore",
|
|
587
|
+
windowsHide: true,
|
|
588
|
+
timeout: MUTAGEN_CALL_TIMEOUT_MS,
|
|
589
|
+
});
|
|
390
590
|
log(`released ${plural(forwards.length, "port forward")} left holding localhost for sandboxes this machine no longer pairs.`);
|
|
391
591
|
}
|
|
392
592
|
};
|
|
393
593
|
const installedVersion = (binary, versionArgs) => {
|
|
394
|
-
const result = spawnSync(binary, versionArgs, { encoding: "utf8", windowsHide: true });
|
|
594
|
+
const result = spawnSync(binary, versionArgs, { encoding: "utf8", windowsHide: true, timeout: MUTAGEN_CALL_TIMEOUT_MS });
|
|
395
595
|
if (result.error !== undefined || result.status !== 0) {
|
|
396
596
|
return undefined;
|
|
397
597
|
}
|
|
398
598
|
return /\d+\.\d+\.\d+/.exec(result.stdout)?.[0];
|
|
399
599
|
};
|
|
400
600
|
const extractTarball = (tarball) => {
|
|
401
|
-
const extract = spawnSync("tar", ["-xzf", tarball, "-C", binDir], { stdio: "inherit", windowsHide: true });
|
|
601
|
+
const extract = spawnSync("tar", ["-xzf", tarball, "-C", binDir], { stdio: "inherit", windowsHide: true, timeout: 2 * 60_000 });
|
|
402
602
|
if (extract.status !== 0) {
|
|
403
603
|
throw new Error(`failed to extract ${tarball}: tar's own reason is above (no \`tar\` on PATH, or a file it must replace is in use)`);
|
|
404
604
|
}
|
|
405
605
|
};
|
|
606
|
+
const PATH_LOOKUP_TIMEOUT_MS = 10_000;
|
|
406
607
|
const resolveOnPath = (command) => {
|
|
407
608
|
const whereExe = join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "where.exe");
|
|
408
609
|
const found = process.platform === "win32"
|
|
409
|
-
? spawnSync(whereExe, [command], { encoding: "utf8", windowsHide: true })
|
|
410
|
-
: spawnSync("sh", ["-c", `command -v ${command}`], { encoding: "utf8" });
|
|
610
|
+
? spawnSync(whereExe, [command], { encoding: "utf8", windowsHide: true, timeout: PATH_LOOKUP_TIMEOUT_MS })
|
|
611
|
+
: spawnSync("sh", ["-c", `command -v ${command}`], { encoding: "utf8", timeout: PATH_LOOKUP_TIMEOUT_MS });
|
|
411
612
|
const first = found.status === 0 ? found.stdout.split("\n")[0]?.trim() : undefined;
|
|
412
613
|
return first === undefined || first === "" || !existsSync(first) ? undefined : first;
|
|
413
614
|
};
|
|
@@ -416,16 +617,30 @@ export const isOwnMutagen = (mutagen, own = ownMutagenPath(), platform = process
|
|
|
416
617
|
const fold = (path) => (platform === "win32" ? path.replaceAll("/", "\\").toLowerCase() : path);
|
|
417
618
|
return fold(mutagen) === fold(own);
|
|
418
619
|
};
|
|
620
|
+
export const userMutagenOnPath = () => {
|
|
621
|
+
const found = resolveOnPath("mutagen");
|
|
622
|
+
return found === undefined || isOwnMutagen(found) ? undefined : found;
|
|
623
|
+
};
|
|
419
624
|
export const ensureMutagen = async () => {
|
|
420
|
-
const own = resolveOnPath("mutagen");
|
|
421
|
-
if (own !== undefined && installedVersion(own, ["version"]) !== undefined) {
|
|
422
|
-
return own;
|
|
423
|
-
}
|
|
424
625
|
const dest = ownMutagenPath();
|
|
425
626
|
if (installedVersion(dest, ["version"]) === MUTAGEN_VERSION) {
|
|
426
627
|
return dest;
|
|
427
628
|
}
|
|
428
|
-
|
|
629
|
+
try {
|
|
630
|
+
await installPinnedMutagen(dest);
|
|
631
|
+
return dest;
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
const onPath = resolveOnPath("mutagen");
|
|
635
|
+
if (onPath !== undefined && installedVersion(onPath, ["version"]) !== undefined) {
|
|
636
|
+
daemonLog?.(`this agent's own Mutagen ${MUTAGEN_VERSION} could not be fetched (${errorMessage(error)}); using ${onPath} until it can.`);
|
|
637
|
+
return onPath;
|
|
638
|
+
}
|
|
639
|
+
throw error;
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
const installPinnedMutagen = async (dest) => {
|
|
643
|
+
spawnSync(dest, ["daemon", "stop"], { stdio: "ignore", windowsHide: true, timeout: MUTAGEN_CALL_TIMEOUT_MS });
|
|
429
644
|
const tarball = join(binDir, "mutagen.tar.gz");
|
|
430
645
|
await download(`https://github.com/mutagen-io/mutagen/releases/download/v${MUTAGEN_VERSION}/mutagen_${osToken()}_${archToken()}_v${MUTAGEN_VERSION}.tar.gz`, tarball);
|
|
431
646
|
const displaced = `${dest}.old`;
|
|
@@ -434,10 +649,9 @@ export const ensureMutagen = async () => {
|
|
|
434
649
|
await chmod(dest, 0o755);
|
|
435
650
|
await rm(displaced, { force: true }).catch(() => undefined);
|
|
436
651
|
await rm(tarball, { force: true }).catch(() => undefined);
|
|
437
|
-
return dest;
|
|
438
652
|
};
|
|
439
653
|
export const runMutagenAsync = async (mutagen, args, log) => {
|
|
440
|
-
const result = await runProcess(mutagen, args);
|
|
654
|
+
const result = await runProcess(mutagen, args, { timeoutMs: MUTAGEN_CREATE_TIMEOUT_MS });
|
|
441
655
|
const said = `${result.stdout}${result.stderr}`.trim();
|
|
442
656
|
if (result.status === 0) {
|
|
443
657
|
return;
|
|
@@ -448,7 +662,7 @@ export const runMutagenAsync = async (mutagen, args, log) => {
|
|
|
448
662
|
throw new Error(`mutagen ${args[0] ?? ""} exited with code ${result.status}`);
|
|
449
663
|
};
|
|
450
664
|
export const runMutagen = (mutagen, args) => {
|
|
451
|
-
const result = spawnSync(mutagen, args, { stdio: "inherit", windowsHide: true });
|
|
665
|
+
const result = spawnSync(mutagen, args, { stdio: "inherit", windowsHide: true, timeout: MUTAGEN_CREATE_TIMEOUT_MS });
|
|
452
666
|
if (result.error !== undefined) {
|
|
453
667
|
throw result.error;
|
|
454
668
|
}
|
|
@@ -468,7 +682,7 @@ export const registerMutagenAutostart = (mutagen, launcher, log) => {
|
|
|
468
682
|
runMutagen(mutagen, ["daemon", "register"]);
|
|
469
683
|
return;
|
|
470
684
|
}
|
|
471
|
-
spawnSync(mutagen, ["daemon", "unregister"], { stdio: "ignore", windowsHide: true });
|
|
685
|
+
spawnSync(mutagen, ["daemon", "unregister"], { stdio: "ignore", windowsHide: true, timeout: MUTAGEN_CALL_TIMEOUT_MS });
|
|
472
686
|
setWindowsRunValue(MUTAGEN_RUN_VALUE, quotedCommandLine(stubCommand(stub, mutagenDaemonLogPath, [mutagen, "daemon", "start"])));
|
|
473
687
|
}
|
|
474
688
|
catch (error) {
|
|
@@ -480,7 +694,7 @@ export const unregisterMutagenAutostart = (mutagen) => {
|
|
|
480
694
|
clearWindowsRunValue(MUTAGEN_RUN_VALUE);
|
|
481
695
|
}
|
|
482
696
|
if (process.platform !== "linux") {
|
|
483
|
-
spawnSync(mutagen, ["daemon", "unregister"], { stdio: "ignore", windowsHide: true });
|
|
697
|
+
spawnSync(mutagen, ["daemon", "unregister"], { stdio: "ignore", windowsHide: true, timeout: MUTAGEN_CALL_TIMEOUT_MS });
|
|
484
698
|
}
|
|
485
699
|
};
|
|
486
700
|
//# sourceMappingURL=mutagen.js.map
|