dockup-cli 1.1.0 → 1.3.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/commands/agent.js +76 -0
- package/dist/commands/remote.js +203 -0
- package/dist/index.js +13 -1
- package/dist/lib/api.js +42 -0
- package/dockupcli.md +55 -0
- package/package.json +1 -1
package/dist/commands/agent.js
CHANGED
|
@@ -38,6 +38,8 @@ __export(agent_exports, {
|
|
|
38
38
|
logsView: () => logsView,
|
|
39
39
|
registryClear: () => registryClear,
|
|
40
40
|
registrySet: () => registrySet,
|
|
41
|
+
resourcesSet: () => resourcesSet,
|
|
42
|
+
resourcesView: () => resourcesView,
|
|
41
43
|
servicesList: () => servicesList,
|
|
42
44
|
statusView: () => statusView
|
|
43
45
|
});
|
|
@@ -326,6 +328,78 @@ async function registryClear(arg, opts) {
|
|
|
326
328
|
(0, import_output.fail)(e.message);
|
|
327
329
|
}
|
|
328
330
|
}
|
|
331
|
+
const CPU_PER_MONTH = 463e-6 * 43200;
|
|
332
|
+
const MEM_PER_GB_PER_MONTH = 231e-6 * 43200;
|
|
333
|
+
const monthlyCost = (cpu, memMB) => Math.round((cpu * CPU_PER_MONTH + memMB / 1024 * MEM_PER_GB_PER_MONTH) * 100) / 100;
|
|
334
|
+
async function resourcesView(arg, opts) {
|
|
335
|
+
applyJson(opts);
|
|
336
|
+
requireAuth();
|
|
337
|
+
const t = target(arg);
|
|
338
|
+
try {
|
|
339
|
+
let memMB, cpu, name, kind;
|
|
340
|
+
if (opts?.db) {
|
|
341
|
+
const d = await import_api.api.getDatabaseDetail(t.projectSlug, t.serviceSlug);
|
|
342
|
+
const s = d.settings || d.database || d;
|
|
343
|
+
memMB = s.memoryLimitMB ?? 1024;
|
|
344
|
+
cpu = s.cpuLimit ?? 1;
|
|
345
|
+
name = d.database?.name || s.name || t.serviceSlug;
|
|
346
|
+
kind = "database";
|
|
347
|
+
} else {
|
|
348
|
+
const s = await import_api.api.getServiceStatus(t.projectSlug, t.serviceSlug);
|
|
349
|
+
memMB = s.memoryLimit ?? 2048;
|
|
350
|
+
cpu = s.cpuLimit ?? 2;
|
|
351
|
+
name = s.name || t.serviceSlug;
|
|
352
|
+
kind = "service";
|
|
353
|
+
}
|
|
354
|
+
const data = {
|
|
355
|
+
target: `${t.projectSlug}/${t.serviceSlug}`,
|
|
356
|
+
kind,
|
|
357
|
+
name,
|
|
358
|
+
cpu,
|
|
359
|
+
memoryMB: memMB,
|
|
360
|
+
estMonthlyUsd: monthlyCost(cpu, memMB),
|
|
361
|
+
limits: { maxCpu: 24, maxMemMB: 24576 }
|
|
362
|
+
};
|
|
363
|
+
(0, import_output.emit)(data, () => {
|
|
364
|
+
console.log(import_chalk.default.cyan(`
|
|
365
|
+
${import_chalk.default.bold(name)} ${import_chalk.default.dim(data.target)} ${import_chalk.default.dim("(" + kind + ")")}
|
|
366
|
+
`));
|
|
367
|
+
console.log(` ${import_chalk.default.dim("CPU:")} ${cpu} vCPU`);
|
|
368
|
+
console.log(` ${import_chalk.default.dim("RAM:")} ${memMB >= 1024 ? memMB / 1024 + " GB" : memMB + " MB"}`);
|
|
369
|
+
console.log(` ${import_chalk.default.dim("Est.:")} $${data.estMonthlyUsd.toFixed(2)}/mo ${import_chalk.default.dim("+ disk")}`);
|
|
370
|
+
console.log();
|
|
371
|
+
});
|
|
372
|
+
} catch (e) {
|
|
373
|
+
(0, import_output.fail)(e.message);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async function resourcesSet(arg, opts) {
|
|
377
|
+
applyJson(opts);
|
|
378
|
+
requireAuth();
|
|
379
|
+
const t = target(arg);
|
|
380
|
+
const cpu = opts?.cpu !== void 0 ? parseFloat(opts.cpu) : void 0;
|
|
381
|
+
const mem = opts?.memory !== void 0 ? parseInt(opts.memory, 10) : void 0;
|
|
382
|
+
if (cpu === void 0 && mem === void 0) {
|
|
383
|
+
return (0, import_output.fail)("Provide --cpu <vCPU> and/or --memory <MB>.", { code: "bad_args" });
|
|
384
|
+
}
|
|
385
|
+
if (cpu !== void 0 && (isNaN(cpu) || cpu <= 0)) return (0, import_output.fail)("--cpu must be a positive number (max 24).", { code: "bad_args" });
|
|
386
|
+
if (mem !== void 0 && (isNaN(mem) || mem <= 0)) return (0, import_output.fail)("--memory must be MB (e.g. 4096, max 24576).", { code: "bad_args" });
|
|
387
|
+
try {
|
|
388
|
+
if (opts?.db) {
|
|
389
|
+
const res = await import_api.api.setDatabaseResources(t.projectSlug, t.serviceSlug, mem ?? 1024, cpu ?? 1);
|
|
390
|
+
(0, import_output.ok)({ target: `${t.projectSlug}/${t.serviceSlug}`, kind: "database", memoryMB: res.memoryLimitMB, cpu: res.cpuLimit, applied: res.applied, estMonthlyUsd: monthlyCost(res.cpuLimit, res.memoryLimitMB) }, () => {
|
|
391
|
+
console.log(import_chalk.default.green(`\u2713 ${res.cpuLimit} vCPU / ${res.memoryLimitMB} MB`) + import_chalk.default.dim(` ${res.applied ? "(applied)" : "(start db to apply)"} ~$${monthlyCost(res.cpuLimit, res.memoryLimitMB).toFixed(2)}/mo`));
|
|
392
|
+
});
|
|
393
|
+
} else {
|
|
394
|
+
const res = await import_api.api.setServiceResources(t.projectSlug, t.serviceSlug, mem ?? 2048, cpu ?? 2);
|
|
395
|
+
(0, import_output.ok)({ target: `${t.projectSlug}/${t.serviceSlug}`, kind: "service", memoryMB: res.memoryLimit, cpu: res.cpuLimit, applied: res.applied, estMonthlyUsd: monthlyCost(res.cpuLimit, res.memoryLimit) }, () => {
|
|
396
|
+
console.log(import_chalk.default.green(`\u2713 ${res.cpuLimit} vCPU / ${res.memoryLimit} MB`) + import_chalk.default.dim(` ${res.applied ? "(applied)" : "(redeploy to apply)"} ~$${monthlyCost(res.cpuLimit, res.memoryLimit).toFixed(2)}/mo`));
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
} catch (e) {
|
|
400
|
+
(0, import_output.fail)(e.message);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
329
403
|
async function deployTrigger(arg, opts) {
|
|
330
404
|
applyJson(opts);
|
|
331
405
|
requireAuth();
|
|
@@ -351,6 +425,8 @@ async function deployTrigger(arg, opts) {
|
|
|
351
425
|
logsView,
|
|
352
426
|
registryClear,
|
|
353
427
|
registrySet,
|
|
428
|
+
resourcesSet,
|
|
429
|
+
resourcesView,
|
|
354
430
|
servicesList,
|
|
355
431
|
statusView
|
|
356
432
|
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var remote_exports = {};
|
|
30
|
+
__export(remote_exports, {
|
|
31
|
+
serverAdd: () => serverAdd,
|
|
32
|
+
serverList: () => serverList,
|
|
33
|
+
serverRemove: () => serverRemove,
|
|
34
|
+
serverTest: () => serverTest,
|
|
35
|
+
taskAdd: () => taskAdd,
|
|
36
|
+
taskList: () => taskList,
|
|
37
|
+
taskRemove: () => taskRemove,
|
|
38
|
+
taskRun: () => taskRun
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(remote_exports);
|
|
41
|
+
var import_chalk = __toESM(require("chalk"));
|
|
42
|
+
var fs = __toESM(require("fs"));
|
|
43
|
+
var import_config = require("../lib/config");
|
|
44
|
+
var import_flags = require("../lib/flags");
|
|
45
|
+
var import_output = require("../lib/output");
|
|
46
|
+
var import_api = require("../lib/api");
|
|
47
|
+
function applyJson(opts) {
|
|
48
|
+
(0, import_flags.setJson)(!!opts?.json);
|
|
49
|
+
}
|
|
50
|
+
function requireAuth() {
|
|
51
|
+
if (!(0, import_config.isLoggedIn)()) (0, import_output.fail)("Not logged in. Run `dockup login` first.", { code: "not_logged_in" });
|
|
52
|
+
}
|
|
53
|
+
async function serverList(opts) {
|
|
54
|
+
applyJson(opts);
|
|
55
|
+
requireAuth();
|
|
56
|
+
try {
|
|
57
|
+
const servers = await import_api.api.listServers();
|
|
58
|
+
(0, import_output.emit)({ count: servers.length, servers }, () => {
|
|
59
|
+
console.log(import_chalk.default.cyan(`
|
|
60
|
+
${servers.length} server(s)
|
|
61
|
+
`));
|
|
62
|
+
for (const s of servers) {
|
|
63
|
+
console.log(` ${import_chalk.default.bold(s.name.padEnd(20))} ${import_chalk.default.dim(s.username + "@" + s.host + ":" + s.port)} ${import_chalk.default.dim(s.authType)} ${import_chalk.default.dim(s.taskCount + " task(s)")}`);
|
|
64
|
+
console.log(` ${import_chalk.default.dim(" id: " + s.id)}`);
|
|
65
|
+
}
|
|
66
|
+
console.log();
|
|
67
|
+
});
|
|
68
|
+
} catch (e) {
|
|
69
|
+
(0, import_output.fail)(e.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function serverAdd(opts) {
|
|
73
|
+
applyJson(opts);
|
|
74
|
+
requireAuth();
|
|
75
|
+
if (!opts?.name || !opts?.host) return (0, import_output.fail)("--name and --host are required.", { code: "bad_args" });
|
|
76
|
+
let authType = "password";
|
|
77
|
+
let secret = opts?.password;
|
|
78
|
+
if (opts?.keyFile) {
|
|
79
|
+
authType = "key";
|
|
80
|
+
try {
|
|
81
|
+
secret = fs.readFileSync(opts.keyFile, "utf-8");
|
|
82
|
+
} catch {
|
|
83
|
+
return (0, import_output.fail)(`Cannot read key file: ${opts.keyFile}`, { code: "file_not_found" });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!secret) return (0, import_output.fail)("Provide --password or --key-file.", { code: "bad_args" });
|
|
87
|
+
try {
|
|
88
|
+
const server = await import_api.api.createServer({
|
|
89
|
+
name: opts.name,
|
|
90
|
+
host: opts.host,
|
|
91
|
+
port: opts.port ? parseInt(opts.port, 10) : 22,
|
|
92
|
+
username: opts.user || "root",
|
|
93
|
+
authType,
|
|
94
|
+
secret,
|
|
95
|
+
passphrase: opts.passphrase
|
|
96
|
+
});
|
|
97
|
+
(0, import_output.ok)({ id: server.id, name: server.name, host: server.host }, () => {
|
|
98
|
+
console.log(import_chalk.default.green(`\u2713 server added`) + import_chalk.default.dim(` ${server.id}`));
|
|
99
|
+
});
|
|
100
|
+
} catch (e) {
|
|
101
|
+
(0, import_output.fail)(e.message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function serverRemove(id, opts) {
|
|
105
|
+
applyJson(opts);
|
|
106
|
+
requireAuth();
|
|
107
|
+
try {
|
|
108
|
+
await import_api.api.deleteServer(id);
|
|
109
|
+
(0, import_output.ok)({ id, deleted: true }, () => console.log(import_chalk.default.green("\u2713 server removed")));
|
|
110
|
+
} catch (e) {
|
|
111
|
+
(0, import_output.fail)(e.message);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function serverTest(id, opts) {
|
|
115
|
+
applyJson(opts);
|
|
116
|
+
requireAuth();
|
|
117
|
+
try {
|
|
118
|
+
const r = await import_api.api.testServer(id);
|
|
119
|
+
(0, import_output.emit)(r, () => {
|
|
120
|
+
if (r.ok) console.log(import_chalk.default.green("\u2713 connection ok") + import_chalk.default.dim(`
|
|
121
|
+
${r.message}`));
|
|
122
|
+
else console.log(import_chalk.default.red("\u2717 connection failed") + import_chalk.default.dim(`
|
|
123
|
+
${r.message}`));
|
|
124
|
+
});
|
|
125
|
+
} catch (e) {
|
|
126
|
+
(0, import_output.fail)(e.message);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function taskList(serverId, opts) {
|
|
130
|
+
applyJson(opts);
|
|
131
|
+
requireAuth();
|
|
132
|
+
try {
|
|
133
|
+
const tasks = await import_api.api.listTasks(serverId);
|
|
134
|
+
(0, import_output.emit)({ serverId, count: tasks.length, tasks }, () => {
|
|
135
|
+
console.log(import_chalk.default.cyan(`
|
|
136
|
+
${tasks.length} task(s)
|
|
137
|
+
`));
|
|
138
|
+
for (const t of tasks) {
|
|
139
|
+
console.log(` ${import_chalk.default.bold(t.name)} ${import_chalk.default.dim("id: " + t.id)}`);
|
|
140
|
+
for (const s of t.steps || []) console.log(` ${import_chalk.default.dim("-")} ${s.cwd ? import_chalk.default.dim("(" + s.cwd + ") ") : ""}${s.command}`);
|
|
141
|
+
}
|
|
142
|
+
console.log();
|
|
143
|
+
});
|
|
144
|
+
} catch (e) {
|
|
145
|
+
(0, import_output.fail)(e.message);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function taskAdd(serverId, opts) {
|
|
149
|
+
applyJson(opts);
|
|
150
|
+
requireAuth();
|
|
151
|
+
if (!opts?.name) return (0, import_output.fail)("--name is required.", { code: "bad_args" });
|
|
152
|
+
const raw = Array.isArray(opts?.step) ? opts.step : opts?.step ? [opts.step] : [];
|
|
153
|
+
if (!raw.length) return (0, import_output.fail)('Provide at least one --step "command" (use "cwd::command" to set a directory).', { code: "bad_args" });
|
|
154
|
+
const steps = raw.map((r) => {
|
|
155
|
+
const idx = r.indexOf("::");
|
|
156
|
+
return idx === -1 ? { command: r } : { cwd: r.slice(0, idx), command: r.slice(idx + 2) };
|
|
157
|
+
});
|
|
158
|
+
try {
|
|
159
|
+
const task = await import_api.api.createTask(serverId, opts.name, steps);
|
|
160
|
+
(0, import_output.ok)({ id: task.id, name: task.name, steps: task.steps }, () => {
|
|
161
|
+
console.log(import_chalk.default.green(`\u2713 task created`) + import_chalk.default.dim(` ${task.id}`));
|
|
162
|
+
});
|
|
163
|
+
} catch (e) {
|
|
164
|
+
(0, import_output.fail)(e.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function taskRemove(taskId, opts) {
|
|
168
|
+
applyJson(opts);
|
|
169
|
+
requireAuth();
|
|
170
|
+
try {
|
|
171
|
+
await import_api.api.deleteTask(taskId);
|
|
172
|
+
(0, import_output.ok)({ taskId, deleted: true }, () => console.log(import_chalk.default.green("\u2713 task removed")));
|
|
173
|
+
} catch (e) {
|
|
174
|
+
(0, import_output.fail)(e.message);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function taskRun(taskId, opts) {
|
|
178
|
+
applyJson(opts);
|
|
179
|
+
requireAuth();
|
|
180
|
+
try {
|
|
181
|
+
const r = await import_api.api.runTask(taskId);
|
|
182
|
+
(0, import_output.emit)({ runId: r.runId, ok: r.ok, status: r.status, exitCode: r.exitCode, steps: r.steps, output: r.output }, () => {
|
|
183
|
+
console.log(r.output);
|
|
184
|
+
console.log(r.ok ? import_chalk.default.green(`
|
|
185
|
+
\u2713 ${r.status}`) : import_chalk.default.red(`
|
|
186
|
+
\u2717 ${r.status} (exit ${r.exitCode})`));
|
|
187
|
+
});
|
|
188
|
+
if (!r.ok) process.exitCode = 1;
|
|
189
|
+
} catch (e) {
|
|
190
|
+
(0, import_output.fail)(e.message);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
194
|
+
0 && (module.exports = {
|
|
195
|
+
serverAdd,
|
|
196
|
+
serverList,
|
|
197
|
+
serverRemove,
|
|
198
|
+
serverTest,
|
|
199
|
+
taskAdd,
|
|
200
|
+
taskList,
|
|
201
|
+
taskRemove,
|
|
202
|
+
taskRun
|
|
203
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -31,8 +31,9 @@ var import_open = require("./commands/open");
|
|
|
31
31
|
var import_agent = require("./commands/agent");
|
|
32
32
|
var import_workspace = require("./commands/workspace");
|
|
33
33
|
var import_database = require("./commands/database");
|
|
34
|
+
var import_remote = require("./commands/remote");
|
|
34
35
|
const program = new import_commander.Command();
|
|
35
|
-
program.name("dockup").description("Dockup CLI - Deploy from your terminal").version("1.
|
|
36
|
+
program.name("dockup").description("Dockup CLI - Deploy from your terminal").version("1.3.0");
|
|
36
37
|
program.command("login").description("Authenticate with Dockup").option("-t, --token <token>", "Use API token directly").action(import_auth.login);
|
|
37
38
|
program.command("logout").description("Log out from Dockup").action(import_auth.logout);
|
|
38
39
|
program.command("whoami").description("Show current logged in user").action(import_auth.whoami);
|
|
@@ -52,6 +53,8 @@ program.command("deployments [project/service]").alias("history").description("D
|
|
|
52
53
|
const registryCmd = program.command("registry").description("Private registry auth for a private FROM base image");
|
|
53
54
|
registryCmd.command("set [project/service]").description("Set registry credentials (used at build time to pull a private base image)").requiredOption("--url <registry>", "Registry host, e.g. ghcr.io").requiredOption("--user <username>", "Registry username").requiredOption("--token <token>", "PAT / password").option("-j, --json", "Machine-readable JSON output").action(import_agent.registrySet);
|
|
54
55
|
registryCmd.command("clear [project/service]").description("Remove registry credentials").option("-j, --json", "Machine-readable JSON output").action(import_agent.registryClear);
|
|
56
|
+
const resourcesCmd = program.command("resources [project/target]").alias("res").description("View reserved CPU/RAM (use `resources set` to change; --db for databases)").option("--db", "Target a database instead of a service").option("-j, --json", "Machine-readable JSON output").action(import_agent.resourcesView);
|
|
57
|
+
resourcesCmd.command("set [project/target]").description("Set reserved CPU/RAM \u2014 PRO (max 24 vCPU / 24576 MB)").option("--cpu <vcpu>", "vCPU to reserve, e.g. 2").option("--memory <mb>", "RAM in MB to reserve, e.g. 4096").option("--db", "Target a database instead of a service").option("-j, --json", "Machine-readable JSON output").action(import_agent.resourcesSet);
|
|
55
58
|
program.command("deploy [project/service]").description("Trigger a deployment (no git push; use `push` for git+deploy)").option("-b, --branch <branch>", "Branch to deploy").option("-j, --json", "Machine-readable JSON output").action(import_agent.deployTrigger);
|
|
56
59
|
program.command("open").description("Open service in browser").option("-d, --dashboard", "Open dashboard instead of live URL").action(import_open.open);
|
|
57
60
|
const workspaceCmd = program.command("workspace").alias("ws").description("Manage workspaces");
|
|
@@ -68,6 +71,15 @@ dbCmd.command("rename [id]").description("Rename a database").action(import_data
|
|
|
68
71
|
dbCmd.command("delete [id]").alias("rm").description("Delete a database").action(import_database.databaseDelete);
|
|
69
72
|
dbCmd.command("credentials [id]").alias("creds").description("Get database credentials").action(import_database.databaseCredentials);
|
|
70
73
|
dbCmd.command("info [id]").description("Get database details").action(import_database.databaseInfo);
|
|
74
|
+
const serverCmd = program.command("server").alias("srv").description("Remote servers & deploy tasks over SSH");
|
|
75
|
+
serverCmd.command("list").alias("ls").description("List remote servers").option("-j, --json", "Machine-readable JSON output").action(import_remote.serverList);
|
|
76
|
+
serverCmd.command("add").description("Add a remote server (use --password or --key-file)").requiredOption("--name <name>", "Friendly name").requiredOption("--host <host>", "IP or hostname").option("--port <port>", "SSH port", "22").option("--user <user>", "SSH username", "root").option("--password <password>", "SSH password").option("--key-file <path>", "Path to a private key file (PEM)").option("--passphrase <passphrase>", "Passphrase for the key, if any").option("-j, --json", "Machine-readable JSON output").action(import_remote.serverAdd);
|
|
77
|
+
serverCmd.command("remove <serverId>").alias("rm").description("Remove a remote server").option("-j, --json", "Machine-readable JSON output").action(import_remote.serverRemove);
|
|
78
|
+
serverCmd.command("test <serverId>").description("Test the SSH connection to a server").option("-j, --json", "Machine-readable JSON output").action(import_remote.serverTest);
|
|
79
|
+
serverCmd.command("tasks <serverId>").description("List deploy tasks on a server").option("-j, --json", "Machine-readable JSON output").action(import_remote.taskList);
|
|
80
|
+
serverCmd.command("task-add <serverId>").description('Create a deploy task. Repeat --step; use "cwd::command" to set a directory').requiredOption("--name <name>", "Task name").option("--step <command>", 'A step. Repeat for multiple. "cwd::command" sets the working dir', (v, acc) => acc.concat([v]), []).option("-j, --json", "Machine-readable JSON output").action(import_remote.taskAdd);
|
|
81
|
+
serverCmd.command("task-remove <taskId>").description("Delete a deploy task").option("-j, --json", "Machine-readable JSON output").action(import_remote.taskRemove);
|
|
82
|
+
serverCmd.command("run <taskId>").description("Run a deploy task over SSH (stops at the first failing step)").option("-j, --json", "Machine-readable JSON output").action(import_remote.taskRun);
|
|
71
83
|
program.parse();
|
|
72
84
|
if (!process.argv.slice(2).length) {
|
|
73
85
|
console.log(import_chalk.default.cyan(`
|
package/dist/lib/api.js
CHANGED
|
@@ -238,6 +238,48 @@ class ApiClient {
|
|
|
238
238
|
async clearRegistry(projectSlug, serviceSlug) {
|
|
239
239
|
return this.delete(`/projects/${projectSlug}/services/${serviceSlug}/registry`);
|
|
240
240
|
}
|
|
241
|
+
// ==================== RESOURCES (reserved CPU/RAM) ====================
|
|
242
|
+
// Database detail (project-scoped) — includes memoryLimitMB / cpuLimit.
|
|
243
|
+
async getDatabaseDetail(projectSlug, dbSlug) {
|
|
244
|
+
return this.get(`/projects/${projectSlug}/databases/${dbSlug}`);
|
|
245
|
+
}
|
|
246
|
+
// Set reserved CPU/RAM for a service (PRO). Applied by recreating the container.
|
|
247
|
+
async setServiceResources(projectSlug, serviceSlug, memoryLimit, cpuLimit) {
|
|
248
|
+
return this.post(`/projects/${projectSlug}/services/${serviceSlug}/resources`, { memoryLimit, cpuLimit });
|
|
249
|
+
}
|
|
250
|
+
// Set reserved CPU/RAM for a database (PRO, main-server only).
|
|
251
|
+
async setDatabaseResources(projectSlug, dbSlug, memoryLimitMB, cpuLimit) {
|
|
252
|
+
return this.post(`/projects/${projectSlug}/databases/${dbSlug}/resources`, { memoryLimitMB, cpuLimit });
|
|
253
|
+
}
|
|
254
|
+
// ==================== REMOTE SERVERS (SSH deploy hooks) ====================
|
|
255
|
+
async listServers() {
|
|
256
|
+
const res = await this.get("/remote/servers");
|
|
257
|
+
return res.servers || [];
|
|
258
|
+
}
|
|
259
|
+
async createServer(body) {
|
|
260
|
+
const res = await this.post("/remote/servers", body);
|
|
261
|
+
return res.server;
|
|
262
|
+
}
|
|
263
|
+
async deleteServer(id) {
|
|
264
|
+
return this.delete(`/remote/servers/${id}`);
|
|
265
|
+
}
|
|
266
|
+
async testServer(id) {
|
|
267
|
+
return this.post(`/remote/servers/${id}/test`);
|
|
268
|
+
}
|
|
269
|
+
async listTasks(serverId) {
|
|
270
|
+
const res = await this.get(`/remote/servers/${serverId}/tasks`);
|
|
271
|
+
return res.tasks || [];
|
|
272
|
+
}
|
|
273
|
+
async createTask(serverId, name, steps) {
|
|
274
|
+
const res = await this.post(`/remote/servers/${serverId}/tasks`, { name, steps });
|
|
275
|
+
return res.task;
|
|
276
|
+
}
|
|
277
|
+
async deleteTask(taskId) {
|
|
278
|
+
return this.delete(`/remote/tasks/${taskId}`);
|
|
279
|
+
}
|
|
280
|
+
async runTask(taskId) {
|
|
281
|
+
return this.post(`/remote/tasks/${taskId}/run`);
|
|
282
|
+
}
|
|
241
283
|
}
|
|
242
284
|
const api = new ApiClient();
|
|
243
285
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dockupcli.md
CHANGED
|
@@ -135,6 +135,61 @@ dockup registry clear my-project/my-api --json
|
|
|
135
135
|
- `dockup info --json` shows `registryUrl`, `registryUsername` and
|
|
136
136
|
`hasRegistryAuth` (the password is never exposed).
|
|
137
137
|
|
|
138
|
+
### Resources (reserved CPU & RAM — PRO)
|
|
139
|
+
|
|
140
|
+
Pick how much CPU and RAM a service or database reserves. Billing follows the
|
|
141
|
+
allocation (≈ $20/vCPU + $10/GB per month + disk). Max **24 vCPU / 24576 MB**.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# View current allocation + estimated monthly cost
|
|
145
|
+
dockup resources my-project/my-api --json
|
|
146
|
+
# → {"target","kind":"service","name","cpu","memoryMB","estMonthlyUsd","limits":{"maxCpu":24,"maxMemMB":24576}}
|
|
147
|
+
|
|
148
|
+
# Set (service). Applied live by recreating the container (no rebuild; data persists).
|
|
149
|
+
dockup resources set my-project/my-api --cpu 2 --memory 4096 --json
|
|
150
|
+
# → {"ok":true,"target","kind":"service","cpu","memoryMB","applied","estMonthlyUsd"}
|
|
151
|
+
|
|
152
|
+
# Databases: add --db (main-server databases only; recreates the container, volume kept)
|
|
153
|
+
dockup resources my-project/my-db --db --json
|
|
154
|
+
dockup resources set my-project/my-db --db --cpu 1 --memory 2048 --json
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
- `--cpu` is vCPU (e.g. `0.5`, `2`, `8`); `--memory` is MB (e.g. `512`, `4096`).
|
|
158
|
+
- Values out of range are clamped to `[0.5 vCPU, 512 MB] … [24 vCPU, 24576 MB]`.
|
|
159
|
+
- PRO only — non-PRO accounts get `403 Upgrade required`.
|
|
160
|
+
- `applied:true` means a running container was recreated immediately; otherwise
|
|
161
|
+
it applies on the next deploy (service) / start (database).
|
|
162
|
+
- `dockup info --json` also surfaces a service's current `memoryLimit`/`cpuLimit`.
|
|
163
|
+
|
|
164
|
+
### Remote servers (SSH deploy hooks)
|
|
165
|
+
|
|
166
|
+
Run deploy steps on **your own** servers over SSH — `git pull`, `npm run build`,
|
|
167
|
+
`pm2 restart`, `plesk ext git --deploy`. Credentials are stored encrypted.
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Register a server (password or --key-file)
|
|
171
|
+
dockup server add --name "plesk-prod" --host 94.130.67.75 --user root --password "$PW" --json
|
|
172
|
+
dockup server add --name "prod" --host 1.2.3.4 --key-file ~/.ssh/id_ed25519 --json
|
|
173
|
+
dockup server list --json
|
|
174
|
+
dockup server test <serverId> --json # opens an SSH connection and runs whoami
|
|
175
|
+
|
|
176
|
+
# Create a deploy task — each --step is a command; "cwd::command" sets the directory
|
|
177
|
+
dockup server task-add <serverId> --name "Deploy frontend" \
|
|
178
|
+
--step "plesk ext git --deploy -domain new.dockup.ai -name deploy-new-frontend.git" \
|
|
179
|
+
--step "/var/www/vhosts/dockup.ai/new.dockup.ai::npm install && npm run build && pm2 restart doknew" \
|
|
180
|
+
--json
|
|
181
|
+
dockup server tasks <serverId> --json
|
|
182
|
+
|
|
183
|
+
# Run it (executes steps in order, stops at the first failing step)
|
|
184
|
+
dockup server run <taskId> --json
|
|
185
|
+
# → {"runId","ok":true,"status":"success","exitCode":0,"steps":[...],"output":"...full log..."}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- Steps run via `bash -lc` so `npm`/`pm2`/`plesk` are on PATH; `cwd` fixes the
|
|
189
|
+
"ran in /root" problem — set it per step.
|
|
190
|
+
- `run` stops at the first non-zero exit and returns the combined output.
|
|
191
|
+
- Manage the same servers/tasks visually in the dashboard → **Servers**.
|
|
192
|
+
|
|
138
193
|
---
|
|
139
194
|
|
|
140
195
|
## Agent recipes
|