@openpalm/lib 0.10.2 → 0.11.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +7 -3
- package/src/control-plane/admin-token.ts +73 -0
- package/src/control-plane/akm-vault.test.ts +105 -0
- package/src/control-plane/akm-vault.ts +307 -0
- package/src/control-plane/channels.ts +3 -3
- package/src/control-plane/cleanup-guardrails.test.ts +8 -9
- package/src/control-plane/compose-args.test.ts +25 -24
- package/src/control-plane/compose-errors.test.ts +106 -0
- package/src/control-plane/compose-errors.ts +117 -0
- package/src/control-plane/config-persistence.ts +103 -65
- package/src/control-plane/core-assets.test.ts +104 -0
- package/src/control-plane/core-assets.ts +54 -57
- package/src/control-plane/docker.ts +55 -21
- package/src/control-plane/env.test.ts +25 -1
- package/src/control-plane/env.ts +80 -0
- package/src/control-plane/home.ts +66 -69
- package/src/control-plane/host-opencode.test.ts +260 -0
- package/src/control-plane/host-opencode.ts +229 -0
- package/src/control-plane/install-edge-cases.test.ts +187 -289
- package/src/control-plane/install-lock.ts +157 -0
- package/src/control-plane/lifecycle.ts +34 -65
- package/src/control-plane/markdown-task.ts +200 -0
- package/src/control-plane/migrate-0110.test.ts +177 -0
- package/src/control-plane/migrate-0110.ts +99 -0
- package/src/control-plane/paths.ts +82 -0
- package/src/control-plane/provider-config.ts +2 -2
- package/src/control-plane/provider-models.ts +154 -0
- package/src/control-plane/registry-components.test.ts +105 -27
- package/src/control-plane/registry.test.ts +49 -47
- package/src/control-plane/registry.ts +71 -50
- package/src/control-plane/rollback.ts +17 -16
- package/src/control-plane/scheduler.ts +75 -262
- package/src/control-plane/secret-backend.test.ts +98 -111
- package/src/control-plane/secret-backend.ts +221 -181
- package/src/control-plane/secret-mappings.ts +4 -8
- package/src/control-plane/secrets.ts +93 -51
- package/src/control-plane/setup-config.schema.json +5 -17
- package/src/control-plane/setup-status.ts +9 -29
- package/src/control-plane/setup-validation.ts +23 -23
- package/src/control-plane/setup.test.ts +138 -239
- package/src/control-plane/setup.ts +215 -130
- package/src/control-plane/skeleton-guardrail.test.ts +151 -0
- package/src/control-plane/spec-to-env.test.ts +59 -58
- package/src/control-plane/spec-to-env.ts +52 -142
- package/src/control-plane/spec-validator.ts +2 -99
- package/src/control-plane/stack-spec.test.ts +21 -77
- package/src/control-plane/stack-spec.ts +7 -83
- package/src/control-plane/types.ts +12 -28
- package/src/control-plane/ui-assets.ts +349 -0
- package/src/control-plane/validate.ts +44 -79
- package/src/index.ts +86 -48
- package/src/logger.test.ts +228 -0
- package/src/logger.ts +71 -1
- package/src/provider-constants.ts +22 -1
- package/src/control-plane/audit.ts +0 -40
- package/src/control-plane/env-schema-validation.test.ts +0 -118
- package/src/control-plane/memory-config.ts +0 -298
- package/src/control-plane/redact-schema.ts +0 -50
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Registry catalog discovery and refresh.
|
|
3
3
|
*
|
|
4
|
-
* `OP_HOME/registry` is the only persistent catalog location.
|
|
4
|
+
* `OP_HOME/state/registry` is the only persistent catalog location.
|
|
5
5
|
* Install seeds it once; refresh replaces it explicitly.
|
|
6
6
|
*/
|
|
7
7
|
import { cpSync, existsSync, mkdtempSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
@@ -46,10 +46,10 @@ export function isValidComponentName(name: string): boolean {
|
|
|
46
46
|
|
|
47
47
|
const DEFAULT_REPO = 'itlackey/openpalm';
|
|
48
48
|
|
|
49
|
-
export
|
|
49
|
+
export type RegistryConfig = {
|
|
50
50
|
repoUrl: string;
|
|
51
51
|
branch: string;
|
|
52
|
-
}
|
|
52
|
+
};
|
|
53
53
|
|
|
54
54
|
export function getRegistryConfig(): RegistryConfig {
|
|
55
55
|
return {
|
|
@@ -63,7 +63,7 @@ export type RegistryAutomationEntry = {
|
|
|
63
63
|
type: 'automation';
|
|
64
64
|
description: string;
|
|
65
65
|
schedule: string;
|
|
66
|
-
|
|
66
|
+
content: string;
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
export type RegistryComponentEntry = {
|
|
@@ -95,7 +95,10 @@ function countValidAddons(rootDir: string): number {
|
|
|
95
95
|
return readdirSync(addonsDir, { withFileTypes: true }).filter((entry) => {
|
|
96
96
|
if (!entry.isDirectory() || !isValidComponentName(entry.name)) return false;
|
|
97
97
|
const addonDir = join(addonsDir, entry.name);
|
|
98
|
-
|
|
98
|
+
// An addon is valid if it has a compose.yml. Overlay-only addons that only
|
|
99
|
+
// patch existing services (ports, env, volumes) do not need an .env.schema;
|
|
100
|
+
// full addons that introduce services and env vars do.
|
|
101
|
+
return existsSync(join(addonDir, 'compose.yml'));
|
|
99
102
|
}).length;
|
|
100
103
|
}
|
|
101
104
|
|
|
@@ -103,8 +106,8 @@ function countValidAutomations(rootDir: string): number {
|
|
|
103
106
|
const automationsDir = join(rootDir, 'automations');
|
|
104
107
|
if (!existsSync(automationsDir)) return 0;
|
|
105
108
|
return readdirSync(automationsDir).filter((file) => {
|
|
106
|
-
if (!file.endsWith('.
|
|
107
|
-
return isValidComponentName(file.replace(/\.
|
|
109
|
+
if (!file.endsWith('.md')) return false;
|
|
110
|
+
return isValidComponentName(file.replace(/\.md$/, ''));
|
|
108
111
|
}).length;
|
|
109
112
|
}
|
|
110
113
|
|
|
@@ -123,8 +126,8 @@ export function verifyRegistryCatalog(rootDir = resolveRegistryDir()): RegistryC
|
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
export function materializeRegistryCatalog(sourceRoot: string): string {
|
|
126
|
-
const sourceAddonsDir = join(sourceRoot, '.openpalm', 'registry', 'addons');
|
|
127
|
-
const sourceAutomationsDir = join(sourceRoot, '.openpalm', 'registry', 'automations');
|
|
129
|
+
const sourceAddonsDir = join(sourceRoot, '.openpalm', 'state', 'registry', 'addons');
|
|
130
|
+
const sourceAutomationsDir = join(sourceRoot, '.openpalm', 'state', 'registry', 'automations');
|
|
128
131
|
const tempRoot = mkdtempSync(join(tmpdir(), 'openpalm-registry-materialize-'));
|
|
129
132
|
|
|
130
133
|
try {
|
|
@@ -184,37 +187,46 @@ export function discoverRegistryComponents(): Record<string, RegistryComponentEn
|
|
|
184
187
|
if (!entry.isDirectory() || !VALID_NAME_RE.test(entry.name)) continue;
|
|
185
188
|
const addonDir = join(addonsDir, entry.name);
|
|
186
189
|
const composeFile = join(addonDir, 'compose.yml');
|
|
190
|
+
if (!existsSync(composeFile)) continue;
|
|
191
|
+
|
|
192
|
+
// .env.schema is optional: overlay-only addons (e.g. a port toggle) do
|
|
193
|
+
// not introduce new env vars, so they ship just compose.yml.
|
|
187
194
|
const schemaFile = join(addonDir, '.env.schema');
|
|
188
|
-
|
|
195
|
+
const schema = existsSync(schemaFile) ? readFileSync(schemaFile, 'utf-8') : '';
|
|
189
196
|
|
|
190
197
|
result[entry.name] = {
|
|
191
198
|
compose: readFileSync(composeFile, 'utf-8'),
|
|
192
|
-
schema
|
|
199
|
+
schema,
|
|
193
200
|
};
|
|
194
201
|
}
|
|
195
202
|
|
|
196
203
|
return result;
|
|
197
204
|
}
|
|
198
205
|
|
|
199
|
-
export function discoverRegistryAutomations(): RegistryAutomationEntry[] {
|
|
206
|
+
export function discoverRegistryAutomations(stashDir: string): RegistryAutomationEntry[] {
|
|
200
207
|
const automationsDir = resolveRegistryAutomationsDir();
|
|
201
208
|
if (!existsSync(automationsDir)) return [];
|
|
202
209
|
|
|
203
210
|
return readdirSync(automationsDir)
|
|
204
|
-
.filter((file) => file.endsWith('.
|
|
211
|
+
.filter((file) => file.endsWith('.md'))
|
|
205
212
|
.map((file) => {
|
|
206
|
-
const name = file.replace(/\.
|
|
213
|
+
const name = file.replace(/\.md$/, '');
|
|
207
214
|
if (!VALID_NAME_RE.test(name)) return null;
|
|
208
215
|
|
|
209
|
-
const
|
|
216
|
+
const content = readFileSync(join(automationsDir, file), 'utf-8');
|
|
210
217
|
let description = '';
|
|
211
218
|
let schedule = '';
|
|
212
219
|
|
|
220
|
+
// Extract frontmatter metadata (between --- delimiters)
|
|
213
221
|
try {
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
222
|
+
const after = content.startsWith('---') ? content.slice(3) : '';
|
|
223
|
+
const end = after.indexOf('\n---');
|
|
224
|
+
if (end !== -1) {
|
|
225
|
+
const parsed = parseYaml(after.slice(0, end));
|
|
226
|
+
if (parsed && typeof parsed === 'object') {
|
|
227
|
+
description = (parsed as Record<string, unknown>).description as string ?? '';
|
|
228
|
+
schedule = (parsed as Record<string, unknown>).schedule as string ?? '';
|
|
229
|
+
}
|
|
218
230
|
}
|
|
219
231
|
} catch {
|
|
220
232
|
// best-effort metadata extraction
|
|
@@ -225,7 +237,7 @@ export function discoverRegistryAutomations(): RegistryAutomationEntry[] {
|
|
|
225
237
|
type: 'automation' as const,
|
|
226
238
|
description,
|
|
227
239
|
schedule,
|
|
228
|
-
|
|
240
|
+
content,
|
|
229
241
|
};
|
|
230
242
|
})
|
|
231
243
|
.filter((entry): entry is RegistryAutomationEntry => entry !== null);
|
|
@@ -233,9 +245,9 @@ export function discoverRegistryAutomations(): RegistryAutomationEntry[] {
|
|
|
233
245
|
|
|
234
246
|
export function getRegistryAutomation(name: string): string | null {
|
|
235
247
|
if (!VALID_NAME_RE.test(name)) return null;
|
|
236
|
-
const
|
|
237
|
-
if (!existsSync(
|
|
238
|
-
return readFileSync(
|
|
248
|
+
const mdPath = join(resolveRegistryAutomationsDir(), `${name}.md`);
|
|
249
|
+
if (!existsSync(mdPath)) return null;
|
|
250
|
+
return readFileSync(mdPath, 'utf-8');
|
|
239
251
|
}
|
|
240
252
|
|
|
241
253
|
export function getRegistryAddonConfig(homeDir: string, name: string): RegistryAddonConfig {
|
|
@@ -243,11 +255,14 @@ export function getRegistryAddonConfig(homeDir: string, name: string): RegistryA
|
|
|
243
255
|
throw new Error(`Invalid addon name: ${name}`);
|
|
244
256
|
}
|
|
245
257
|
|
|
246
|
-
|
|
258
|
+
// Overlay-only addons (compose.yml only, no .env.schema) have no env vars
|
|
259
|
+
// to render, so the schema reads as an empty string.
|
|
260
|
+
const schemaPath = `state/registry/addons/${name}/.env.schema`;
|
|
261
|
+
const schemaFile = join(homeDir, schemaPath);
|
|
247
262
|
return {
|
|
248
263
|
schemaPath,
|
|
249
|
-
userEnvPath: '
|
|
250
|
-
envSchema: readFileSync(
|
|
264
|
+
userEnvPath: 'config/stack/stack.env',
|
|
265
|
+
envSchema: existsSync(schemaFile) ? readFileSync(schemaFile, 'utf-8') : '',
|
|
251
266
|
};
|
|
252
267
|
}
|
|
253
268
|
|
|
@@ -261,7 +276,7 @@ export function listAvailableAddonIds(): string[] {
|
|
|
261
276
|
}
|
|
262
277
|
|
|
263
278
|
export function listEnabledAddonIds(homeDir: string): string[] {
|
|
264
|
-
const addonsDir = join(homeDir, 'stack', 'addons');
|
|
279
|
+
const addonsDir = join(homeDir, 'config', 'stack', 'addons');
|
|
265
280
|
if (!existsSync(addonsDir)) return [];
|
|
266
281
|
|
|
267
282
|
return readdirSync(addonsDir, { withFileTypes: true })
|
|
@@ -274,19 +289,21 @@ function copyAddonFromRegistry(homeDir: string, name: string): void {
|
|
|
274
289
|
if (!VALID_NAME_RE.test(name)) throw new Error(`Invalid addon name: ${name}`);
|
|
275
290
|
|
|
276
291
|
const sourceDir = join(resolveRegistryAddonsDir(), name);
|
|
277
|
-
|
|
292
|
+
// compose.yml is the only required file. Overlay-only addons may omit
|
|
293
|
+
// .env.schema entirely.
|
|
294
|
+
if (!existsSync(join(sourceDir, 'compose.yml'))) {
|
|
278
295
|
throw new Error(`Addon "${name}" not found in registry`);
|
|
279
296
|
}
|
|
280
297
|
|
|
281
|
-
const targetDir = join(homeDir, 'stack', 'addons', name);
|
|
298
|
+
const targetDir = join(homeDir, 'config', 'stack', 'addons', name);
|
|
282
299
|
rmSync(targetDir, { recursive: true, force: true });
|
|
283
|
-
mkdirSync(join(homeDir, 'stack', 'addons'), { recursive: true });
|
|
300
|
+
mkdirSync(join(homeDir, 'config', 'stack', 'addons'), { recursive: true });
|
|
284
301
|
cpSync(sourceDir, targetDir, { recursive: true });
|
|
285
302
|
}
|
|
286
303
|
|
|
287
304
|
function removeEnabledAddon(homeDir: string, name: string): void {
|
|
288
305
|
if (!VALID_NAME_RE.test(name)) throw new Error(`Invalid addon name: ${name}`);
|
|
289
|
-
rmSync(join(homeDir, 'stack', 'addons', name), { recursive: true, force: true });
|
|
306
|
+
rmSync(join(homeDir, 'config', 'stack', 'addons', name), { recursive: true, force: true });
|
|
290
307
|
}
|
|
291
308
|
|
|
292
309
|
function readAddonServiceNames(composePath: string): string[] {
|
|
@@ -310,8 +327,8 @@ export function getAddonServiceNames(homeDir: string, name: string): string[] {
|
|
|
310
327
|
if (!VALID_NAME_RE.test(name)) throw new Error(`Invalid addon name: ${name}`);
|
|
311
328
|
|
|
312
329
|
const composeCandidates = [
|
|
313
|
-
join(homeDir, "stack", "addons", name, "compose.yml"),
|
|
314
|
-
join(homeDir, "registry", "addons", name, "compose.yml"),
|
|
330
|
+
join(homeDir, "config", "stack", "addons", name, "compose.yml"),
|
|
331
|
+
join(homeDir, "state", "registry", "addons", name, "compose.yml"),
|
|
315
332
|
];
|
|
316
333
|
|
|
317
334
|
for (const composePath of composeCandidates) {
|
|
@@ -325,8 +342,8 @@ export function getAddonServiceNames(homeDir: string, name: string): string[] {
|
|
|
325
342
|
export function enableAddon(homeDir: string, name: string): MutationResult {
|
|
326
343
|
try {
|
|
327
344
|
copyAddonFromRegistry(homeDir, name);
|
|
328
|
-
// Pre-create the addon
|
|
329
|
-
mkdirSync(join(homeDir, '
|
|
345
|
+
// Pre-create the addon services directory so Docker doesn't create it as root
|
|
346
|
+
mkdirSync(join(homeDir, 'services', name), { recursive: true });
|
|
330
347
|
return { ok: true };
|
|
331
348
|
} catch (error) {
|
|
332
349
|
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
@@ -342,7 +359,7 @@ export function disableAddonByName(homeDir: string, name: string): MutationResul
|
|
|
342
359
|
}
|
|
343
360
|
}
|
|
344
361
|
|
|
345
|
-
export function setAddonEnabled(homeDir: string,
|
|
362
|
+
export function setAddonEnabled(homeDir: string, stackDir: string, name: string, enabled: boolean): AddonMutationResult {
|
|
346
363
|
if (!VALID_NAME_RE.test(name)) {
|
|
347
364
|
return { ok: false, error: `Invalid addon name: ${name}` };
|
|
348
365
|
}
|
|
@@ -367,9 +384,9 @@ export function setAddonEnabled(homeDir: string, vaultDir: string, name: string,
|
|
|
367
384
|
if (!mutation.ok) return mutation;
|
|
368
385
|
|
|
369
386
|
if (enabled) {
|
|
370
|
-
const composePath = join(homeDir, "stack", "addons", name, "compose.yml");
|
|
387
|
+
const composePath = join(homeDir, "config", "stack", "addons", name, "compose.yml");
|
|
371
388
|
if (isChannelAddon(composePath)) {
|
|
372
|
-
writeChannelSecrets(
|
|
389
|
+
writeChannelSecrets(stackDir, { [name]: randomHex(16) });
|
|
373
390
|
}
|
|
374
391
|
}
|
|
375
392
|
|
|
@@ -381,38 +398,42 @@ export function setAddonEnabled(homeDir: string, vaultDir: string, name: string,
|
|
|
381
398
|
};
|
|
382
399
|
}
|
|
383
400
|
|
|
384
|
-
export function installAutomationFromRegistry(name: string,
|
|
401
|
+
export function installAutomationFromRegistry(name: string, stashDir: string): MutationResult {
|
|
385
402
|
if (!VALID_NAME_RE.test(name)) {
|
|
386
403
|
return { ok: false, error: `Invalid automation name: ${name}` };
|
|
387
404
|
}
|
|
388
405
|
|
|
389
|
-
const
|
|
390
|
-
if (!
|
|
406
|
+
const markdownContent = getRegistryAutomation(name);
|
|
407
|
+
if (!markdownContent) {
|
|
391
408
|
return { ok: false, error: `Automation "${name}" not found in registry` };
|
|
392
409
|
}
|
|
393
410
|
|
|
394
|
-
const
|
|
395
|
-
mkdirSync(
|
|
411
|
+
const tasksDir = join(stashDir, 'tasks');
|
|
412
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
396
413
|
|
|
397
|
-
const
|
|
398
|
-
if (existsSync(
|
|
414
|
+
const mdPath = join(tasksDir, `${name}.md`);
|
|
415
|
+
if (existsSync(mdPath)) {
|
|
399
416
|
return { ok: false, error: `Automation "${name}" is already installed` };
|
|
400
417
|
}
|
|
401
418
|
|
|
402
|
-
writeFileSync(
|
|
419
|
+
writeFileSync(mdPath, markdownContent);
|
|
420
|
+
// The assistant container's 60-second akm tasks sync loop picks up the new
|
|
421
|
+
// file from the shared stash mount and registers it with OS cron.
|
|
403
422
|
return { ok: true };
|
|
404
423
|
}
|
|
405
424
|
|
|
406
|
-
export function uninstallAutomation(name: string,
|
|
425
|
+
export function uninstallAutomation(name: string, stashDir: string): MutationResult {
|
|
407
426
|
if (!VALID_NAME_RE.test(name)) {
|
|
408
427
|
return { ok: false, error: `Invalid automation name: ${name}` };
|
|
409
428
|
}
|
|
410
429
|
|
|
411
|
-
const
|
|
412
|
-
if (!existsSync(
|
|
430
|
+
const mdPath = join(stashDir, 'tasks', `${name}.md`);
|
|
431
|
+
if (!existsSync(mdPath)) {
|
|
413
432
|
return { ok: false, error: `Automation "${name}" is not installed` };
|
|
414
433
|
}
|
|
415
434
|
|
|
416
|
-
rmSync(
|
|
435
|
+
rmSync(mdPath, { force: true });
|
|
436
|
+
// The assistant container's 60-second akm tasks sync will notice the file
|
|
437
|
+
// is gone and deregister it from OS cron on next sync.
|
|
417
438
|
return { ok: true };
|
|
418
439
|
}
|
|
@@ -11,11 +11,12 @@ import type { ControlPlaneState } from "./types.js";
|
|
|
11
11
|
import { resolveRollbackDir } from "./home.js";
|
|
12
12
|
|
|
13
13
|
/** Files that are tracked for rollback (relative to homeDir).
|
|
14
|
-
* Only
|
|
15
|
-
*
|
|
14
|
+
* Only config/ system files are included — user-editable config files
|
|
15
|
+
* are never overwritten by lifecycle operations. */
|
|
16
16
|
const SNAPSHOT_FILES = [
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"config/stack/stack.env",
|
|
18
|
+
"config/stack/guardian.env",
|
|
19
|
+
"config/auth.json",
|
|
19
20
|
];
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -43,12 +44,12 @@ export function snapshotCurrentState(state: ControlPlaneState): void {
|
|
|
43
44
|
safeCopy(src, dest);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
// Snapshot stack/core.compose.yml
|
|
47
|
-
const coreCompose = join(state.homeDir, "stack/core.compose.yml");
|
|
48
|
-
safeCopy(coreCompose, join(rollbackDir, "stack/core.compose.yml"));
|
|
47
|
+
// Snapshot config/stack/core.compose.yml
|
|
48
|
+
const coreCompose = join(state.homeDir, "config/stack/core.compose.yml");
|
|
49
|
+
safeCopy(coreCompose, join(rollbackDir, "config/stack/core.compose.yml"));
|
|
49
50
|
|
|
50
|
-
// Snapshot stack/addons/*/compose.yml
|
|
51
|
-
const addonsDir = join(state.homeDir, "stack/addons");
|
|
51
|
+
// Snapshot config/stack/addons/*/compose.yml
|
|
52
|
+
const addonsDir = join(state.homeDir, "config/stack/addons");
|
|
52
53
|
if (existsSync(addonsDir)) {
|
|
53
54
|
for (const entry of readdirSync(addonsDir, { withFileTypes: true })) {
|
|
54
55
|
if (entry.isDirectory()) {
|
|
@@ -56,7 +57,7 @@ export function snapshotCurrentState(state: ControlPlaneState): void {
|
|
|
56
57
|
if (existsSync(addonCompose)) {
|
|
57
58
|
safeCopy(
|
|
58
59
|
addonCompose,
|
|
59
|
-
join(rollbackDir, "stack/addons", entry.name, "compose.yml"),
|
|
60
|
+
join(rollbackDir, "config/stack/addons", entry.name, "compose.yml"),
|
|
60
61
|
);
|
|
61
62
|
}
|
|
62
63
|
}
|
|
@@ -87,14 +88,14 @@ export function restoreSnapshot(state: ControlPlaneState): void {
|
|
|
87
88
|
safeCopy(src, dest);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
// Restore stack/core.compose.yml
|
|
91
|
-
const srcCoreCompose = join(rollbackDir, "stack/core.compose.yml");
|
|
91
|
+
// Restore config/stack/core.compose.yml
|
|
92
|
+
const srcCoreCompose = join(rollbackDir, "config/stack/core.compose.yml");
|
|
92
93
|
if (existsSync(srcCoreCompose)) {
|
|
93
|
-
safeCopy(srcCoreCompose, join(state.homeDir, "stack/core.compose.yml"));
|
|
94
|
+
safeCopy(srcCoreCompose, join(state.homeDir, "config/stack/core.compose.yml"));
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
// Restore stack/addons/*/compose.yml
|
|
97
|
-
const srcAddons = join(rollbackDir, "stack/addons");
|
|
97
|
+
// Restore config/stack/addons/*/compose.yml
|
|
98
|
+
const srcAddons = join(rollbackDir, "config/stack/addons");
|
|
98
99
|
if (existsSync(srcAddons)) {
|
|
99
100
|
for (const entry of readdirSync(srcAddons, { withFileTypes: true })) {
|
|
100
101
|
if (entry.isDirectory()) {
|
|
@@ -102,7 +103,7 @@ export function restoreSnapshot(state: ControlPlaneState): void {
|
|
|
102
103
|
if (existsSync(srcAddonCompose)) {
|
|
103
104
|
safeCopy(
|
|
104
105
|
srcAddonCompose,
|
|
105
|
-
join(state.homeDir, "stack/addons", entry.name, "compose.yml"),
|
|
106
|
+
join(state.homeDir, "config/stack/addons", entry.name, "compose.yml"),
|
|
106
107
|
);
|
|
107
108
|
}
|
|
108
109
|
}
|