dokku-compose 0.3.5 → 0.4.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/dist/index.js +658 -500
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import * as fs3 from "fs";
|
|
6
6
|
import * as yaml3 from "js-yaml";
|
|
7
|
+
import { createRequire } from "module";
|
|
7
8
|
|
|
8
9
|
// src/core/config.ts
|
|
9
10
|
import * as fs from "fs";
|
|
@@ -26,6 +27,9 @@ var ChecksSchema = z.union([
|
|
|
26
27
|
skipped: z.array(z.string()).optional()
|
|
27
28
|
}).catchall(z.union([z.string(), z.number(), z.boolean()]))
|
|
28
29
|
]);
|
|
30
|
+
var GitSchema = z.object({
|
|
31
|
+
deploy_branch: z.string().optional()
|
|
32
|
+
});
|
|
29
33
|
var AppSchema = z.object({
|
|
30
34
|
domains: z.union([z.array(z.string()), z.literal(false)]).optional(),
|
|
31
35
|
links: z.array(z.string()).optional(),
|
|
@@ -60,12 +64,26 @@ var AppSchema = z.object({
|
|
|
60
64
|
build: z.array(z.string()).optional(),
|
|
61
65
|
deploy: z.array(z.string()).optional(),
|
|
62
66
|
run: z.array(z.string()).optional()
|
|
63
|
-
}).optional()
|
|
67
|
+
}).optional(),
|
|
68
|
+
git: GitSchema.optional()
|
|
69
|
+
});
|
|
70
|
+
var ServiceBackupAuthSchema = z.object({
|
|
71
|
+
access_key_id: z.string(),
|
|
72
|
+
secret_access_key: z.string(),
|
|
73
|
+
region: z.string(),
|
|
74
|
+
signature_version: z.string(),
|
|
75
|
+
endpoint: z.string()
|
|
76
|
+
});
|
|
77
|
+
var ServiceBackupSchema = z.object({
|
|
78
|
+
schedule: z.string(),
|
|
79
|
+
bucket: z.string(),
|
|
80
|
+
auth: ServiceBackupAuthSchema
|
|
64
81
|
});
|
|
65
82
|
var ServiceSchema = z.object({
|
|
66
83
|
plugin: z.string(),
|
|
67
84
|
version: z.string().optional(),
|
|
68
|
-
image: z.string().optional()
|
|
85
|
+
image: z.string().optional(),
|
|
86
|
+
backup: ServiceBackupSchema.optional()
|
|
69
87
|
});
|
|
70
88
|
var PluginSchema = z.object({
|
|
71
89
|
url: z.string().url(),
|
|
@@ -82,29 +100,39 @@ var ConfigSchema = z.object({
|
|
|
82
100
|
domains: z.union([z.array(z.string()), z.literal(false)]).optional(),
|
|
83
101
|
env: EnvMapSchema.optional(),
|
|
84
102
|
nginx: z.record(z.string(), z.union([z.string(), z.number()])).optional(),
|
|
85
|
-
logs: z.record(z.string(), z.union([z.string(), z.number()])).optional()
|
|
103
|
+
logs: z.record(z.string(), z.union([z.string(), z.number()])).optional(),
|
|
104
|
+
git: GitSchema.optional()
|
|
86
105
|
});
|
|
87
106
|
function parseConfig(raw) {
|
|
88
107
|
return ConfigSchema.parse(raw);
|
|
89
108
|
}
|
|
90
109
|
|
|
91
110
|
// src/core/config.ts
|
|
111
|
+
function interpolateEnvVars(content) {
|
|
112
|
+
return content.replace(/\$\{([^}]+)\}/g, (_, name) => process.env[name] ?? "");
|
|
113
|
+
}
|
|
92
114
|
function loadConfig(filePath) {
|
|
93
115
|
if (!fs.existsSync(filePath)) {
|
|
94
116
|
throw new Error(`Config file not found: ${filePath}`);
|
|
95
117
|
}
|
|
96
|
-
const
|
|
118
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
119
|
+
const raw = yaml.load(interpolateEnvVars(content));
|
|
97
120
|
return parseConfig(raw);
|
|
98
121
|
}
|
|
99
122
|
|
|
100
123
|
// src/core/dokku.ts
|
|
101
124
|
import { execa } from "execa";
|
|
125
|
+
import { createHash } from "crypto";
|
|
126
|
+
import * as os from "os";
|
|
127
|
+
import * as path from "path";
|
|
102
128
|
function createRunner(opts = {}) {
|
|
103
129
|
const log = [];
|
|
130
|
+
const controlPath = opts.host ? path.join(os.tmpdir(), `dc-${createHash("sha1").update(opts.host).digest("hex").slice(0, 16)}.sock`) : null;
|
|
131
|
+
const sshControlFlags = controlPath ? ["-o", "ControlMaster=auto", "-o", `ControlPath=${controlPath}`, "-o", "ControlPersist=60"] : [];
|
|
104
132
|
async function execDokku(args) {
|
|
105
133
|
if (opts.host) {
|
|
106
134
|
try {
|
|
107
|
-
const result = await execa("ssh", [`dokku@${opts.host}`, ...args]);
|
|
135
|
+
const result = await execa("ssh", [...sshControlFlags, `dokku@${opts.host}`, ...args]);
|
|
108
136
|
return { stdout: result.stdout, ok: true };
|
|
109
137
|
} catch (e) {
|
|
110
138
|
return { stdout: e.stdout ?? "", ok: false };
|
|
@@ -136,10 +164,80 @@ function createRunner(opts = {}) {
|
|
|
136
164
|
if (opts.dryRun) return false;
|
|
137
165
|
const { ok } = await execDokku(args);
|
|
138
166
|
return ok;
|
|
167
|
+
},
|
|
168
|
+
async close() {
|
|
169
|
+
if (!opts.host || !controlPath) return;
|
|
170
|
+
try {
|
|
171
|
+
await execa("ssh", ["-O", "exit", "-o", `ControlPath=${controlPath}`, `dokku@${opts.host}`]);
|
|
172
|
+
} catch {
|
|
173
|
+
}
|
|
139
174
|
}
|
|
140
175
|
};
|
|
141
176
|
}
|
|
142
177
|
|
|
178
|
+
// src/core/context.ts
|
|
179
|
+
function createContext(runner) {
|
|
180
|
+
const cache = /* @__PURE__ */ new Map();
|
|
181
|
+
const commands = [];
|
|
182
|
+
return {
|
|
183
|
+
commands,
|
|
184
|
+
runner,
|
|
185
|
+
query(...args) {
|
|
186
|
+
const key = args.join("\0");
|
|
187
|
+
if (!cache.has(key)) {
|
|
188
|
+
cache.set(key, runner.query(...args));
|
|
189
|
+
}
|
|
190
|
+
return cache.get(key);
|
|
191
|
+
},
|
|
192
|
+
check(...args) {
|
|
193
|
+
return runner.check(...args);
|
|
194
|
+
},
|
|
195
|
+
async run(...args) {
|
|
196
|
+
commands.push(args);
|
|
197
|
+
await runner.run(...args);
|
|
198
|
+
},
|
|
199
|
+
close() {
|
|
200
|
+
return runner.close();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/core/change.ts
|
|
206
|
+
function computeChange(before, after) {
|
|
207
|
+
if (before === null || before === void 0 || after === null || after === void 0) {
|
|
208
|
+
return { before, after, changed: before !== after };
|
|
209
|
+
}
|
|
210
|
+
if (Array.isArray(before) && Array.isArray(after)) {
|
|
211
|
+
const beforeSet = new Set(before);
|
|
212
|
+
const afterSet = new Set(after);
|
|
213
|
+
const added = after.filter((x) => !beforeSet.has(x));
|
|
214
|
+
const removed = before.filter((x) => !afterSet.has(x));
|
|
215
|
+
return {
|
|
216
|
+
before,
|
|
217
|
+
after,
|
|
218
|
+
changed: added.length > 0 || removed.length > 0,
|
|
219
|
+
added,
|
|
220
|
+
removed
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (typeof before === "object" && typeof after === "object") {
|
|
224
|
+
const b = before;
|
|
225
|
+
const a = after;
|
|
226
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(a)]);
|
|
227
|
+
const added = {};
|
|
228
|
+
const removed = [];
|
|
229
|
+
const modified = {};
|
|
230
|
+
for (const key of allKeys) {
|
|
231
|
+
if (!(key in b)) added[key] = a[key];
|
|
232
|
+
else if (!(key in a)) removed.push(key);
|
|
233
|
+
else if (String(b[key]) !== String(a[key])) modified[key] = a[key];
|
|
234
|
+
}
|
|
235
|
+
const changed = Object.keys(added).length > 0 || removed.length > 0 || Object.keys(modified).length > 0;
|
|
236
|
+
return { before, after, changed, added, removed, modified };
|
|
237
|
+
}
|
|
238
|
+
return { before, after, changed: before !== after };
|
|
239
|
+
}
|
|
240
|
+
|
|
143
241
|
// src/core/logger.ts
|
|
144
242
|
import chalk from "chalk";
|
|
145
243
|
function logAction(context, message) {
|
|
@@ -152,68 +250,326 @@ function logSkip() {
|
|
|
152
250
|
console.log(`... ${chalk.yellow("already configured")}`);
|
|
153
251
|
}
|
|
154
252
|
|
|
155
|
-
// src/
|
|
156
|
-
async function
|
|
157
|
-
|
|
158
|
-
logAction(
|
|
159
|
-
if (
|
|
160
|
-
|
|
253
|
+
// src/core/reconcile.ts
|
|
254
|
+
async function reconcile(resource, ctx, target, desired) {
|
|
255
|
+
if (desired === void 0) return;
|
|
256
|
+
logAction(target, `${resource.key}`);
|
|
257
|
+
if (resource.forceApply) {
|
|
258
|
+
await resource.onChange(ctx, target, { before: void 0, after: desired, changed: true });
|
|
259
|
+
logDone();
|
|
161
260
|
return;
|
|
162
261
|
}
|
|
163
|
-
await
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
async function destroyApp(runner, app) {
|
|
167
|
-
const exists = await runner.check("apps:exists", app);
|
|
168
|
-
logAction(app, "Destroying app");
|
|
169
|
-
if (!exists) {
|
|
262
|
+
const before = await resource.read(ctx, target);
|
|
263
|
+
const change = computeChange(before, desired);
|
|
264
|
+
if (!change.changed) {
|
|
170
265
|
logSkip();
|
|
171
266
|
return;
|
|
172
267
|
}
|
|
173
|
-
await
|
|
268
|
+
await resource.onChange(ctx, target, change);
|
|
174
269
|
logDone();
|
|
175
270
|
}
|
|
176
|
-
async function exportApps(runner) {
|
|
177
|
-
const output = await runner.query("apps:list");
|
|
178
|
-
return output.split("\n").map((s) => s.trim()).filter(
|
|
179
|
-
(s) => s && !s.startsWith("=====>")
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
271
|
|
|
183
|
-
// src/
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
272
|
+
// src/resources/lifecycle.ts
|
|
273
|
+
var Apps = {
|
|
274
|
+
key: "_app",
|
|
275
|
+
read: async (ctx, target) => {
|
|
276
|
+
return ctx.check("apps:exists", target);
|
|
277
|
+
},
|
|
278
|
+
onChange: async (ctx, target, { after }) => {
|
|
279
|
+
if (after) {
|
|
280
|
+
await ctx.run("apps:create", target);
|
|
281
|
+
} else {
|
|
282
|
+
await ctx.run("apps:destroy", target, "--force");
|
|
283
|
+
}
|
|
193
284
|
}
|
|
194
|
-
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// src/resources/lists.ts
|
|
288
|
+
function splitWords(raw) {
|
|
289
|
+
return raw.split(/\s+/).map((s) => s.trim()).filter(Boolean);
|
|
195
290
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
291
|
+
function splitLines(raw) {
|
|
292
|
+
return raw.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
293
|
+
}
|
|
294
|
+
var Ports = {
|
|
295
|
+
key: "ports",
|
|
296
|
+
read: async (ctx, target) => {
|
|
297
|
+
const raw = await ctx.query("ports:report", target, "--ports-map");
|
|
298
|
+
return splitWords(raw);
|
|
299
|
+
},
|
|
300
|
+
onChange: async (ctx, target, change) => {
|
|
301
|
+
await ctx.run("ports:set", target, ...change.after);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
var Domains = {
|
|
305
|
+
key: "domains",
|
|
306
|
+
read: async (ctx, target) => {
|
|
307
|
+
const raw = await ctx.query("domains:report", target, "--domains-app-vhosts");
|
|
308
|
+
return splitLines(raw);
|
|
309
|
+
},
|
|
310
|
+
onChange: async (ctx, target, { added, removed }) => {
|
|
311
|
+
for (const d of removed) await ctx.run("domains:remove", target, d);
|
|
312
|
+
for (const d of added) await ctx.run("domains:add", target, d);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
var Storage = {
|
|
316
|
+
key: "storage",
|
|
317
|
+
read: async (ctx, target) => {
|
|
318
|
+
const raw = await ctx.query("storage:report", target, "--storage-mounts");
|
|
319
|
+
return splitLines(raw);
|
|
320
|
+
},
|
|
321
|
+
onChange: async (ctx, target, { added, removed }) => {
|
|
322
|
+
for (const m of removed) await ctx.run("storage:unmount", target, m);
|
|
323
|
+
for (const m of added) await ctx.run("storage:mount", target, m);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// src/resources/parsers.ts
|
|
328
|
+
function parseReport(raw, namespace) {
|
|
329
|
+
const result = {};
|
|
330
|
+
const prefix = new RegExp(`^${namespace}\\s+`, "i");
|
|
331
|
+
for (const line of raw.split("\n")) {
|
|
332
|
+
if (line.trimStart().startsWith("=====>")) continue;
|
|
333
|
+
const colonIdx = line.indexOf(":");
|
|
334
|
+
if (colonIdx === -1) continue;
|
|
335
|
+
const rawKey = line.slice(0, colonIdx).trim();
|
|
336
|
+
if (!rawKey) continue;
|
|
337
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
338
|
+
const stripped = rawKey.replace(prefix, "");
|
|
339
|
+
const key = stripped.toLowerCase().replace(/\s+/g, "-");
|
|
340
|
+
if (key.startsWith("computed-") || key.startsWith("global-") || key === "last-visited-at") continue;
|
|
341
|
+
if (!value) continue;
|
|
342
|
+
result[key] = value;
|
|
202
343
|
}
|
|
203
|
-
|
|
344
|
+
return result;
|
|
204
345
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
346
|
+
|
|
347
|
+
// src/resources/properties.ts
|
|
348
|
+
function propertyResource(opts) {
|
|
349
|
+
return {
|
|
350
|
+
key: opts.key,
|
|
351
|
+
async read(ctx, target) {
|
|
352
|
+
const raw = await ctx.query(`${opts.namespace}:report`, target);
|
|
353
|
+
return parseReport(raw, opts.namespace);
|
|
354
|
+
},
|
|
355
|
+
async onChange(ctx, target, change) {
|
|
356
|
+
for (const [key, value] of Object.entries({ ...change.added, ...change.modified })) {
|
|
357
|
+
await ctx.run(opts.setCmd, target, key, String(value));
|
|
358
|
+
}
|
|
359
|
+
if (opts.afterChange) {
|
|
360
|
+
for (const cmd of opts.afterChange) {
|
|
361
|
+
await ctx.run(cmd, target);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
};
|
|
212
366
|
}
|
|
367
|
+
var Nginx = propertyResource({
|
|
368
|
+
key: "nginx",
|
|
369
|
+
namespace: "nginx",
|
|
370
|
+
setCmd: "nginx:set",
|
|
371
|
+
afterChange: ["proxy:build-config"]
|
|
372
|
+
});
|
|
373
|
+
var Logs = propertyResource({
|
|
374
|
+
key: "logs",
|
|
375
|
+
namespace: "logs",
|
|
376
|
+
setCmd: "logs:set"
|
|
377
|
+
});
|
|
378
|
+
var Registry = propertyResource({
|
|
379
|
+
key: "registry",
|
|
380
|
+
namespace: "registry",
|
|
381
|
+
setCmd: "registry:set"
|
|
382
|
+
});
|
|
383
|
+
var Scheduler = {
|
|
384
|
+
key: "scheduler",
|
|
385
|
+
async read(ctx, target) {
|
|
386
|
+
const raw = await ctx.query("scheduler:report", target);
|
|
387
|
+
const report = parseReport(raw, "scheduler");
|
|
388
|
+
return report["selected"] ?? "";
|
|
389
|
+
},
|
|
390
|
+
async onChange(ctx, target, change) {
|
|
391
|
+
await ctx.run("scheduler:set", target, "selected", change.after);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// src/resources/toggle.ts
|
|
396
|
+
var Proxy = {
|
|
397
|
+
key: "proxy",
|
|
398
|
+
read: async (ctx, target) => {
|
|
399
|
+
const raw = await ctx.query("proxy:report", target, "--proxy-enabled");
|
|
400
|
+
return raw.trim() === "true";
|
|
401
|
+
},
|
|
402
|
+
onChange: async (ctx, target, { after }) => {
|
|
403
|
+
await ctx.run(after ? "proxy:enable" : "proxy:disable", target);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/resources/config.ts
|
|
408
|
+
var MANAGED_KEYS_VAR = "DOKKU_COMPOSE_MANAGED_KEYS";
|
|
409
|
+
var Config = {
|
|
410
|
+
key: "env",
|
|
411
|
+
read: async (ctx, target) => {
|
|
412
|
+
const managedRaw = await ctx.query("config:get", target, MANAGED_KEYS_VAR);
|
|
413
|
+
const managedKeys = managedRaw.trim() ? managedRaw.trim().split(",").filter(Boolean) : [];
|
|
414
|
+
const result = {};
|
|
415
|
+
if (managedKeys.length > 0) {
|
|
416
|
+
const raw = await ctx.query("config:export", target, "--format", "shell");
|
|
417
|
+
for (const line of raw.split("\n")) {
|
|
418
|
+
const match = line.match(/^export\s+(\w+)=['"]?(.*?)['"]?$/);
|
|
419
|
+
if (match && managedKeys.includes(match[1])) {
|
|
420
|
+
result[match[1]] = match[2];
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
},
|
|
426
|
+
onChange: async (ctx, target, change) => {
|
|
427
|
+
const { added, removed, modified } = change;
|
|
428
|
+
if (removed.length > 0) {
|
|
429
|
+
await ctx.run("config:unset", "--no-restart", target, ...removed);
|
|
430
|
+
}
|
|
431
|
+
const toSet = { ...added, ...modified };
|
|
432
|
+
const allDesiredKeys = Object.keys(change.after);
|
|
433
|
+
const managedValue = allDesiredKeys.join(",");
|
|
434
|
+
if (Object.keys(toSet).length > 0 || removed.length > 0) {
|
|
435
|
+
const pairs = Object.entries(change.after).map(([k, v]) => `${k}=${v}`);
|
|
436
|
+
await ctx.run(
|
|
437
|
+
"config:set",
|
|
438
|
+
"--no-restart",
|
|
439
|
+
target,
|
|
440
|
+
...pairs,
|
|
441
|
+
`${MANAGED_KEYS_VAR}=${managedValue}`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
// src/resources/certs.ts
|
|
448
|
+
var Certs = {
|
|
449
|
+
key: "ssl",
|
|
450
|
+
read: async (ctx, target) => {
|
|
451
|
+
const raw = await ctx.query("certs:report", target, "--ssl-enabled");
|
|
452
|
+
return raw.trim() === "true";
|
|
453
|
+
},
|
|
454
|
+
onChange: async (ctx, target, { before, after }) => {
|
|
455
|
+
if (after === false && before) {
|
|
456
|
+
await ctx.run("certs:remove", target);
|
|
457
|
+
}
|
|
458
|
+
if (after && typeof after === "object") {
|
|
459
|
+
await ctx.run("certs:add", target, after.certfile, after.keyfile);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
// src/resources/builder.ts
|
|
465
|
+
var Builder = {
|
|
466
|
+
key: "build",
|
|
467
|
+
forceApply: true,
|
|
468
|
+
read: async () => ({}),
|
|
469
|
+
onChange: async (ctx, target, { after }) => {
|
|
470
|
+
if (after.dockerfile)
|
|
471
|
+
await ctx.run("builder-dockerfile:set", target, "dockerfile-path", after.dockerfile);
|
|
472
|
+
if (after.app_json)
|
|
473
|
+
await ctx.run("app-json:set", target, "appjson-path", after.app_json);
|
|
474
|
+
if (after.context)
|
|
475
|
+
await ctx.run("builder:set", target, "build-dir", after.context);
|
|
476
|
+
if (after.args) {
|
|
477
|
+
for (const [key, value] of Object.entries(after.args)) {
|
|
478
|
+
await ctx.run("docker-options:add", target, "build", `--build-arg ${key}=${value}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// src/resources/docker-options.ts
|
|
485
|
+
var DockerOptions = {
|
|
486
|
+
key: "docker_options",
|
|
487
|
+
forceApply: true,
|
|
488
|
+
read: async () => ({}),
|
|
489
|
+
onChange: async (ctx, target, { after }) => {
|
|
490
|
+
for (const phase of ["build", "deploy", "run"]) {
|
|
491
|
+
const opts = after[phase];
|
|
492
|
+
if (!opts || opts.length === 0) continue;
|
|
493
|
+
await ctx.run("docker-options:clear", target, phase);
|
|
494
|
+
for (const opt of opts) {
|
|
495
|
+
await ctx.run("docker-options:add", target, phase, opt);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
// src/resources/git.ts
|
|
502
|
+
var Git = {
|
|
503
|
+
key: "git",
|
|
504
|
+
read: async (ctx, target) => {
|
|
505
|
+
const report = await ctx.query("git:report", target, "--git-deploy-branch");
|
|
506
|
+
return { deploy_branch: report.trim() || void 0 };
|
|
507
|
+
},
|
|
508
|
+
onChange: async (ctx, target, { after }) => {
|
|
509
|
+
if (after.deploy_branch) {
|
|
510
|
+
await ctx.run("git:set", target, "deploy-branch", after.deploy_branch);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// src/resources/checks.ts
|
|
516
|
+
var Checks = {
|
|
517
|
+
key: "checks",
|
|
518
|
+
forceApply: true,
|
|
519
|
+
read: async () => ({}),
|
|
520
|
+
onChange: async (ctx, target, { after }) => {
|
|
521
|
+
if (after === false) {
|
|
522
|
+
await ctx.run("checks:disable", target);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (after.disabled && after.disabled.length > 0) {
|
|
526
|
+
await ctx.run("checks:disable", target, ...after.disabled);
|
|
527
|
+
}
|
|
528
|
+
if (after.skipped && after.skipped.length > 0) {
|
|
529
|
+
await ctx.run("checks:skip", target, ...after.skipped);
|
|
530
|
+
}
|
|
531
|
+
for (const [key, value] of Object.entries(after)) {
|
|
532
|
+
if (key === "disabled" || key === "skipped") continue;
|
|
533
|
+
await ctx.run("checks:set", target, key, String(value));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
// src/resources/network.ts
|
|
539
|
+
var Networks = {
|
|
540
|
+
key: "networks",
|
|
541
|
+
read: async (ctx, target) => {
|
|
542
|
+
const raw = await ctx.query("network:report", target, "--network-attach-post-deploy");
|
|
543
|
+
return raw.trim() ? raw.trim().split(/\s+/) : [];
|
|
544
|
+
},
|
|
545
|
+
onChange: async (ctx, target, { after }) => {
|
|
546
|
+
await ctx.run("network:set", target, "attach-post-deploy", ...after);
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
var NetworkProps = {
|
|
550
|
+
key: "network",
|
|
551
|
+
forceApply: true,
|
|
552
|
+
read: async () => ({}),
|
|
553
|
+
onChange: async (ctx, target, { after }) => {
|
|
554
|
+
if (after.attach_post_create !== void 0 && after.attach_post_create !== false) {
|
|
555
|
+
const nets = Array.isArray(after.attach_post_create) ? after.attach_post_create : [after.attach_post_create];
|
|
556
|
+
await ctx.run("network:set", target, "attach-post-create", ...nets);
|
|
557
|
+
}
|
|
558
|
+
if (after.initial_network !== void 0 && after.initial_network !== false) {
|
|
559
|
+
await ctx.run("network:set", target, "initial-network", after.initial_network);
|
|
560
|
+
}
|
|
561
|
+
if (after.bind_all_interfaces !== void 0) {
|
|
562
|
+
await ctx.run("network:set", target, "bind-all-interfaces", String(after.bind_all_interfaces));
|
|
563
|
+
}
|
|
564
|
+
if (after.tld !== void 0 && after.tld !== false) {
|
|
565
|
+
await ctx.run("network:set", target, "tld", after.tld);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
};
|
|
213
569
|
|
|
214
570
|
// src/modules/plugins.ts
|
|
215
|
-
async function ensurePlugins(
|
|
216
|
-
const listOutput = await
|
|
571
|
+
async function ensurePlugins(ctx, plugins) {
|
|
572
|
+
const listOutput = await ctx.query("plugin:list");
|
|
217
573
|
const installedNames = new Set(
|
|
218
574
|
listOutput.split("\n").map((line) => line.trim().split(/\s+/)[0]).filter(Boolean)
|
|
219
575
|
);
|
|
@@ -223,511 +579,304 @@ async function ensurePlugins(runner, plugins) {
|
|
|
223
579
|
logSkip();
|
|
224
580
|
continue;
|
|
225
581
|
}
|
|
226
|
-
await
|
|
582
|
+
await ctx.run("plugin:install", config.url, "--name", name);
|
|
227
583
|
logDone();
|
|
228
584
|
}
|
|
229
585
|
}
|
|
230
586
|
|
|
231
587
|
// src/modules/network.ts
|
|
232
|
-
async function ensureNetworks(
|
|
588
|
+
async function ensureNetworks(ctx, networks) {
|
|
233
589
|
for (const net of networks) {
|
|
234
590
|
logAction("network", `Creating ${net}`);
|
|
235
|
-
const exists = await
|
|
591
|
+
const exists = await ctx.check("network:exists", net);
|
|
236
592
|
if (exists) {
|
|
237
593
|
logSkip();
|
|
238
594
|
continue;
|
|
239
595
|
}
|
|
240
|
-
await
|
|
596
|
+
await ctx.run("network:create", net);
|
|
241
597
|
logDone();
|
|
242
598
|
}
|
|
243
599
|
}
|
|
244
|
-
async function ensureAppNetworks(runner, app, networks) {
|
|
245
|
-
if (!networks || networks.length === 0) return;
|
|
246
|
-
logAction(app, "Setting networks");
|
|
247
|
-
await runner.run("network:set", app, "attach-post-deploy", ...networks);
|
|
248
|
-
logDone();
|
|
249
|
-
}
|
|
250
|
-
async function ensureAppNetwork(runner, app, network) {
|
|
251
|
-
if (!network) return;
|
|
252
|
-
if (network.attach_post_create !== void 0 && network.attach_post_create !== false) {
|
|
253
|
-
const nets = Array.isArray(network.attach_post_create) ? network.attach_post_create : [network.attach_post_create];
|
|
254
|
-
await runner.run("network:set", app, "attach-post-create", ...nets);
|
|
255
|
-
}
|
|
256
|
-
if (network.initial_network !== void 0 && network.initial_network !== false) {
|
|
257
|
-
await runner.run("network:set", app, "initial-network", network.initial_network);
|
|
258
|
-
}
|
|
259
|
-
if (network.bind_all_interfaces !== void 0) {
|
|
260
|
-
await runner.run("network:set", app, "bind-all-interfaces", String(network.bind_all_interfaces));
|
|
261
|
-
}
|
|
262
|
-
if (network.tld !== void 0 && network.tld !== false) {
|
|
263
|
-
await runner.run("network:set", app, "tld", network.tld);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
600
|
var DOCKER_BUILTIN_NETWORKS = /* @__PURE__ */ new Set(["bridge", "host", "none"]);
|
|
267
|
-
async function exportNetworks(
|
|
268
|
-
const output = await
|
|
601
|
+
async function exportNetworks(ctx) {
|
|
602
|
+
const output = await ctx.query("network:list");
|
|
269
603
|
return output.split("\n").map((s) => s.trim()).filter((s) => s && !s.startsWith("=====>") && !DOCKER_BUILTIN_NETWORKS.has(s));
|
|
270
604
|
}
|
|
271
|
-
async function exportAppNetwork(runner, app) {
|
|
272
|
-
const output = await runner.query("network:report", app);
|
|
273
|
-
if (!output) return void 0;
|
|
274
|
-
const result = {};
|
|
275
|
-
const lines = output.split("\n");
|
|
276
|
-
for (const line of lines) {
|
|
277
|
-
const [key, ...valueParts] = line.split(":").map((s) => s.trim());
|
|
278
|
-
const value = valueParts.join(":").trim();
|
|
279
|
-
if (!key || !value) continue;
|
|
280
|
-
if (key === "Network attach post deploy") {
|
|
281
|
-
result.networks = value.split(" ").filter(Boolean);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
285
|
-
}
|
|
286
605
|
|
|
287
606
|
// src/modules/services.ts
|
|
288
|
-
|
|
607
|
+
import { createHash as createHash2 } from "crypto";
|
|
608
|
+
function backupHashKey(serviceName) {
|
|
609
|
+
return "DOKKU_COMPOSE_BACKUP_HASH_" + serviceName.toUpperCase().replace(/-/g, "_");
|
|
610
|
+
}
|
|
611
|
+
function computeBackupHash(backup) {
|
|
612
|
+
return createHash2("sha256").update(JSON.stringify(backup)).digest("hex");
|
|
613
|
+
}
|
|
614
|
+
async function ensureServices(ctx, services) {
|
|
289
615
|
for (const [name, config] of Object.entries(services)) {
|
|
290
616
|
logAction("services", `Ensuring ${name}`);
|
|
291
|
-
const exists = await
|
|
617
|
+
const exists = await ctx.check(`${config.plugin}:exists`, name);
|
|
292
618
|
if (exists) {
|
|
293
619
|
logSkip();
|
|
294
620
|
continue;
|
|
295
621
|
}
|
|
296
|
-
|
|
622
|
+
const args = [`${config.plugin}:create`, name];
|
|
623
|
+
if (config.image) args.push("--image", config.image);
|
|
624
|
+
if (config.version) args.push("--image-version", config.version);
|
|
625
|
+
await ctx.run(...args);
|
|
626
|
+
logDone();
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
async function ensureServiceBackups(ctx, services) {
|
|
630
|
+
for (const [name, config] of Object.entries(services)) {
|
|
631
|
+
if (!config.backup) continue;
|
|
632
|
+
logAction("services", `Configuring backup for ${name}`);
|
|
633
|
+
const hashKey = backupHashKey(name);
|
|
634
|
+
const desiredHash = computeBackupHash(config.backup);
|
|
635
|
+
const storedHash = await ctx.query("config:get", "--global", hashKey);
|
|
636
|
+
if (storedHash === desiredHash) {
|
|
637
|
+
logSkip();
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
const { schedule, bucket, auth } = config.backup;
|
|
641
|
+
await ctx.run(`${config.plugin}:backup-deauth`, name);
|
|
642
|
+
await ctx.run(
|
|
643
|
+
`${config.plugin}:backup-auth`,
|
|
644
|
+
name,
|
|
645
|
+
auth.access_key_id,
|
|
646
|
+
auth.secret_access_key,
|
|
647
|
+
auth.region,
|
|
648
|
+
auth.signature_version,
|
|
649
|
+
auth.endpoint
|
|
650
|
+
);
|
|
651
|
+
await ctx.run(`${config.plugin}:backup-schedule`, name, schedule, bucket);
|
|
652
|
+
await ctx.run("config:set", "--global", `${hashKey}=${desiredHash}`);
|
|
297
653
|
logDone();
|
|
298
654
|
}
|
|
299
655
|
}
|
|
300
|
-
async function ensureAppLinks(
|
|
656
|
+
async function ensureAppLinks(ctx, app, desiredLinks, allServices) {
|
|
301
657
|
const desiredSet = new Set(desiredLinks);
|
|
302
658
|
for (const [serviceName, serviceConfig] of Object.entries(allServices)) {
|
|
303
|
-
const isLinked = await
|
|
659
|
+
const isLinked = await ctx.check(`${serviceConfig.plugin}:linked`, serviceName, app);
|
|
304
660
|
const isDesired = desiredSet.has(serviceName);
|
|
305
661
|
if (isDesired && !isLinked) {
|
|
306
662
|
logAction(app, `Linking ${serviceName}`);
|
|
307
|
-
await
|
|
663
|
+
await ctx.run(`${serviceConfig.plugin}:link`, serviceName, app, "--no-restart");
|
|
308
664
|
logDone();
|
|
309
665
|
} else if (!isDesired && isLinked) {
|
|
310
666
|
logAction(app, `Unlinking ${serviceName}`);
|
|
311
|
-
await
|
|
667
|
+
await ctx.run(`${serviceConfig.plugin}:unlink`, serviceName, app, "--no-restart");
|
|
312
668
|
logDone();
|
|
313
669
|
}
|
|
314
670
|
}
|
|
315
671
|
}
|
|
316
|
-
async function destroyAppLinks(
|
|
672
|
+
async function destroyAppLinks(ctx, app, links, allServices) {
|
|
317
673
|
for (const serviceName of links) {
|
|
318
674
|
const config = allServices[serviceName];
|
|
319
675
|
if (!config) continue;
|
|
320
|
-
const isLinked = await
|
|
676
|
+
const isLinked = await ctx.check(`${config.plugin}:linked`, serviceName, app);
|
|
321
677
|
if (isLinked) {
|
|
322
|
-
await
|
|
678
|
+
await ctx.run(`${config.plugin}:unlink`, serviceName, app, "--no-restart");
|
|
323
679
|
}
|
|
324
680
|
}
|
|
325
681
|
}
|
|
326
|
-
async function destroyServices(
|
|
682
|
+
async function destroyServices(ctx, services) {
|
|
327
683
|
for (const [name, config] of Object.entries(services)) {
|
|
328
684
|
logAction("services", `Destroying ${name}`);
|
|
329
|
-
const exists = await
|
|
685
|
+
const exists = await ctx.check(`${config.plugin}:exists`, name);
|
|
330
686
|
if (!exists) {
|
|
331
687
|
logSkip();
|
|
332
688
|
continue;
|
|
333
689
|
}
|
|
334
|
-
await
|
|
690
|
+
await ctx.run(`${config.plugin}:destroy`, name, "--force");
|
|
335
691
|
logDone();
|
|
336
692
|
}
|
|
337
693
|
}
|
|
338
|
-
async function exportServices(
|
|
694
|
+
async function exportServices(_ctx) {
|
|
339
695
|
return {};
|
|
340
696
|
}
|
|
341
|
-
async function exportAppLinks(
|
|
697
|
+
async function exportAppLinks(ctx, app, services) {
|
|
342
698
|
const linked = [];
|
|
343
699
|
for (const [serviceName, config] of Object.entries(services)) {
|
|
344
|
-
const isLinked = await
|
|
700
|
+
const isLinked = await ctx.check(`${config.plugin}:linked`, serviceName, app);
|
|
345
701
|
if (isLinked) linked.push(serviceName);
|
|
346
702
|
}
|
|
347
703
|
return linked;
|
|
348
704
|
}
|
|
349
705
|
|
|
350
|
-
// src/modules/
|
|
351
|
-
async function
|
|
352
|
-
logAction(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (current === enabled) {
|
|
356
|
-
logSkip();
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
if (enabled) {
|
|
360
|
-
await runner.run("proxy:enable", app);
|
|
706
|
+
// src/modules/domains.ts
|
|
707
|
+
async function ensureGlobalDomains(ctx, domains) {
|
|
708
|
+
logAction("global", "Configuring domains");
|
|
709
|
+
if (domains === false) {
|
|
710
|
+
await ctx.run("domains:clear-global");
|
|
361
711
|
} else {
|
|
362
|
-
await
|
|
712
|
+
await ctx.run("domains:set-global", ...domains);
|
|
363
713
|
}
|
|
364
714
|
logDone();
|
|
365
715
|
}
|
|
366
|
-
async function exportAppProxy(runner, app) {
|
|
367
|
-
const raw = await runner.query("proxy:report", app, "--proxy-enabled");
|
|
368
|
-
const enabled = raw.trim() === "true";
|
|
369
|
-
if (enabled) return void 0;
|
|
370
|
-
return { enabled };
|
|
371
|
-
}
|
|
372
716
|
|
|
373
|
-
// src/modules/
|
|
374
|
-
async function
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
const desired = [...ports].sort();
|
|
379
|
-
if (JSON.stringify(current) === JSON.stringify(desired)) {
|
|
380
|
-
logSkip();
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
await runner.run("ports:set", app, ...ports);
|
|
384
|
-
logDone();
|
|
385
|
-
}
|
|
386
|
-
async function exportAppPorts(runner, app) {
|
|
387
|
-
const raw = await runner.query("ports:report", app, "--ports-map");
|
|
388
|
-
const ports = raw.split(/\s+/).map((s) => s.trim()).filter(Boolean);
|
|
389
|
-
if (ports.length === 0) return void 0;
|
|
390
|
-
return ports;
|
|
717
|
+
// src/modules/config.ts
|
|
718
|
+
async function ensureGlobalConfig(ctx, env) {
|
|
719
|
+
if (env === false) return;
|
|
720
|
+
const pairs = Object.entries(env).map(([k, v]) => `${k}=${v}`);
|
|
721
|
+
await ctx.run("config:set", "--global", ...pairs);
|
|
391
722
|
}
|
|
392
723
|
|
|
393
|
-
// src/modules/
|
|
394
|
-
async function
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const enabled = enabledRaw.trim() === "true";
|
|
398
|
-
if (ssl === false) {
|
|
399
|
-
if (!enabled) {
|
|
400
|
-
logSkip();
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
await runner.run("certs:remove", app);
|
|
404
|
-
logDone();
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
if (ssl === true) {
|
|
408
|
-
if (enabled) {
|
|
409
|
-
logSkip();
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
logSkip();
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
if (enabled) {
|
|
416
|
-
logSkip();
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
await runner.run("certs:add", app, ssl.certfile, ssl.keyfile);
|
|
420
|
-
logDone();
|
|
421
|
-
}
|
|
422
|
-
async function exportAppCerts(runner, app) {
|
|
423
|
-
const raw = await runner.query("certs:report", app, "--ssl-enabled");
|
|
424
|
-
const enabled = raw.trim() === "true";
|
|
425
|
-
if (!enabled) return void 0;
|
|
426
|
-
return true;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// src/modules/storage.ts
|
|
430
|
-
async function ensureAppStorage(runner, app, storage) {
|
|
431
|
-
logAction(app, "Configuring storage");
|
|
432
|
-
const currentRaw = await runner.query("storage:report", app, "--storage-mounts");
|
|
433
|
-
const current = currentRaw.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
434
|
-
const desired = new Set(storage);
|
|
435
|
-
const currentSet = new Set(current);
|
|
436
|
-
const toUnmount = current.filter((m) => !desired.has(m));
|
|
437
|
-
const toMount = storage.filter((m) => !currentSet.has(m));
|
|
438
|
-
if (toUnmount.length === 0 && toMount.length === 0) {
|
|
439
|
-
logSkip();
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
for (const mount of toUnmount) {
|
|
443
|
-
await runner.run("storage:unmount", app, mount);
|
|
444
|
-
}
|
|
445
|
-
for (const mount of toMount) {
|
|
446
|
-
await runner.run("storage:mount", app, mount);
|
|
724
|
+
// src/modules/logs.ts
|
|
725
|
+
async function ensureGlobalLogs(ctx, logs) {
|
|
726
|
+
for (const [key, value] of Object.entries(logs)) {
|
|
727
|
+
await ctx.run("logs:set", "--global", key, String(value));
|
|
447
728
|
}
|
|
448
|
-
logDone();
|
|
449
|
-
}
|
|
450
|
-
async function exportAppStorage(runner, app) {
|
|
451
|
-
const raw = await runner.query("storage:report", app, "--storage-mounts");
|
|
452
|
-
const mounts = raw.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
453
|
-
if (mounts.length === 0) return void 0;
|
|
454
|
-
return mounts;
|
|
455
729
|
}
|
|
456
730
|
|
|
457
731
|
// src/modules/nginx.ts
|
|
458
|
-
async function
|
|
459
|
-
for (const [key, value] of Object.entries(nginx)) {
|
|
460
|
-
await runner.run("nginx:set", app, key, String(value));
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
async function ensureGlobalNginx(runner, nginx) {
|
|
732
|
+
async function ensureGlobalNginx(ctx, nginx) {
|
|
464
733
|
for (const [key, value] of Object.entries(nginx)) {
|
|
465
|
-
await
|
|
734
|
+
await ctx.run("nginx:set", "--global", key, String(value));
|
|
466
735
|
}
|
|
467
736
|
}
|
|
468
|
-
async function exportAppNginx(runner, app) {
|
|
469
|
-
const raw = await runner.query("nginx:report", app);
|
|
470
|
-
if (!raw) return void 0;
|
|
471
|
-
const result = {};
|
|
472
|
-
for (const line of raw.split("\n")) {
|
|
473
|
-
const match = line.match(/^\s*Nginx\s+(.+?):\s*(.+?)\s*$/);
|
|
474
|
-
if (match) {
|
|
475
|
-
const key = match[1].toLowerCase().replace(/\s+/g, "-");
|
|
476
|
-
if (key.startsWith("computed-") || key.startsWith("global-") || key === "last-visited-at") continue;
|
|
477
|
-
const value = match[2].trim();
|
|
478
|
-
if (!value) continue;
|
|
479
|
-
result[key] = value;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// src/modules/checks.ts
|
|
486
|
-
async function ensureAppChecks(runner, app, checks) {
|
|
487
|
-
logAction(app, "Configuring checks");
|
|
488
|
-
if (checks === false) {
|
|
489
|
-
await runner.run("checks:disable", app);
|
|
490
|
-
logDone();
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
if (checks.disabled && checks.disabled.length > 0) {
|
|
494
|
-
await runner.run("checks:disable", app, ...checks.disabled);
|
|
495
|
-
}
|
|
496
|
-
if (checks.skipped && checks.skipped.length > 0) {
|
|
497
|
-
await runner.run("checks:skip", app, ...checks.skipped);
|
|
498
|
-
}
|
|
499
|
-
for (const [key, value] of Object.entries(checks)) {
|
|
500
|
-
if (key === "disabled" || key === "skipped") continue;
|
|
501
|
-
await runner.run("checks:set", app, key, String(value));
|
|
502
|
-
}
|
|
503
|
-
logDone();
|
|
504
|
-
}
|
|
505
|
-
async function exportAppChecks(runner, app) {
|
|
506
|
-
const raw = await runner.query("checks:report", app);
|
|
507
|
-
if (!raw) return void 0;
|
|
508
|
-
return void 0;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// src/modules/logs.ts
|
|
512
|
-
async function ensureAppLogs(runner, app, logs) {
|
|
513
|
-
for (const [key, value] of Object.entries(logs)) {
|
|
514
|
-
await runner.run("logs:set", app, key, String(value));
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
async function ensureGlobalLogs(runner, logs) {
|
|
518
|
-
for (const [key, value] of Object.entries(logs)) {
|
|
519
|
-
await runner.run("logs:set", "--global", key, String(value));
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
async function exportAppLogs(runner, app) {
|
|
523
|
-
const raw = await runner.query("logs:report", app);
|
|
524
|
-
if (!raw) return void 0;
|
|
525
|
-
return void 0;
|
|
526
|
-
}
|
|
527
737
|
|
|
528
|
-
// src/
|
|
529
|
-
async function
|
|
530
|
-
|
|
531
|
-
|
|
738
|
+
// src/commands/up.ts
|
|
739
|
+
async function runUp(ctx, config, appFilter) {
|
|
740
|
+
const apps = appFilter.length > 0 ? appFilter : Object.keys(config.apps);
|
|
741
|
+
if (config.plugins) await ensurePlugins(ctx, config.plugins);
|
|
742
|
+
if (config.domains !== void 0) await ensureGlobalDomains(ctx, config.domains);
|
|
743
|
+
if (config.env !== void 0) await ensureGlobalConfig(ctx, config.env);
|
|
744
|
+
if (config.logs !== void 0) await ensureGlobalLogs(ctx, config.logs);
|
|
745
|
+
if (config.nginx !== void 0) await ensureGlobalNginx(ctx, config.nginx);
|
|
746
|
+
if (config.networks) await ensureNetworks(ctx, config.networks);
|
|
747
|
+
if (config.services) await ensureServices(ctx, config.services);
|
|
748
|
+
if (config.services) await ensureServiceBackups(ctx, config.services);
|
|
749
|
+
for (const app of apps) {
|
|
750
|
+
const appConfig = config.apps[app];
|
|
751
|
+
if (!appConfig) continue;
|
|
752
|
+
await reconcile(Apps, ctx, app, true);
|
|
753
|
+
await reconcile(Domains, ctx, app, appConfig.domains);
|
|
754
|
+
await reconcile(Networks, ctx, app, appConfig.networks);
|
|
755
|
+
await reconcile(NetworkProps, ctx, app, appConfig.network);
|
|
756
|
+
await reconcile(Proxy, ctx, app, appConfig.proxy?.enabled);
|
|
757
|
+
await reconcile(Ports, ctx, app, appConfig.ports);
|
|
758
|
+
if (config.services) {
|
|
759
|
+
await ensureAppLinks(ctx, app, appConfig.links ?? [], config.services);
|
|
760
|
+
}
|
|
761
|
+
await reconcile(Certs, ctx, app, appConfig.ssl);
|
|
762
|
+
await reconcile(Storage, ctx, app, appConfig.storage);
|
|
763
|
+
await reconcile(Nginx, ctx, app, appConfig.nginx);
|
|
764
|
+
await reconcile(Checks, ctx, app, appConfig.checks);
|
|
765
|
+
await reconcile(Logs, ctx, app, appConfig.logs);
|
|
766
|
+
await reconcile(Registry, ctx, app, appConfig.registry);
|
|
767
|
+
await reconcile(Scheduler, ctx, app, appConfig.scheduler);
|
|
768
|
+
await reconcile(Config, ctx, app, appConfig.env);
|
|
769
|
+
await reconcile(Builder, ctx, app, appConfig.build);
|
|
770
|
+
await reconcile(Git, ctx, app, appConfig.git ?? config.git);
|
|
771
|
+
await reconcile(DockerOptions, ctx, app, appConfig.docker_options);
|
|
532
772
|
}
|
|
533
773
|
}
|
|
534
|
-
async function exportAppRegistry(runner, app) {
|
|
535
|
-
const raw = await runner.query("registry:report", app);
|
|
536
|
-
if (!raw) return void 0;
|
|
537
|
-
return void 0;
|
|
538
|
-
}
|
|
539
774
|
|
|
540
|
-
// src/modules/
|
|
541
|
-
async function
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (
|
|
775
|
+
// src/modules/apps.ts
|
|
776
|
+
async function destroyApp(ctx, app) {
|
|
777
|
+
const exists = await ctx.check("apps:exists", app);
|
|
778
|
+
logAction(app, "Destroying app");
|
|
779
|
+
if (!exists) {
|
|
545
780
|
logSkip();
|
|
546
781
|
return;
|
|
547
782
|
}
|
|
548
|
-
await
|
|
783
|
+
await ctx.run("apps:destroy", app, "--force");
|
|
549
784
|
logDone();
|
|
550
785
|
}
|
|
551
|
-
async function
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
return scheduler;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// src/modules/config.ts
|
|
559
|
-
var MANAGED_KEYS_VAR = "DOKKU_COMPOSE_MANAGED_KEYS";
|
|
560
|
-
async function ensureAppConfig(runner, app, env) {
|
|
561
|
-
logAction(app, "Configuring env vars");
|
|
562
|
-
if (env === false) {
|
|
563
|
-
logSkip();
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
const prevManagedRaw = await runner.query("config:get", app, MANAGED_KEYS_VAR);
|
|
567
|
-
const prevManaged = prevManagedRaw.trim() ? prevManagedRaw.trim().split(",").filter(Boolean) : [];
|
|
568
|
-
const desiredKeys = Object.keys(env);
|
|
569
|
-
const toUnset = prevManaged.filter((k) => !desiredKeys.includes(k));
|
|
570
|
-
if (toUnset.length > 0) {
|
|
571
|
-
await runner.run("config:unset", "--no-restart", app, ...toUnset);
|
|
572
|
-
}
|
|
573
|
-
const pairs = Object.entries(env).map(([k, v]) => `${k}=${v}`);
|
|
574
|
-
const newManagedKeys = desiredKeys.join(",");
|
|
575
|
-
await runner.run(
|
|
576
|
-
"config:set",
|
|
577
|
-
"--no-restart",
|
|
578
|
-
app,
|
|
579
|
-
...pairs,
|
|
580
|
-
`${MANAGED_KEYS_VAR}=${newManagedKeys}`
|
|
786
|
+
async function exportApps(ctx) {
|
|
787
|
+
const output = await ctx.query("apps:list");
|
|
788
|
+
return output.split("\n").map((s) => s.trim()).filter(
|
|
789
|
+
(s) => s && !s.startsWith("=====>")
|
|
581
790
|
);
|
|
582
|
-
logDone();
|
|
583
|
-
}
|
|
584
|
-
async function ensureGlobalConfig(runner, env) {
|
|
585
|
-
if (env === false) return;
|
|
586
|
-
const pairs = Object.entries(env).map(([k, v]) => `${k}=${v}`);
|
|
587
|
-
await runner.run("config:set", "--global", ...pairs);
|
|
588
|
-
}
|
|
589
|
-
async function exportAppConfig(runner, app) {
|
|
590
|
-
const raw = await runner.query("config:export", app, "--format", "shell");
|
|
591
|
-
if (!raw) return void 0;
|
|
592
|
-
const result = {};
|
|
593
|
-
for (const line of raw.split("\n")) {
|
|
594
|
-
const match = line.match(/^export\s+(\w+)=['"]?(.*?)['"]?$/);
|
|
595
|
-
if (match && match[1] !== MANAGED_KEYS_VAR) {
|
|
596
|
-
result[match[1]] = match[2];
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// src/modules/builder.ts
|
|
603
|
-
async function ensureAppBuilder(runner, app, build) {
|
|
604
|
-
if (build.dockerfile) {
|
|
605
|
-
await runner.run("builder-dockerfile:set", app, "dockerfile-path", build.dockerfile);
|
|
606
|
-
}
|
|
607
|
-
if (build.app_json) {
|
|
608
|
-
await runner.run("app-json:set", app, "appjson-path", build.app_json);
|
|
609
|
-
}
|
|
610
|
-
if (build.context) {
|
|
611
|
-
await runner.run("builder:set", app, "build-dir", build.context);
|
|
612
|
-
}
|
|
613
|
-
if (build.args && Object.keys(build.args).length > 0) {
|
|
614
|
-
for (const [key, value] of Object.entries(build.args)) {
|
|
615
|
-
await runner.run("docker-options:add", app, "build", `--build-arg ${key}=${value}`);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// src/modules/docker-options.ts
|
|
621
|
-
async function ensureAppDockerOptions(runner, app, options) {
|
|
622
|
-
const phases = ["build", "deploy", "run"];
|
|
623
|
-
for (const phase of phases) {
|
|
624
|
-
const opts = options[phase];
|
|
625
|
-
if (!opts || opts.length === 0) continue;
|
|
626
|
-
await runner.run("docker-options:clear", app, phase);
|
|
627
|
-
for (const opt of opts) {
|
|
628
|
-
await runner.run("docker-options:add", app, phase, opt);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// src/commands/up.ts
|
|
634
|
-
async function runUp(runner, config, appFilter) {
|
|
635
|
-
const apps = appFilter.length > 0 ? appFilter : Object.keys(config.apps);
|
|
636
|
-
if (config.plugins) await ensurePlugins(runner, config.plugins);
|
|
637
|
-
if (config.domains !== void 0) await ensureGlobalDomains(runner, config.domains);
|
|
638
|
-
if (config.env !== void 0) await ensureGlobalConfig(runner, config.env);
|
|
639
|
-
if (config.logs !== void 0) await ensureGlobalLogs(runner, config.logs);
|
|
640
|
-
if (config.nginx !== void 0) await ensureGlobalNginx(runner, config.nginx);
|
|
641
|
-
if (config.networks) await ensureNetworks(runner, config.networks);
|
|
642
|
-
if (config.services) await ensureServices(runner, config.services);
|
|
643
|
-
for (const app of apps) {
|
|
644
|
-
const appConfig = config.apps[app];
|
|
645
|
-
if (!appConfig) continue;
|
|
646
|
-
await ensureApp(runner, app);
|
|
647
|
-
await ensureAppDomains(runner, app, appConfig.domains);
|
|
648
|
-
if (config.services) await ensureAppLinks(runner, app, appConfig.links ?? [], config.services);
|
|
649
|
-
await ensureAppNetworks(runner, app, appConfig.networks);
|
|
650
|
-
await ensureAppNetwork(runner, app, appConfig.network);
|
|
651
|
-
if (appConfig.proxy) await ensureAppProxy(runner, app, appConfig.proxy.enabled);
|
|
652
|
-
if (appConfig.ports) await ensureAppPorts(runner, app, appConfig.ports);
|
|
653
|
-
if (appConfig.ssl !== void 0) await ensureAppCerts(runner, app, appConfig.ssl);
|
|
654
|
-
if (appConfig.storage) await ensureAppStorage(runner, app, appConfig.storage);
|
|
655
|
-
if (appConfig.nginx) await ensureAppNginx(runner, app, appConfig.nginx);
|
|
656
|
-
if (appConfig.checks !== void 0) await ensureAppChecks(runner, app, appConfig.checks);
|
|
657
|
-
if (appConfig.logs) await ensureAppLogs(runner, app, appConfig.logs);
|
|
658
|
-
if (appConfig.registry) await ensureAppRegistry(runner, app, appConfig.registry);
|
|
659
|
-
if (appConfig.scheduler) await ensureAppScheduler(runner, app, appConfig.scheduler);
|
|
660
|
-
if (appConfig.env !== void 0) await ensureAppConfig(runner, app, appConfig.env);
|
|
661
|
-
if (appConfig.build) await ensureAppBuilder(runner, app, appConfig.build);
|
|
662
|
-
if (appConfig.docker_options) await ensureAppDockerOptions(runner, app, appConfig.docker_options);
|
|
663
|
-
}
|
|
664
791
|
}
|
|
665
792
|
|
|
666
793
|
// src/commands/down.ts
|
|
667
|
-
async function runDown(
|
|
794
|
+
async function runDown(ctx, config, appFilter, opts) {
|
|
668
795
|
const apps = appFilter.length > 0 ? appFilter : Object.keys(config.apps);
|
|
669
796
|
for (const app of apps) {
|
|
670
797
|
const appConfig = config.apps[app];
|
|
671
798
|
if (!appConfig) continue;
|
|
672
799
|
if (config.services && appConfig.links) {
|
|
673
|
-
await destroyAppLinks(
|
|
800
|
+
await destroyAppLinks(ctx, app, appConfig.links, config.services);
|
|
674
801
|
}
|
|
675
|
-
await destroyApp(
|
|
802
|
+
await destroyApp(ctx, app);
|
|
676
803
|
}
|
|
677
804
|
if (config.services) {
|
|
678
|
-
await destroyServices(
|
|
805
|
+
await destroyServices(ctx, config.services);
|
|
679
806
|
}
|
|
680
807
|
if (config.networks) {
|
|
681
808
|
for (const net of config.networks) {
|
|
682
809
|
logAction("network", `Destroying ${net}`);
|
|
683
|
-
const exists = await
|
|
810
|
+
const exists = await ctx.check("network:exists", net);
|
|
684
811
|
if (!exists) {
|
|
685
812
|
logSkip();
|
|
686
813
|
continue;
|
|
687
814
|
}
|
|
688
|
-
await
|
|
815
|
+
await ctx.run("network:destroy", net, "--force");
|
|
689
816
|
logDone();
|
|
690
817
|
}
|
|
691
818
|
}
|
|
692
819
|
}
|
|
693
820
|
|
|
821
|
+
// src/resources/index.ts
|
|
822
|
+
var NETWORKING_RESOURCES = [
|
|
823
|
+
Domains,
|
|
824
|
+
Networks,
|
|
825
|
+
NetworkProps,
|
|
826
|
+
Proxy,
|
|
827
|
+
Ports
|
|
828
|
+
];
|
|
829
|
+
var CONFIG_RESOURCES = [
|
|
830
|
+
Certs,
|
|
831
|
+
Storage,
|
|
832
|
+
Nginx,
|
|
833
|
+
Checks,
|
|
834
|
+
Logs,
|
|
835
|
+
Registry,
|
|
836
|
+
Scheduler,
|
|
837
|
+
Config
|
|
838
|
+
];
|
|
839
|
+
var BUILD_RESOURCES = [
|
|
840
|
+
Builder,
|
|
841
|
+
Git,
|
|
842
|
+
DockerOptions
|
|
843
|
+
];
|
|
844
|
+
var ALL_APP_RESOURCES = [
|
|
845
|
+
...NETWORKING_RESOURCES,
|
|
846
|
+
...CONFIG_RESOURCES,
|
|
847
|
+
...BUILD_RESOURCES
|
|
848
|
+
];
|
|
849
|
+
|
|
694
850
|
// src/commands/export.ts
|
|
695
|
-
async function runExport(
|
|
851
|
+
async function runExport(ctx, opts) {
|
|
696
852
|
const config = { apps: {} };
|
|
697
|
-
const
|
|
698
|
-
const
|
|
853
|
+
const versionOutput = await ctx.query("version");
|
|
854
|
+
const versionMatch = versionOutput.match(/(\d+\.\d+\.\d+)/);
|
|
855
|
+
if (versionMatch) config.dokku = { version: versionMatch[1] };
|
|
856
|
+
const apps = opts.appFilter?.length ? opts.appFilter : await exportApps(ctx);
|
|
857
|
+
const networks = await exportNetworks(ctx);
|
|
699
858
|
if (networks.length > 0) config.networks = networks;
|
|
700
|
-
const services = await exportServices(
|
|
859
|
+
const services = await exportServices(ctx);
|
|
701
860
|
if (Object.keys(services).length > 0) config.services = services;
|
|
702
861
|
for (const app of apps) {
|
|
703
862
|
const appConfig = {};
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
if (logs && Object.keys(logs).length) appConfig.logs = logs;
|
|
722
|
-
const registry = await exportAppRegistry(runner, app);
|
|
723
|
-
if (registry && Object.keys(registry).length) appConfig.registry = registry;
|
|
724
|
-
const scheduler = await exportAppScheduler(runner, app);
|
|
725
|
-
if (scheduler) appConfig.scheduler = scheduler;
|
|
726
|
-
const networkCfg = await exportAppNetwork(runner, app);
|
|
727
|
-
if (networkCfg?.networks?.length) appConfig.networks = networkCfg.networks;
|
|
728
|
-
if (networkCfg?.network) appConfig.network = networkCfg.network;
|
|
729
|
-
const env = await exportAppConfig(runner, app);
|
|
730
|
-
if (env && Object.keys(env).length) appConfig.env = env;
|
|
863
|
+
for (const resource of ALL_APP_RESOURCES) {
|
|
864
|
+
if (resource.key.startsWith("_")) continue;
|
|
865
|
+
if (resource.forceApply) continue;
|
|
866
|
+
const value = await resource.read(ctx, app);
|
|
867
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
868
|
+
if (Array.isArray(value) && value.length === 0) continue;
|
|
869
|
+
if (typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0) continue;
|
|
870
|
+
if (resource.key === "proxy") {
|
|
871
|
+
appConfig.proxy = { enabled: value };
|
|
872
|
+
} else {
|
|
873
|
+
appConfig[resource.key] = value;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
if (Object.keys(services).length > 0) {
|
|
877
|
+
const links = await exportAppLinks(ctx, app, services);
|
|
878
|
+
if (links.length > 0) appConfig.links = links;
|
|
879
|
+
}
|
|
731
880
|
config.apps[app] = appConfig;
|
|
732
881
|
}
|
|
733
882
|
return config;
|
|
@@ -735,52 +884,38 @@ async function runExport(runner, opts) {
|
|
|
735
884
|
|
|
736
885
|
// src/commands/diff.ts
|
|
737
886
|
import chalk2 from "chalk";
|
|
738
|
-
function computeDiff(
|
|
887
|
+
async function computeDiff(ctx, config) {
|
|
739
888
|
const result = { apps: {}, services: {}, inSync: true };
|
|
740
|
-
for (const [app,
|
|
741
|
-
const currentApp = current.apps[app] ?? {};
|
|
889
|
+
for (const [app, appConfig] of Object.entries(config.apps)) {
|
|
742
890
|
const appDiff = {};
|
|
743
|
-
const
|
|
744
|
-
"
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
"
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
const d = desiredApp[feature];
|
|
760
|
-
const c = currentApp[feature];
|
|
761
|
-
if (d === void 0) continue;
|
|
762
|
-
const dStr = JSON.stringify(d);
|
|
763
|
-
const cStr = JSON.stringify(c);
|
|
764
|
-
if (c === void 0) {
|
|
765
|
-
appDiff[feature] = { status: "missing", desired: d, current: void 0 };
|
|
766
|
-
result.inSync = false;
|
|
767
|
-
} else if (dStr !== cStr) {
|
|
768
|
-
appDiff[feature] = { status: "changed", desired: d, current: c };
|
|
891
|
+
for (const resource of ALL_APP_RESOURCES) {
|
|
892
|
+
if (resource.key.startsWith("_")) continue;
|
|
893
|
+
if (resource.forceApply) continue;
|
|
894
|
+
let desired;
|
|
895
|
+
if (resource.key === "proxy") {
|
|
896
|
+
desired = appConfig.proxy?.enabled;
|
|
897
|
+
} else {
|
|
898
|
+
desired = appConfig[resource.key];
|
|
899
|
+
}
|
|
900
|
+
if (desired === void 0) continue;
|
|
901
|
+
const current = await resource.read(ctx, app);
|
|
902
|
+
const change = computeChange(current, desired);
|
|
903
|
+
if (!change.changed) {
|
|
904
|
+
appDiff[resource.key] = { status: "in-sync", desired, current };
|
|
905
|
+
} else if (current === null || current === void 0 || Array.isArray(current) && current.length === 0 || typeof current === "object" && Object.keys(current).length === 0) {
|
|
906
|
+
appDiff[resource.key] = { status: "missing", desired, current };
|
|
769
907
|
result.inSync = false;
|
|
770
908
|
} else {
|
|
771
|
-
appDiff[
|
|
909
|
+
appDiff[resource.key] = { status: "changed", desired, current };
|
|
910
|
+
result.inSync = false;
|
|
772
911
|
}
|
|
773
912
|
}
|
|
774
913
|
result.apps[app] = appDiff;
|
|
775
914
|
}
|
|
776
|
-
for (const [svc] of Object.entries(
|
|
777
|
-
const exists =
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
result.inSync = false;
|
|
781
|
-
} else {
|
|
782
|
-
result.services[svc] = { status: "in-sync" };
|
|
783
|
-
}
|
|
915
|
+
for (const [svc, svcConfig] of Object.entries(config.services ?? {})) {
|
|
916
|
+
const exists = await ctx.check(`${svcConfig.plugin}:exists`, svc);
|
|
917
|
+
result.services[svc] = { status: exists ? "in-sync" : "missing" };
|
|
918
|
+
if (!exists) result.inSync = false;
|
|
784
919
|
}
|
|
785
920
|
return result;
|
|
786
921
|
}
|
|
@@ -893,7 +1028,9 @@ function validate(filePath) {
|
|
|
893
1028
|
}
|
|
894
1029
|
|
|
895
1030
|
// src/index.ts
|
|
896
|
-
var
|
|
1031
|
+
var require2 = createRequire(import.meta.url);
|
|
1032
|
+
var { version } = require2("../package.json");
|
|
1033
|
+
var program = new Command().name("dokku-compose").version(version);
|
|
897
1034
|
function makeRunner(opts) {
|
|
898
1035
|
return createRunner({
|
|
899
1036
|
host: process.env.DOKKU_HOST,
|
|
@@ -903,10 +1040,15 @@ function makeRunner(opts) {
|
|
|
903
1040
|
program.command("up [apps...]").description("Create/update apps and services to match config").option("-f, --file <path>", "Config file", "dokku-compose.yml").option("--dry-run", "Print commands without executing").option("--fail-fast", "Stop on first error").action(async (apps, opts) => {
|
|
904
1041
|
const config = loadConfig(opts.file);
|
|
905
1042
|
const runner = makeRunner(opts);
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1043
|
+
const ctx = createContext(runner);
|
|
1044
|
+
try {
|
|
1045
|
+
await runUp(ctx, config, apps);
|
|
1046
|
+
if (opts.dryRun) {
|
|
1047
|
+
console.log("\n# Commands that would run:");
|
|
1048
|
+
for (const cmd of runner.dryRunLog) console.log(`dokku ${cmd}`);
|
|
1049
|
+
}
|
|
1050
|
+
} finally {
|
|
1051
|
+
await ctx.close();
|
|
910
1052
|
}
|
|
911
1053
|
});
|
|
912
1054
|
program.command("down [apps...]").description("Destroy apps and services (requires --force)").option("-f, --file <path>", "Config file", "dokku-compose.yml").option("--force", "Required to destroy apps").action(async (apps, opts) => {
|
|
@@ -916,7 +1058,12 @@ program.command("down [apps...]").description("Destroy apps and services (requir
|
|
|
916
1058
|
}
|
|
917
1059
|
const config = loadConfig(opts.file);
|
|
918
1060
|
const runner = makeRunner({});
|
|
919
|
-
|
|
1061
|
+
const ctx = createContext(runner);
|
|
1062
|
+
try {
|
|
1063
|
+
await runDown(ctx, config, apps, { force: true });
|
|
1064
|
+
} finally {
|
|
1065
|
+
await ctx.close();
|
|
1066
|
+
}
|
|
920
1067
|
});
|
|
921
1068
|
program.command("validate [file]").description("Validate dokku-compose.yml without touching the server").action((file = "dokku-compose.yml") => {
|
|
922
1069
|
const result = validate(file);
|
|
@@ -936,33 +1083,44 @@ ${result.errors.length} error(s), ${result.warnings.length} warning(s)`);
|
|
|
936
1083
|
});
|
|
937
1084
|
program.command("export").description("Export server state to dokku-compose.yml format").option("--app <app>", "Export only a specific app").option("-o, --output <path>", "Write to file instead of stdout").action(async (opts) => {
|
|
938
1085
|
const runner = makeRunner({});
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1086
|
+
const ctx = createContext(runner);
|
|
1087
|
+
try {
|
|
1088
|
+
const result = await runExport(ctx, {
|
|
1089
|
+
appFilter: opts.app ? [opts.app] : void 0
|
|
1090
|
+
});
|
|
1091
|
+
const out = yaml3.dump(result, { lineWidth: 120 });
|
|
1092
|
+
if (opts.output) {
|
|
1093
|
+
fs3.writeFileSync(opts.output, out);
|
|
1094
|
+
console.error(`Written to ${opts.output}`);
|
|
1095
|
+
} else {
|
|
1096
|
+
process.stdout.write(out);
|
|
1097
|
+
}
|
|
1098
|
+
} finally {
|
|
1099
|
+
await ctx.close();
|
|
948
1100
|
}
|
|
949
1101
|
});
|
|
950
1102
|
program.command("diff").description("Show what is out of sync between config and server").option("-f, --file <path>", "Config file", "dokku-compose.yml").option("--verbose", "Show git-style +/- diff").action(async (opts) => {
|
|
951
1103
|
const desired = loadConfig(opts.file);
|
|
952
1104
|
const runner = makeRunner({});
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1105
|
+
const ctx = createContext(runner);
|
|
1106
|
+
try {
|
|
1107
|
+
const diff = await computeDiff(ctx, desired);
|
|
1108
|
+
const output = opts.verbose ? formatVerbose(diff) : formatSummary(diff);
|
|
1109
|
+
process.stdout.write(output);
|
|
1110
|
+
process.exit(diff.inSync ? 0 : 1);
|
|
1111
|
+
} finally {
|
|
1112
|
+
await ctx.close();
|
|
1113
|
+
}
|
|
960
1114
|
});
|
|
961
1115
|
program.command("ps [apps...]").description("Show status of configured apps").option("-f, --file <path>", "Config file", "dokku-compose.yml").action(async (apps, opts) => {
|
|
962
1116
|
const config = loadConfig(opts.file);
|
|
963
1117
|
const runner = makeRunner({});
|
|
964
|
-
|
|
965
|
-
|
|
1118
|
+
try {
|
|
1119
|
+
const { runPs } = await import("./ps-33II4UU3.js");
|
|
1120
|
+
await runPs(runner, config, apps);
|
|
1121
|
+
} finally {
|
|
1122
|
+
await runner.close();
|
|
1123
|
+
}
|
|
966
1124
|
});
|
|
967
1125
|
program.command("init [apps...]").description("Create a starter dokku-compose.yml").option("-f, --file <path>", "Config file", "dokku-compose.yml").action(async (apps, opts) => {
|
|
968
1126
|
const { runInit } = await import("./init-GIXEVLNW.js");
|