@geminixiang/mikan 1.0.0-beta.84 → 1.0.0-beta.86
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/CHANGELOG.md +57 -0
- package/README.md +10 -10
- package/dist/adapters/commands/session-view.js +1 -1
- package/dist/adapters/commands/session-view.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +1 -8
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/github/activity.d.ts +3 -0
- package/dist/adapters/github/activity.d.ts.map +1 -0
- package/dist/adapters/github/activity.js +154 -0
- package/dist/adapters/github/activity.js.map +1 -0
- package/dist/adapters/github/bot.d.ts +10 -20
- package/dist/adapters/github/bot.d.ts.map +1 -1
- package/dist/adapters/github/bot.js +119 -342
- package/dist/adapters/github/bot.js.map +1 -1
- package/dist/adapters/github/client.d.ts +9 -19
- package/dist/adapters/github/client.d.ts.map +1 -1
- package/dist/adapters/github/client.js +28 -104
- package/dist/adapters/github/client.js.map +1 -1
- package/dist/adapters/github/context.d.ts.map +1 -1
- package/dist/adapters/github/context.js +29 -20
- package/dist/adapters/github/context.js.map +1 -1
- package/dist/adapters/github/github-ops.d.ts +2 -10
- package/dist/adapters/github/github-ops.d.ts.map +1 -1
- package/dist/adapters/github/github-ops.js +28 -91
- package/dist/adapters/github/github-ops.js.map +1 -1
- package/dist/adapters/github/policy.d.ts +19 -0
- package/dist/adapters/github/policy.d.ts.map +1 -0
- package/dist/adapters/github/policy.js +79 -0
- package/dist/adapters/github/policy.js.map +1 -0
- package/dist/adapters/github/tool-pack.d.ts +2 -2
- package/dist/adapters/github/tool-pack.d.ts.map +1 -1
- package/dist/adapters/github/tool-pack.js +4 -9
- package/dist/adapters/github/tool-pack.js.map +1 -1
- package/dist/adapters/github/tools/checks.d.ts.map +1 -1
- package/dist/adapters/github/tools/checks.js +6 -8
- package/dist/adapters/github/tools/checks.js.map +1 -1
- package/dist/adapters/github/tools/pr.d.ts.map +1 -1
- package/dist/adapters/github/tools/pr.js +7 -9
- package/dist/adapters/github/tools/pr.js.map +1 -1
- package/dist/adapters/github/types.d.ts +66 -67
- package/dist/adapters/github/types.d.ts.map +1 -1
- package/dist/adapters/github/types.js.map +1 -1
- package/dist/adapters/github/webhook.d.ts +2 -1
- package/dist/adapters/github/webhook.d.ts.map +1 -1
- package/dist/adapters/github/webhook.js +46 -4
- package/dist/adapters/github/webhook.js.map +1 -1
- package/dist/adapters/progressive-renderer.d.ts.map +1 -1
- package/dist/adapters/progressive-renderer.js +3 -0
- package/dist/adapters/progressive-renderer.js.map +1 -1
- package/dist/adapters/shared.d.ts +3 -1
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +45 -23
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/slack/blocks.d.ts.map +1 -1
- package/dist/adapters/slack/blocks.js +65 -2
- package/dist/adapters/slack/blocks.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +4 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +84 -36
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/jev-context.js +2 -2
- package/dist/adapters/slack/jev-context.js.map +1 -1
- package/dist/adapters/slack/response-lifecycle.js +2 -2
- package/dist/adapters/slack/response-lifecycle.js.map +1 -1
- package/dist/adapters/slack/task-status.d.ts +3 -2
- package/dist/adapters/slack/task-status.d.ts.map +1 -1
- package/dist/adapters/slack/task-status.js +12 -15
- package/dist/adapters/slack/task-status.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +1 -8
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/web/admin/portal.js +1 -1
- package/dist/adapters/web/admin/portal.js.map +1 -1
- package/dist/adapters/web/admin/provider-models.d.ts.map +1 -1
- package/dist/adapters/web/admin/provider-models.js +10 -3
- package/dist/adapters/web/admin/provider-models.js.map +1 -1
- package/dist/adapters/web/login/portal.js +6 -1
- package/dist/adapters/web/login/portal.js.map +1 -1
- package/dist/adapters/web/server.d.ts.map +1 -1
- package/dist/adapters/web/server.js +5 -3
- package/dist/adapters/web/server.js.map +1 -1
- package/dist/adapters/web/session-view/portal.d.ts +2 -1
- package/dist/adapters/web/session-view/portal.d.ts.map +1 -1
- package/dist/adapters/web/session-view/portal.js +18 -27
- package/dist/adapters/web/session-view/portal.js.map +1 -1
- package/dist/cli/boot.d.ts.map +1 -1
- package/dist/cli/boot.js +4 -6
- package/dist/cli/boot.js.map +1 -1
- package/dist/cli/migrate.d.ts +3 -0
- package/dist/cli/migrate.d.ts.map +1 -0
- package/dist/cli/migrate.js +74 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli/office.d.ts.map +1 -1
- package/dist/cli/office.js +8 -129
- package/dist/cli/office.js.map +1 -1
- package/dist/cli/onboard.d.ts +0 -1
- package/dist/cli/onboard.d.ts.map +1 -1
- package/dist/cli/onboard.js +4 -7
- package/dist/cli/onboard.js.map +1 -1
- package/dist/cli/types.d.ts +2 -3
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/env-manifest.d.ts.map +1 -1
- package/dist/env-manifest.js +24 -22
- package/dist/env-manifest.js.map +1 -1
- package/dist/events/index.d.ts +3 -13
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +12 -58
- package/dist/events/index.js.map +1 -1
- package/dist/events/scheduler.d.ts.map +1 -1
- package/dist/events/scheduler.js +3 -0
- package/dist/events/scheduler.js.map +1 -1
- package/dist/file-guards.d.ts +7 -0
- package/dist/file-guards.d.ts.map +1 -1
- package/dist/file-guards.js +63 -1
- package/dist/file-guards.js.map +1 -1
- package/dist/harness/budget-stop.d.ts +3 -0
- package/dist/harness/budget-stop.d.ts.map +1 -0
- package/dist/harness/budget-stop.js +36 -0
- package/dist/harness/budget-stop.js.map +1 -0
- package/dist/harness/execution-resolver.d.ts.map +1 -1
- package/dist/harness/execution-resolver.js +5 -9
- package/dist/harness/execution-resolver.js.map +1 -1
- package/dist/harness/mcp.d.ts.map +1 -1
- package/dist/harness/mcp.js +75 -31
- package/dist/harness/mcp.js.map +1 -1
- package/dist/harness/models.js +2 -2
- package/dist/harness/models.js.map +1 -1
- package/dist/harness/open-connector.d.ts +0 -9
- package/dist/harness/open-connector.d.ts.map +1 -1
- package/dist/harness/open-connector.js +15 -69
- package/dist/harness/open-connector.js.map +1 -1
- package/dist/harness/presenter.d.ts.map +1 -1
- package/dist/harness/presenter.js +33 -2
- package/dist/harness/presenter.js.map +1 -1
- package/dist/harness/prompt.d.ts +1 -1
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js +14 -32
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/run-events.d.ts.map +1 -1
- package/dist/harness/run-events.js +6 -1
- package/dist/harness/run-events.js.map +1 -1
- package/dist/harness/runner.d.ts.map +1 -1
- package/dist/harness/runner.js +6 -9
- package/dist/harness/runner.js.map +1 -1
- package/dist/harness/session.d.ts.map +1 -1
- package/dist/harness/session.js +6 -0
- package/dist/harness/session.js.map +1 -1
- package/dist/harness/tools/event.d.ts.map +1 -1
- package/dist/harness/tools/event.js +19 -2
- package/dist/harness/tools/event.js.map +1 -1
- package/dist/harness/tools/generate-image.d.ts.map +1 -1
- package/dist/harness/tools/generate-image.js +17 -3
- package/dist/harness/tools/generate-image.js.map +1 -1
- package/dist/harness/tools/jev-browser.d.ts +1 -2
- package/dist/harness/tools/jev-browser.d.ts.map +1 -1
- package/dist/harness/tools/jev-browser.js +75 -50
- package/dist/harness/tools/jev-browser.js.map +1 -1
- package/dist/harness/types.d.ts +16 -0
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/main.js +40 -93
- package/dist/main.js.map +1 -1
- package/dist/memory-capture/index.d.ts.map +1 -1
- package/dist/memory-capture/index.js +3 -3
- package/dist/memory-capture/index.js.map +1 -1
- package/dist/migrations/actor-vaults.d.ts +3 -0
- package/dist/migrations/actor-vaults.d.ts.map +1 -0
- package/dist/migrations/actor-vaults.js +43 -0
- package/dist/migrations/actor-vaults.js.map +1 -0
- package/dist/migrations/conversation-settings.d.ts +3 -0
- package/dist/migrations/conversation-settings.d.ts.map +1 -0
- package/dist/migrations/conversation-settings.js +31 -0
- package/dist/migrations/conversation-settings.js.map +1 -0
- package/dist/migrations/index.d.ts +14 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +99 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/models-json.d.ts +3 -0
- package/dist/migrations/models-json.d.ts.map +1 -0
- package/dist/migrations/models-json.js +18 -0
- package/dist/migrations/models-json.js.map +1 -0
- package/dist/migrations/office-layout.d.ts +4 -0
- package/dist/migrations/office-layout.d.ts.map +1 -0
- package/dist/migrations/office-layout.js +94 -0
- package/dist/migrations/office-layout.js.map +1 -0
- package/dist/migrations/office-sessions.d.ts +3 -0
- package/dist/migrations/office-sessions.d.ts.map +1 -0
- package/dist/migrations/office-sessions.js +70 -0
- package/dist/migrations/office-sessions.js.map +1 -0
- package/dist/migrations/sandbox-containers.d.ts +3 -0
- package/dist/migrations/sandbox-containers.d.ts.map +1 -0
- package/dist/migrations/sandbox-containers.js +36 -0
- package/dist/migrations/sandbox-containers.js.map +1 -0
- package/dist/{sessions/migrate-common.d.ts → migrations/session-files.d.ts} +1 -2
- package/dist/migrations/session-files.d.ts.map +1 -0
- package/dist/{sessions/migrate-common.js → migrations/session-files.js} +2 -2
- package/dist/migrations/session-files.js.map +1 -0
- package/dist/{sessions/migrate-v3.d.ts → migrations/sessions-v3.d.ts} +4 -3
- package/dist/migrations/sessions-v3.d.ts.map +1 -0
- package/dist/{sessions/migrate-v3.js → migrations/sessions-v3.js} +14 -4
- package/dist/migrations/sessions-v3.js.map +1 -0
- package/dist/migrations/types.d.ts +30 -0
- package/dist/migrations/types.d.ts.map +1 -0
- package/dist/migrations/types.js +2 -0
- package/dist/migrations/types.js.map +1 -0
- package/dist/migrations/workspace-events.d.ts +3 -0
- package/dist/migrations/workspace-events.d.ts.map +1 -0
- package/dist/migrations/workspace-events.js +43 -0
- package/dist/migrations/workspace-events.js.map +1 -0
- package/dist/office/index.d.ts +2 -24
- package/dist/office/index.d.ts.map +1 -1
- package/dist/office/index.js +15 -579
- package/dist/office/index.js.map +1 -1
- package/dist/office/types.d.ts +0 -10
- package/dist/office/types.d.ts.map +1 -1
- package/dist/office/types.js.map +1 -1
- package/dist/runtime/conversation-runtime.js +12 -12
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/sandbox/identity.d.ts +2 -3
- package/dist/sandbox/identity.d.ts.map +1 -1
- package/dist/sandbox/identity.js +6 -13
- package/dist/sandbox/identity.js.map +1 -1
- package/dist/sandbox/layout.d.ts +0 -2
- package/dist/sandbox/layout.d.ts.map +1 -1
- package/dist/sandbox/layout.js +2 -2
- package/dist/sandbox/layout.js.map +1 -1
- package/dist/sandbox/provisioner.d.ts +4 -38
- package/dist/sandbox/provisioner.d.ts.map +1 -1
- package/dist/sandbox/provisioner.js +32 -456
- package/dist/sandbox/provisioner.js.map +1 -1
- package/dist/sessions/chat-history-sync.d.ts.map +1 -1
- package/dist/sessions/chat-history-sync.js +28 -40
- package/dist/sessions/chat-history-sync.js.map +1 -1
- package/dist/sessions/session-store.d.ts.map +1 -1
- package/dist/sessions/session-store.js +1 -8
- package/dist/sessions/session-store.js.map +1 -1
- package/dist/sessions/store.d.ts +2 -3
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +8 -14
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +6 -15
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js.map +1 -1
- package/dist/settings/index.d.ts +0 -48
- package/dist/settings/index.d.ts.map +1 -1
- package/dist/settings/index.js +6 -37
- package/dist/settings/index.js.map +1 -1
- package/dist/types.d.ts +0 -29
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/vault/index.d.ts +0 -7
- package/dist/vault/index.d.ts.map +1 -1
- package/dist/vault/index.js +1 -36
- package/dist/vault/index.js.map +1 -1
- package/package.json +8 -6
- package/dist/adapters/github/repo.d.ts +0 -8
- package/dist/adapters/github/repo.d.ts.map +0 -1
- package/dist/adapters/github/repo.js +0 -92
- package/dist/adapters/github/repo.js.map +0 -1
- package/dist/adapters/github/tools/sync.d.ts +0 -13
- package/dist/adapters/github/tools/sync.d.ts.map +0 -1
- package/dist/adapters/github/tools/sync.js +0 -29
- package/dist/adapters/github/tools/sync.js.map +0 -1
- package/dist/cli/sandbox.d.ts +0 -3
- package/dist/cli/sandbox.d.ts.map +0 -1
- package/dist/cli/sandbox.js +0 -80
- package/dist/cli/sandbox.js.map +0 -1
- package/dist/cli/sessions.d.ts +0 -2
- package/dist/cli/sessions.d.ts.map +0 -1
- package/dist/cli/sessions.js +0 -78
- package/dist/cli/sessions.js.map +0 -1
- package/dist/sessions/migrate-common.d.ts.map +0 -1
- package/dist/sessions/migrate-common.js.map +0 -1
- package/dist/sessions/migrate-pi-084.d.ts +0 -7
- package/dist/sessions/migrate-pi-084.d.ts.map +0 -1
- package/dist/sessions/migrate-pi-084.js +0 -293
- package/dist/sessions/migrate-pi-084.js.map +0 -1
- package/dist/sessions/migrate-v3.d.ts.map +0 -1
- package/dist/sessions/migrate-v3.js.map +0 -1
- package/dist/settings/migrate.d.ts +0 -16
- package/dist/settings/migrate.d.ts.map +0 -1
- package/dist/settings/migrate.js +0 -70
- package/dist/settings/migrate.js.map +0 -1
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import { legacyConversationResourceKey, sanitizeIdentitySegment } from "./identity.js";
|
|
3
|
-
import { GUEST_HOME, GUEST_PUBLIC_OFFICES_DIR } from "./layout.js";
|
|
4
2
|
import { createHash } from "node:crypto";
|
|
5
3
|
import { readFileSync, statSync } from "node:fs";
|
|
4
|
+
import { isAbsolute, relative } from "node:path";
|
|
6
5
|
import { promisify } from "node:util";
|
|
7
6
|
import * as log from "../log.js";
|
|
8
7
|
import { reportUserFacingError } from "../observability/index.js";
|
|
@@ -21,29 +20,16 @@ function isDockerNotFoundError(err) {
|
|
|
21
20
|
/error: no such [^\n]+/.test(haystack));
|
|
22
21
|
}
|
|
23
22
|
import { errorMessage } from "../unknown-values.js";
|
|
24
|
-
function bindSpecToMount(bindSpec) {
|
|
25
|
-
const readOnly = bindSpec.endsWith(":ro");
|
|
26
|
-
const spec = readOnly ? bindSpec.slice(0, -3) : bindSpec;
|
|
27
|
-
const separator = spec.indexOf(":");
|
|
28
|
-
return {
|
|
29
|
-
source: spec.slice(0, separator),
|
|
30
|
-
target: spec.slice(separator + 1),
|
|
31
|
-
readOnly: readOnly ? true : undefined,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
23
|
export class DockerContainerManager {
|
|
35
24
|
static { this.MANAGED_LABEL = "mikan.managed=true"; }
|
|
36
25
|
static { this.IMAGE_MODE_LABEL = "mikan.sandbox=image"; }
|
|
37
26
|
static { this.VAULT_ID_LABEL_KEY = "mikan.vault-id"; }
|
|
38
27
|
static { this.CONVERSATION_ID_LABEL_KEY = "mikan.conversation-id"; }
|
|
39
28
|
static { this.MOUNT_SIGNATURE_LABEL_KEY = "mikan.mount-signature"; }
|
|
40
|
-
static { this.MIGRATE_IMAGE_PREFIX = "mikan-migrate"; }
|
|
41
|
-
static { this.MIGRATE_BINDS_LABEL_KEY = "mikan.migrate-binds"; }
|
|
42
29
|
constructor(image, options = {}) {
|
|
43
30
|
this.image = image;
|
|
44
31
|
this.state = new Map();
|
|
45
32
|
this.inflight = new Map();
|
|
46
|
-
this.layoutMigrations = new Map();
|
|
47
33
|
this.keyQueues = new Map();
|
|
48
34
|
this.boostedKeys = new Set();
|
|
49
35
|
this.overrideLimits = new Map();
|
|
@@ -51,18 +37,12 @@ export class DockerContainerManager {
|
|
|
51
37
|
this.boostLimits = options.boostLimits;
|
|
52
38
|
this.execFileImpl = options.execFileImpl ?? execFileAsync;
|
|
53
39
|
}
|
|
54
|
-
static sanitizeSegment(value) {
|
|
55
|
-
return sanitizeIdentitySegment(value);
|
|
56
|
-
}
|
|
57
40
|
static containerName(containerKey) {
|
|
58
41
|
return `mikan-sandbox-${containerKey}`;
|
|
59
42
|
}
|
|
60
43
|
static networkName(containerKey) {
|
|
61
44
|
return `mikan-sandbox-net-${containerKey}`;
|
|
62
45
|
}
|
|
63
|
-
static homeVolumeName(containerKey) {
|
|
64
|
-
return `mikan-home-${containerKey}`;
|
|
65
|
-
}
|
|
66
46
|
serialize(containerKey, task) {
|
|
67
47
|
const previous = this.keyQueues.get(containerKey) ?? Promise.resolve();
|
|
68
48
|
const next = previous.then(task, task);
|
|
@@ -87,28 +67,16 @@ export class DockerContainerManager {
|
|
|
87
67
|
async provisionInner(containerKey, options) {
|
|
88
68
|
const containerName = options.containerName ?? DockerContainerManager.containerName(containerKey);
|
|
89
69
|
const mounts = options.mounts ?? [];
|
|
90
|
-
await this.migrateContainerLayout(containerName);
|
|
91
70
|
const status = await this.inspectStatus(containerName);
|
|
92
71
|
try {
|
|
93
|
-
const binds = status === "missing" ? [] : await this.inspectBindMounts(containerName);
|
|
94
|
-
const homeVolume = this.hasHomeVolumeBind(containerKey, binds);
|
|
95
72
|
const drift = status === "missing"
|
|
96
73
|
? undefined
|
|
97
|
-
: await this.runtimeDrift(containerKey, containerName, mounts,
|
|
98
|
-
|
|
99
|
-
status,
|
|
100
|
-
homeVolume,
|
|
101
|
-
});
|
|
102
|
-
if (drift && homeVolume) {
|
|
74
|
+
: await this.runtimeDrift(containerKey, containerName, mounts, status);
|
|
75
|
+
if (drift) {
|
|
103
76
|
log.logInfo(`Container ${containerName} is out of date (${drift}); replacing container`);
|
|
104
|
-
await this.
|
|
77
|
+
await this.replaceContainer(containerKey, containerName, mounts, options);
|
|
105
78
|
log.logInfo(`Container ${containerName} replaced from image ${this.image}`);
|
|
106
79
|
}
|
|
107
|
-
else if (drift) {
|
|
108
|
-
log.logInfo(`Container ${containerName} configuration changed (${drift}); recreating container`);
|
|
109
|
-
await this.recreateContainerPreservingContents(containerKey, containerName, mounts);
|
|
110
|
-
log.logInfo(`Container ${containerName} recreated`);
|
|
111
|
-
}
|
|
112
80
|
else if (status === "stopped") {
|
|
113
81
|
await this.execFileImpl("docker", ["start", containerName]);
|
|
114
82
|
log.logInfo(`Container ${containerName} started`);
|
|
@@ -173,10 +141,10 @@ export class DockerContainerManager {
|
|
|
173
141
|
log.logWarning(`Failed to stop container ${containerName}`, errorMessage(err));
|
|
174
142
|
}
|
|
175
143
|
}
|
|
176
|
-
remove(containerKey
|
|
177
|
-
return this.serialize(containerKey, () => this.removeInner(containerKey
|
|
144
|
+
remove(containerKey) {
|
|
145
|
+
return this.serialize(containerKey, () => this.removeInner(containerKey));
|
|
178
146
|
}
|
|
179
|
-
async removeInner(containerKey
|
|
147
|
+
async removeInner(containerKey) {
|
|
180
148
|
const containerName = this.getContainerName(containerKey);
|
|
181
149
|
const networkName = DockerContainerManager.networkName(containerKey);
|
|
182
150
|
const removed = await this.forceRemoveContainer(containerName, `Container ${containerName} removed`, `Failed to remove container ${containerName}`);
|
|
@@ -190,9 +158,6 @@ export class DockerContainerManager {
|
|
|
190
158
|
catch (err) {
|
|
191
159
|
log.logWarning(`Failed to remove network ${networkName}`, errorMessage(err));
|
|
192
160
|
}
|
|
193
|
-
await this.removeMigrateSnapshot(containerName);
|
|
194
|
-
if (options.purgeHome)
|
|
195
|
-
await this.removeHomeVolume(containerKey);
|
|
196
161
|
this.state.delete(containerKey);
|
|
197
162
|
this.boostedKeys.delete(containerKey);
|
|
198
163
|
this.overrideLimits.delete(containerKey);
|
|
@@ -207,291 +172,25 @@ export class DockerContainerManager {
|
|
|
207
172
|
}
|
|
208
173
|
await Promise.all(toStop.map((containerKey) => this.stop(containerKey)));
|
|
209
174
|
}
|
|
210
|
-
async
|
|
211
|
-
if (conversationIds.size === 0)
|
|
212
|
-
return;
|
|
175
|
+
async reconcile(workspaceRoot) {
|
|
213
176
|
const names = await this.listContainerNamesByLabel();
|
|
214
|
-
await Promise.all(names.map(async (containerName) => {
|
|
215
|
-
const details = await this.inspectContainerDetails(containerName);
|
|
216
|
-
if (!details?.conversationId || !conversationIds.has(details.conversationId))
|
|
217
|
-
return;
|
|
218
|
-
const removed = await this.forceRemoveContainer(containerName, `Removed container ${containerName} after office migration`, `Failed to remove container ${containerName} after office migration`);
|
|
219
|
-
const containerKey = this.containerKeyFromContainerName(containerName);
|
|
220
|
-
if (removed && containerKey)
|
|
221
|
-
await this.removeHomeVolume(containerKey);
|
|
222
|
-
}));
|
|
223
|
-
}
|
|
224
|
-
armContainerLayoutMigration(translator) {
|
|
225
|
-
this.bindTranslator = translator;
|
|
226
|
-
}
|
|
227
|
-
async sweepContainerLayoutMigration(delayMs = 2000) {
|
|
228
|
-
if (!this.bindTranslator)
|
|
229
|
-
return;
|
|
230
|
-
let names;
|
|
231
|
-
try {
|
|
232
|
-
const containers = await this.listContainerNamesByLabel();
|
|
233
|
-
const snapshots = await this.listMigrateSnapshotContainerNames();
|
|
234
|
-
names = Array.from(new Set([...containers, ...snapshots]));
|
|
235
|
-
}
|
|
236
|
-
catch (err) {
|
|
237
|
-
log.logWarning("Container layout sweep could not list containers", String(err));
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
for (const containerName of names) {
|
|
241
|
-
try {
|
|
242
|
-
await this.migrateContainerLayout(containerName);
|
|
243
|
-
}
|
|
244
|
-
catch (err) {
|
|
245
|
-
log.logWarning(`Container layout migration failed for ${containerName}; leaving it in place`, errorMessage(err));
|
|
246
|
-
}
|
|
247
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
248
|
-
}
|
|
249
|
-
await this.removeDanglingMigrateImages();
|
|
250
|
-
}
|
|
251
|
-
migrateContainerLayout(containerName) {
|
|
252
|
-
const translator = this.bindTranslator;
|
|
253
|
-
if (!translator)
|
|
254
|
-
return Promise.resolve();
|
|
255
|
-
const existing = this.layoutMigrations.get(containerName);
|
|
256
|
-
if (existing)
|
|
257
|
-
return existing;
|
|
258
|
-
const pending = this.migrateContainerLayoutInner(containerName, translator).finally(() => {
|
|
259
|
-
this.layoutMigrations.delete(containerName);
|
|
260
|
-
});
|
|
261
|
-
this.layoutMigrations.set(containerName, pending);
|
|
262
|
-
return pending;
|
|
263
|
-
}
|
|
264
|
-
async migrateContainerLayoutInner(containerName, translator) {
|
|
265
|
-
const snapshotImage = `${DockerContainerManager.MIGRATE_IMAGE_PREFIX}:${containerName}`;
|
|
266
|
-
let originalBinds;
|
|
267
|
-
const status = await this.inspectStatus(containerName);
|
|
268
|
-
if (status === "missing") {
|
|
269
|
-
const labeled = await this.readMigrateImageBinds(snapshotImage);
|
|
270
|
-
if (labeled === undefined)
|
|
271
|
-
return;
|
|
272
|
-
originalBinds = labeled;
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
originalBinds = await this.inspectBindMounts(containerName);
|
|
276
|
-
const translated = originalBinds.map(translator);
|
|
277
|
-
if (this.sameBinds(translated.toSorted(), originalBinds.slice().toSorted())) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
log.logInfo(`Migrating container ${containerName} to the office layout`);
|
|
281
|
-
await this.execFileImpl("docker", [
|
|
282
|
-
"commit",
|
|
283
|
-
"-c",
|
|
284
|
-
`LABEL ${DockerContainerManager.MIGRATE_BINDS_LABEL_KEY}=${JSON.stringify(JSON.stringify(originalBinds))}`,
|
|
285
|
-
containerName,
|
|
286
|
-
snapshotImage,
|
|
287
|
-
]);
|
|
288
|
-
await this.execFileImpl("docker", ["rm", "-f", containerName]);
|
|
289
|
-
}
|
|
290
|
-
await this.createContainerFromSnapshot(containerName, originalBinds.map(translator));
|
|
291
|
-
await this.removeMigrateSnapshot(containerName);
|
|
292
|
-
log.logInfo(`Container ${containerName} migrated to the office layout`);
|
|
293
|
-
}
|
|
294
|
-
async createContainerFromSnapshot(containerName, bindSpecs, knownContainerKey) {
|
|
295
|
-
const snapshotImage = `${DockerContainerManager.MIGRATE_IMAGE_PREFIX}:${containerName}`;
|
|
296
|
-
const containerKey = knownContainerKey ?? this.containerKeyFromContainerName(containerName) ?? containerName;
|
|
297
|
-
const networkName = await this.ensureNetwork(containerKey);
|
|
298
|
-
const conversationId = await this.readImageLabel(snapshotImage, DockerContainerManager.CONVERSATION_ID_LABEL_KEY);
|
|
299
|
-
const labels = [
|
|
300
|
-
"--label",
|
|
301
|
-
DockerContainerManager.MANAGED_LABEL,
|
|
302
|
-
"--label",
|
|
303
|
-
DockerContainerManager.IMAGE_MODE_LABEL,
|
|
304
|
-
"--label",
|
|
305
|
-
`${DockerContainerManager.VAULT_ID_LABEL_KEY}=${containerKey}`,
|
|
306
|
-
];
|
|
307
|
-
if (conversationId) {
|
|
308
|
-
labels.push("--label", `${DockerContainerManager.CONVERSATION_ID_LABEL_KEY}=${conversationId}`);
|
|
309
|
-
}
|
|
310
|
-
labels.push("--label", `${DockerContainerManager.MOUNT_SIGNATURE_LABEL_KEY}=${this.mountSignature(bindSpecs.map(bindSpecToMount))}`);
|
|
311
|
-
await this.execFileImpl("docker", [
|
|
312
|
-
"create",
|
|
313
|
-
"--name",
|
|
314
|
-
containerName,
|
|
315
|
-
"--network",
|
|
316
|
-
networkName,
|
|
317
|
-
"--cap-drop",
|
|
318
|
-
"ALL",
|
|
319
|
-
"--security-opt",
|
|
320
|
-
"no-new-privileges",
|
|
321
|
-
"--pids-limit",
|
|
322
|
-
"1024",
|
|
323
|
-
...labels,
|
|
324
|
-
...this.resourceLimitArgs(this.effectiveLimits(containerKey)),
|
|
325
|
-
...bindSpecs.flatMap((bind) => ["-v", bind]),
|
|
326
|
-
snapshotImage,
|
|
327
|
-
"sleep",
|
|
328
|
-
"infinity",
|
|
329
|
-
]);
|
|
330
|
-
}
|
|
331
|
-
async removeStaleMountpoints(containerName, bindSpecs) {
|
|
332
|
-
const keep = new Set(bindSpecs
|
|
333
|
-
.map((bind) => bindSpecToMount(bind).target)
|
|
334
|
-
.filter((target) => target.startsWith(`${GUEST_PUBLIC_OFFICES_DIR}/`))
|
|
335
|
-
.map((target) => target.slice(GUEST_PUBLIC_OFFICES_DIR.length + 1)));
|
|
336
|
-
const script = [
|
|
337
|
-
`cd ${GUEST_PUBLIC_OFFICES_DIR} 2>/dev/null || exit 0`,
|
|
338
|
-
"for d in *; do",
|
|
339
|
-
' [ -e "$d" ] || continue',
|
|
340
|
-
' case " $KEEP " in *" ${d##*/} "*) continue;; esac',
|
|
341
|
-
' rmdir "$d" 2>/dev/null || true',
|
|
342
|
-
"done",
|
|
343
|
-
].join("\n");
|
|
344
|
-
try {
|
|
345
|
-
await this.execFileImpl("docker", [
|
|
346
|
-
"exec",
|
|
347
|
-
"-e",
|
|
348
|
-
`KEEP=${[...keep].join(" ")}`,
|
|
349
|
-
containerName,
|
|
350
|
-
"sh",
|
|
351
|
-
"-c",
|
|
352
|
-
script,
|
|
353
|
-
]);
|
|
354
|
-
}
|
|
355
|
-
catch (err) {
|
|
356
|
-
log.logWarning(`Could not clean stale mountpoints in ${containerName}`, String(err));
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
async recreateContainerPreservingContents(containerKey, containerName, mounts) {
|
|
360
|
-
const snapshotImage = `${DockerContainerManager.MIGRATE_IMAGE_PREFIX}:${containerName}`;
|
|
361
|
-
const bindSpecs = mounts.map((mount) => this.toBindSpec(mount));
|
|
362
|
-
await this.execFileImpl("docker", [
|
|
363
|
-
"commit",
|
|
364
|
-
"-c",
|
|
365
|
-
`LABEL ${DockerContainerManager.MIGRATE_BINDS_LABEL_KEY}=${JSON.stringify(JSON.stringify(bindSpecs))}`,
|
|
366
|
-
containerName,
|
|
367
|
-
snapshotImage,
|
|
368
|
-
]);
|
|
369
|
-
await this.execFileImpl("docker", ["rm", "-f", containerName]);
|
|
370
|
-
await this.createContainerFromSnapshot(containerName, bindSpecs, containerKey);
|
|
371
|
-
await this.execFileImpl("docker", ["start", containerName]);
|
|
372
|
-
await this.removeStaleMountpoints(containerName, bindSpecs);
|
|
373
|
-
await this.removeMigrateSnapshot(containerName);
|
|
374
|
-
}
|
|
375
|
-
async readMigrateImageBinds(snapshotImage) {
|
|
376
|
-
const raw = await this.readImageLabel(snapshotImage, DockerContainerManager.MIGRATE_BINDS_LABEL_KEY);
|
|
377
|
-
if (raw === undefined)
|
|
378
|
-
return undefined;
|
|
379
|
-
const parsed = JSON.parse(raw);
|
|
380
|
-
if (!Array.isArray(parsed) || parsed.some((bind) => typeof bind !== "string")) {
|
|
381
|
-
throw new Error(`Snapshot image ${snapshotImage} carries malformed migrate binds`);
|
|
382
|
-
}
|
|
383
|
-
return parsed;
|
|
384
|
-
}
|
|
385
|
-
async readImageLabel(image, key) {
|
|
386
|
-
try {
|
|
387
|
-
const { stdout } = await this.execFileImpl("docker", [
|
|
388
|
-
"inspect",
|
|
389
|
-
"-f",
|
|
390
|
-
`{{index .Config.Labels "${key}"}}`,
|
|
391
|
-
image,
|
|
392
|
-
]);
|
|
393
|
-
return this.normalizeDockerValue(stdout.trim());
|
|
394
|
-
}
|
|
395
|
-
catch {
|
|
396
|
-
return undefined;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
async removeMigrateSnapshot(containerName) {
|
|
400
|
-
try {
|
|
401
|
-
await this.execFileImpl("docker", [
|
|
402
|
-
"rmi",
|
|
403
|
-
`${DockerContainerManager.MIGRATE_IMAGE_PREFIX}:${containerName}`,
|
|
404
|
-
]);
|
|
405
|
-
}
|
|
406
|
-
catch { }
|
|
407
|
-
}
|
|
408
|
-
async listMigrateSnapshotContainerNames() {
|
|
409
|
-
try {
|
|
410
|
-
const { stdout } = await this.execFileImpl("docker", [
|
|
411
|
-
"images",
|
|
412
|
-
DockerContainerManager.MIGRATE_IMAGE_PREFIX,
|
|
413
|
-
"--format",
|
|
414
|
-
"{{.Tag}}",
|
|
415
|
-
]);
|
|
416
|
-
return this.parseNameLines(stdout);
|
|
417
|
-
}
|
|
418
|
-
catch {
|
|
419
|
-
return [];
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
async removeDanglingMigrateImages() {
|
|
423
|
-
let stdout;
|
|
424
|
-
try {
|
|
425
|
-
({ stdout } = await this.execFileImpl("docker", [
|
|
426
|
-
"images",
|
|
427
|
-
`${DockerContainerManager.MIGRATE_IMAGE_PREFIX}`,
|
|
428
|
-
"--format",
|
|
429
|
-
"{{.Repository}}:{{.Tag}}",
|
|
430
|
-
]));
|
|
431
|
-
}
|
|
432
|
-
catch {
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
for (const image of this.parseNameLines(stdout)) {
|
|
436
|
-
const containerName = image.slice(DockerContainerManager.MIGRATE_IMAGE_PREFIX.length + 1);
|
|
437
|
-
const status = await this.inspectStatus(containerName);
|
|
438
|
-
if (status === "missing")
|
|
439
|
-
continue;
|
|
440
|
-
if (await this.containerRunsFromImage(containerName, image))
|
|
441
|
-
continue;
|
|
442
|
-
try {
|
|
443
|
-
await this.execFileImpl("docker", ["rmi", image]);
|
|
444
|
-
log.logInfo(`Removed layout-migration snapshot image ${image}`);
|
|
445
|
-
}
|
|
446
|
-
catch (err) {
|
|
447
|
-
log.logWarning(`Could not remove snapshot image ${image}`, String(err));
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
async containerRunsFromImage(containerName, image) {
|
|
452
|
-
const { stdout } = await this.execFileImpl("docker", [
|
|
453
|
-
"inspect",
|
|
454
|
-
"-f",
|
|
455
|
-
"{{.Config.Image}}",
|
|
456
|
-
containerName,
|
|
457
|
-
]);
|
|
458
|
-
return stdout.trim() === image;
|
|
459
|
-
}
|
|
460
|
-
async reconcile() {
|
|
461
|
-
const discovered = new Set();
|
|
462
|
-
const labeledNames = await this.listContainerNamesByLabel();
|
|
463
|
-
for (const name of labeledNames)
|
|
464
|
-
discovered.add(name);
|
|
465
|
-
const legacyNames = await this.listContainerNamesByPrefix();
|
|
466
|
-
for (const name of legacyNames)
|
|
467
|
-
discovered.add(name);
|
|
468
177
|
this.state.clear();
|
|
469
|
-
const inspected = await Promise.all(
|
|
178
|
+
const inspected = await Promise.all(names.map(async (containerName) => ({
|
|
470
179
|
containerName,
|
|
471
180
|
details: await this.inspectContainerDetails(containerName),
|
|
472
181
|
})));
|
|
473
|
-
const legacyRemovals = [];
|
|
474
182
|
for (const { containerName, details } of inspected) {
|
|
475
|
-
if (!details)
|
|
476
|
-
continue;
|
|
477
|
-
if (!details.conversationId) {
|
|
478
|
-
legacyRemovals.push(this.removeLegacyContainer(containerName));
|
|
183
|
+
if (!details || !mountsWithin(details.bindSources, workspaceRoot))
|
|
479
184
|
continue;
|
|
480
|
-
}
|
|
481
185
|
const containerKey = this.containerKeyFromContainerName(containerName);
|
|
482
186
|
if (!containerKey) {
|
|
483
187
|
log.logWarning(`Skipping unmanaged-style container without container key`, containerName);
|
|
484
188
|
continue;
|
|
485
189
|
}
|
|
486
|
-
if (containerKey === legacyConversationResourceKey(details.conversationId)) {
|
|
487
|
-
legacyRemovals.push(this.forceRemoveContainer(containerName, `Removed container ${containerName} keyed by pre-office resource identity`, `Failed to remove legacy-keyed container ${containerName}`).then(() => undefined));
|
|
488
|
-
continue;
|
|
489
|
-
}
|
|
490
190
|
const status = details.running ? "running" : "stopped";
|
|
491
191
|
const lastUsed = details.startedAtMs ?? Date.now();
|
|
492
192
|
this.state.set(containerKey, { status, lastUsed, containerName });
|
|
493
193
|
}
|
|
494
|
-
await Promise.all(legacyRemovals);
|
|
495
194
|
const running = Array.from(this.state.values()).filter((s) => s.status === "running").length;
|
|
496
195
|
const stopped = this.state.size - running;
|
|
497
196
|
log.logInfo(`Reconciled ${this.state.size} managed containers (running=${running}, stopped=${stopped})`);
|
|
@@ -511,7 +210,6 @@ export class DockerContainerManager {
|
|
|
511
210
|
}
|
|
512
211
|
async runContainer(containerKey, containerName, mounts, options) {
|
|
513
212
|
const networkName = await this.ensureNetwork(containerKey);
|
|
514
|
-
const homeVolume = await this.ensureHomeVolume(containerKey);
|
|
515
213
|
log.logInfo(`Creating container ${containerName} from image ${this.image}`);
|
|
516
214
|
const labels = [
|
|
517
215
|
"--label",
|
|
@@ -542,8 +240,6 @@ export class DockerContainerManager {
|
|
|
542
240
|
"1024",
|
|
543
241
|
...labels,
|
|
544
242
|
...this.resourceLimitArgs(this.effectiveLimits(containerKey)),
|
|
545
|
-
"-v",
|
|
546
|
-
`${homeVolume}:${GUEST_HOME}`,
|
|
547
243
|
...this.mountArgs(mounts),
|
|
548
244
|
this.image,
|
|
549
245
|
"sleep",
|
|
@@ -593,23 +289,19 @@ export class DockerContainerManager {
|
|
|
593
289
|
});
|
|
594
290
|
}
|
|
595
291
|
}
|
|
596
|
-
async runtimeDrift(containerKey, containerName, mounts,
|
|
597
|
-
|
|
598
|
-
if (this.hasBindMountDrift(binds, mounts))
|
|
292
|
+
async runtimeDrift(containerKey, containerName, mounts, status) {
|
|
293
|
+
if (this.hasBindMountDrift(await this.inspectBindMounts(containerName), mounts))
|
|
599
294
|
return "binds";
|
|
600
295
|
if (await this.hasMountSignatureDrift(containerName, mounts))
|
|
601
296
|
return "mount-content";
|
|
602
297
|
if (await this.hasNetworkModeDrift(containerKey, containerName))
|
|
603
298
|
return "network";
|
|
604
|
-
if (
|
|
299
|
+
if (status === "stopped" && (await this.hasImageDrift(containerName)))
|
|
605
300
|
return "image";
|
|
606
|
-
}
|
|
607
301
|
return undefined;
|
|
608
302
|
}
|
|
609
303
|
hasBindMountDrift(binds, mounts) {
|
|
610
|
-
|
|
611
|
-
const actual = binds.filter((bind) => !this.isHomeVolumeBind(bind));
|
|
612
|
-
return !this.sameBinds(expected, actual);
|
|
304
|
+
return !this.sameBinds(this.expectedBinds(mounts), [...binds]);
|
|
613
305
|
}
|
|
614
306
|
expectedBinds(mounts) {
|
|
615
307
|
return mounts
|
|
@@ -675,21 +367,6 @@ export class DockerContainerManager {
|
|
|
675
367
|
}
|
|
676
368
|
return [...parsed].toSorted();
|
|
677
369
|
}
|
|
678
|
-
isHomeVolumeBind(bind) {
|
|
679
|
-
const separator = bind.indexOf(":");
|
|
680
|
-
if (separator === -1)
|
|
681
|
-
return false;
|
|
682
|
-
const source = bind.slice(0, separator);
|
|
683
|
-
const target = bind.slice(separator + 1).replace(/:r[ow]$/, "");
|
|
684
|
-
return source.startsWith(DockerContainerManager.homeVolumeName("")) && target === GUEST_HOME;
|
|
685
|
-
}
|
|
686
|
-
hasHomeVolumeBind(containerKey, binds) {
|
|
687
|
-
const expected = `${DockerContainerManager.homeVolumeName(containerKey)}:${GUEST_HOME}`;
|
|
688
|
-
return binds.includes(expected);
|
|
689
|
-
}
|
|
690
|
-
async hasHomeVolume(containerKey, containerName) {
|
|
691
|
-
return this.hasHomeVolumeBind(containerKey, await this.inspectBindMounts(containerName));
|
|
692
|
-
}
|
|
693
370
|
async hasImageDrift(containerName) {
|
|
694
371
|
const desired = await this.localImageId();
|
|
695
372
|
if (!desired)
|
|
@@ -717,107 +394,12 @@ export class DockerContainerManager {
|
|
|
717
394
|
return undefined;
|
|
718
395
|
}
|
|
719
396
|
}
|
|
720
|
-
async
|
|
721
|
-
const volumeName = DockerContainerManager.homeVolumeName(containerKey);
|
|
722
|
-
await this.execFileImpl("docker", [
|
|
723
|
-
"volume",
|
|
724
|
-
"create",
|
|
725
|
-
"--label",
|
|
726
|
-
DockerContainerManager.MANAGED_LABEL,
|
|
727
|
-
"--label",
|
|
728
|
-
`${DockerContainerManager.VAULT_ID_LABEL_KEY}=${containerKey}`,
|
|
729
|
-
volumeName,
|
|
730
|
-
]);
|
|
731
|
-
return volumeName;
|
|
732
|
-
}
|
|
733
|
-
async removeHomeVolume(containerKey) {
|
|
734
|
-
const volumeName = DockerContainerManager.homeVolumeName(containerKey);
|
|
735
|
-
try {
|
|
736
|
-
await this.execFileImpl("docker", ["volume", "rm", volumeName]);
|
|
737
|
-
log.logInfo(`Home volume ${volumeName} removed`);
|
|
738
|
-
}
|
|
739
|
-
catch (err) {
|
|
740
|
-
if (isDockerNotFoundError(err))
|
|
741
|
-
return;
|
|
742
|
-
log.logWarning(`Failed to remove home volume ${volumeName}`, errorMessage(err));
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
async replaceHomeVolumeContainer(containerKey, containerName, mounts, options) {
|
|
397
|
+
async replaceContainer(containerKey, containerName, mounts, options) {
|
|
746
398
|
const removed = await this.forceRemoveContainer(containerName, `Container ${containerName} removed for replacement`, `Failed to remove container ${containerName} for replacement`);
|
|
747
399
|
if (!removed)
|
|
748
400
|
throw new Error(`Failed to remove container ${containerName} for replacement`);
|
|
749
401
|
await this.runContainer(containerKey, containerName, mounts, options);
|
|
750
402
|
}
|
|
751
|
-
async inventory() {
|
|
752
|
-
const names = await this.listContainerNamesByLabel();
|
|
753
|
-
const entries = [];
|
|
754
|
-
for (const containerName of names) {
|
|
755
|
-
const containerKey = this.containerKeyFromContainerName(containerName);
|
|
756
|
-
const status = await this.inspectStatus(containerName);
|
|
757
|
-
if (status === "missing")
|
|
758
|
-
continue;
|
|
759
|
-
const homeVolume = containerKey
|
|
760
|
-
? await this.hasHomeVolume(containerKey, containerName)
|
|
761
|
-
: false;
|
|
762
|
-
entries.push({
|
|
763
|
-
containerName,
|
|
764
|
-
containerKey,
|
|
765
|
-
running: status === "running",
|
|
766
|
-
homeVolume,
|
|
767
|
-
imageStale: await this.hasImageDrift(containerName),
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
return entries;
|
|
771
|
-
}
|
|
772
|
-
async systemChanges(containerName) {
|
|
773
|
-
const { stdout } = await this.execFileImpl("docker", ["diff", containerName]);
|
|
774
|
-
return this.parseNameLines(stdout).filter((line) => {
|
|
775
|
-
const path = line.slice(2);
|
|
776
|
-
return !this.isUnderGuestPath(path, GUEST_HOME) && !this.isUnderGuestPath(path, "/workspace");
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
isUnderGuestPath(path, root) {
|
|
780
|
-
return path === root || path.startsWith(`${root}/`);
|
|
781
|
-
}
|
|
782
|
-
migrateToHomeVolume(containerKey) {
|
|
783
|
-
return this.serialize(containerKey, () => this.migrateToHomeVolumeInner(containerKey));
|
|
784
|
-
}
|
|
785
|
-
async migrateToHomeVolumeInner(containerKey) {
|
|
786
|
-
const containerName = DockerContainerManager.containerName(containerKey);
|
|
787
|
-
const status = await this.inspectStatus(containerName);
|
|
788
|
-
if (status === "missing")
|
|
789
|
-
return "missing";
|
|
790
|
-
if (await this.hasHomeVolume(containerKey, containerName))
|
|
791
|
-
return "already-migrated";
|
|
792
|
-
const details = await this.inspectContainerDetails(containerName);
|
|
793
|
-
const mounts = (await this.inspectBindMounts(containerName))
|
|
794
|
-
.filter((bind) => !this.isHomeVolumeBind(bind))
|
|
795
|
-
.map(bindSpecToMount);
|
|
796
|
-
const snapshotImage = `${DockerContainerManager.MIGRATE_IMAGE_PREFIX}:${containerName}`;
|
|
797
|
-
log.logInfo(`Migrating container ${containerName} to a home volume`);
|
|
798
|
-
await this.execFileImpl("docker", ["commit", containerName, snapshotImage]);
|
|
799
|
-
const homeVolume = await this.ensureHomeVolume(containerKey);
|
|
800
|
-
await this.execFileImpl("docker", [
|
|
801
|
-
"run",
|
|
802
|
-
"--rm",
|
|
803
|
-
"--network",
|
|
804
|
-
"none",
|
|
805
|
-
"-v",
|
|
806
|
-
`${homeVolume}:${GUEST_HOME}`,
|
|
807
|
-
snapshotImage,
|
|
808
|
-
"true",
|
|
809
|
-
]);
|
|
810
|
-
const removed = await this.forceRemoveContainer(containerName, `Container ${containerName} removed for home-volume migration`, `Failed to remove container ${containerName} for home-volume migration`);
|
|
811
|
-
if (!removed)
|
|
812
|
-
throw new Error(`Failed to remove container ${containerName}`);
|
|
813
|
-
await this.runContainer(containerKey, containerName, mounts, details?.conversationId ? { conversationId: details.conversationId } : {});
|
|
814
|
-
if (status === "stopped")
|
|
815
|
-
await this.execFileImpl("docker", ["stop", containerName]);
|
|
816
|
-
await this.removeMigrateSnapshot(containerName);
|
|
817
|
-
this.setState(containerKey, status === "stopped" ? "stopped" : "running", containerName);
|
|
818
|
-
log.logInfo(`Container ${containerName} migrated to home volume ${homeVolume}`);
|
|
819
|
-
return "migrated";
|
|
820
|
-
}
|
|
821
403
|
async hasNetworkModeDrift(containerKey, containerName) {
|
|
822
404
|
const expected = DockerContainerManager.networkName(containerKey);
|
|
823
405
|
const { stdout } = await this.execFileImpl("docker", [
|
|
@@ -888,23 +470,6 @@ export class DockerContainerManager {
|
|
|
888
470
|
return [];
|
|
889
471
|
}
|
|
890
472
|
}
|
|
891
|
-
async listContainerNamesByPrefix() {
|
|
892
|
-
try {
|
|
893
|
-
const { stdout } = await this.execFileImpl("docker", [
|
|
894
|
-
"ps",
|
|
895
|
-
"-a",
|
|
896
|
-
"--filter",
|
|
897
|
-
`name=${DockerContainerManager.containerName("")}`,
|
|
898
|
-
"--format",
|
|
899
|
-
"{{.Names}}",
|
|
900
|
-
]);
|
|
901
|
-
return this.parseNameLines(stdout);
|
|
902
|
-
}
|
|
903
|
-
catch (err) {
|
|
904
|
-
log.logWarning("Failed to list legacy managed containers", errorMessage(err));
|
|
905
|
-
return [];
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
473
|
parseNameLines(stdout) {
|
|
909
474
|
return stdout
|
|
910
475
|
.split("\n")
|
|
@@ -916,14 +481,14 @@ export class DockerContainerManager {
|
|
|
916
481
|
const { stdout } = await this.execFileImpl("docker", [
|
|
917
482
|
"inspect",
|
|
918
483
|
"-f",
|
|
919
|
-
`{{.State.Running}}\t{{.State.StartedAt}}\t{{index .Config.Labels "${DockerContainerManager.VAULT_ID_LABEL_KEY}"}}\t{{index .Config.Labels "${DockerContainerManager.CONVERSATION_ID_LABEL_KEY}"}}`,
|
|
484
|
+
`{{.State.Running}}\t{{.State.StartedAt}}\t{{index .Config.Labels "${DockerContainerManager.VAULT_ID_LABEL_KEY}"}}\t{{index .Config.Labels "${DockerContainerManager.CONVERSATION_ID_LABEL_KEY}"}}\t{{json .HostConfig.Binds}}`,
|
|
920
485
|
containerName,
|
|
921
486
|
]);
|
|
922
|
-
const [runningRaw, startedAtRaw, , conversationIdRaw] = stdout.trim().split("\t");
|
|
487
|
+
const [runningRaw, startedAtRaw, , conversationIdRaw, bindsRaw] = stdout.trim().split("\t");
|
|
923
488
|
const running = runningRaw === "true";
|
|
924
489
|
const startedAtMs = this.parseDockerTimestamp(startedAtRaw);
|
|
925
490
|
const conversationId = this.normalizeDockerValue(conversationIdRaw);
|
|
926
|
-
return { running, startedAtMs, conversationId };
|
|
491
|
+
return { running, startedAtMs, conversationId, bindSources: parseBindSources(bindsRaw) };
|
|
927
492
|
}
|
|
928
493
|
catch (err) {
|
|
929
494
|
log.logWarning(`Failed to inspect container ${containerName} during reconcile`, errorMessage(err));
|
|
@@ -964,8 +529,19 @@ export class DockerContainerManager {
|
|
|
964
529
|
return false;
|
|
965
530
|
}
|
|
966
531
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
532
|
+
}
|
|
533
|
+
function parseBindSources(raw) {
|
|
534
|
+
const parsed = JSON.parse(raw && raw.length > 0 ? raw : "null");
|
|
535
|
+
if (!Array.isArray(parsed))
|
|
536
|
+
return [];
|
|
537
|
+
return parsed
|
|
538
|
+
.filter((bind) => typeof bind === "string")
|
|
539
|
+
.map((bind) => bind.split(":", 1)[0] ?? bind);
|
|
540
|
+
}
|
|
541
|
+
function mountsWithin(sources, root) {
|
|
542
|
+
return sources.some((source) => {
|
|
543
|
+
const path = relative(root, source);
|
|
544
|
+
return path !== "" && !path.startsWith("..") && !isAbsolute(path);
|
|
545
|
+
});
|
|
970
546
|
}
|
|
971
547
|
//# sourceMappingURL=provisioner.js.map
|