@studiometa/forge-cli 0.4.7 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -27
- package/dist/cli.js +543 -76
- package/dist/cli.js.map +1 -1
- package/dist/commands/backups/handlers.d.ts.map +1 -1
- package/dist/commands/backups/help.d.ts.map +1 -1
- package/dist/commands/backups/options.d.ts +33 -0
- package/dist/commands/backups/options.d.ts.map +1 -0
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/deployments/handlers.d.ts.map +1 -1
- package/dist/commands/env/handlers.d.ts.map +1 -1
- package/dist/commands/nginx/handlers.d.ts.map +1 -1
- package/dist/commands/php/handlers.d.ts.map +1 -1
- package/dist/commands/scheduled-jobs/help.d.ts.map +1 -1
- package/dist/commands/servers/command.d.ts.map +1 -1
- package/dist/commands/servers/handlers.d.ts +4 -0
- package/dist/commands/servers/handlers.d.ts.map +1 -1
- package/dist/commands/servers/help.d.ts.map +1 -1
- package/dist/commands/storage-providers/command.d.ts +2 -0
- package/dist/commands/storage-providers/command.d.ts.map +1 -0
- package/dist/commands/storage-providers/handlers.d.ts +6 -0
- package/dist/commands/storage-providers/handlers.d.ts.map +1 -0
- package/dist/commands/storage-providers/help.d.ts +2 -0
- package/dist/commands/storage-providers/help.d.ts.map +1 -0
- package/dist/commands/storage-providers/index.d.ts +3 -0
- package/dist/commands/storage-providers/index.d.ts.map +1 -0
- package/dist/utils/print-content.d.ts +9 -0
- package/dist/utils/print-content.d.ts.map +1 -0
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as isOutputFormat, _ as setToken, a as runCommand, c as OutputFormatter, d as createConfigStore, f as deleteToken, g as setOrganizationSlug, h as getToken, l as colors, m as getOrganizationSlug, n as exitWithError, o as createContext, r as exitWithValidationError, t as ExitCode, u as setColorEnabled, v as ApiError, x as ValidationError } from "./error-handler-Adi84RcF.js";
|
|
3
|
-
import {
|
|
2
|
+
import { C as isOutputFormat, _ as setToken, a as runCommand, c as OutputFormatter, d as createConfigStore$1, f as deleteToken, g as setOrganizationSlug, h as getToken$1, l as colors, m as getOrganizationSlug, n as exitWithError, o as createContext, r as exitWithValidationError, t as ExitCode, u as setColorEnabled, v as ApiError, x as ValidationError } from "./error-handler-Adi84RcF.js";
|
|
3
|
+
import { STORAGE_PROVIDER_TYPES } from "@studiometa/forge-api";
|
|
4
|
+
import { PHP_CONFIG_TYPES, RESTARTABLE_SERVICES, activateCertificate, createAuditLogger, createBackupConfig, createCommand, createDatabaseUser, createMonitor, createNginxTemplate, createRedirectRule, createScheduledJob, createSecurityRule, createServer, createSshKey, createStorageProvider, deleteBackupConfig, deleteDatabaseUser, deleteMonitor, deleteNginxTemplate, deleteRedirectRule, deleteScheduledJob, deleteSecurityRule, deleteSshKey, deleteStorageProvider, deploySiteAndWait, disablePhpOpcache, enablePhpOpcache, getBackupConfig, getCertificate, getCommand, getDaemon, getDaemonLog, getDatabase, getDatabaseUser, getDeploymentLog, getDeploymentScript, getEnv, getFirewallRule, getMonitor, getNginxConfig, getNginxTemplate, getPhpConfig, getPhpMaxExecutionTime, getPhpMaxUploadSize, getPhpOpcache, getRecipe, getRedirectRule, getScheduledJob, getSecurityRule, getServer, getSite, getSshKey, getStorageProvider, getUser, listBackupConfigs, listCommands, listDaemons, listDatabaseUsers, listDatabases, listDeployments, listFirewallRules, listMonitors, listNginxTemplates, listPhpVersions, listRecipes, listRedirectRules, listScheduledJobs, listSecurityRules, listServers, listServices, listSites, listSshKeys, listStorageProviders, matchByName, rebootServer, restartDaemon, restartService, runRecipe, updateBackupConfig, updateDaemon, updateDatabaseUser, updateDeploymentScript, updateEnv, updateNginxConfig, updateNginxTemplate, updatePhpConfig, updatePhpMaxExecutionTime, updatePhpMaxUploadSize, updateRecipe, updateSecurityRule, updateSite } from "@studiometa/forge-core";
|
|
4
5
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
6
|
import { homedir } from "node:os";
|
|
6
7
|
import { join } from "node:path";
|
|
@@ -122,7 +123,87 @@ async function resolveSiteId(value, serverId, execCtx) {
|
|
|
122
123
|
return String(partial[0].id);
|
|
123
124
|
}
|
|
124
125
|
//#endregion
|
|
126
|
+
//#region src/commands/backups/options.ts
|
|
127
|
+
/** Backup frequencies accepted by Forge. */
|
|
128
|
+
var BACKUP_FREQUENCIES = [
|
|
129
|
+
"hourly",
|
|
130
|
+
"daily",
|
|
131
|
+
"weekly",
|
|
132
|
+
"custom"
|
|
133
|
+
];
|
|
134
|
+
/**
|
|
135
|
+
* Read a list option: repeated and/or comma separated (`--databases 1,2 --databases app`).
|
|
136
|
+
*/
|
|
137
|
+
function listValues(value) {
|
|
138
|
+
return [value].flat().filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Whether a value is a numeric ID.
|
|
142
|
+
*/
|
|
143
|
+
function isId(value) {
|
|
144
|
+
return /^\d+$/.test(value);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Check the --frequency value. Throws a ValidationError for an unknown frequency.
|
|
148
|
+
*/
|
|
149
|
+
function checkFrequency(frequency) {
|
|
150
|
+
if (!BACKUP_FREQUENCIES.includes(frequency)) throw new ValidationError(`Invalid frequency "${frequency}": use ${BACKUP_FREQUENCIES.join(", ")}`, "frequency");
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Parse the --retention value: an integer from 1 to 8760.
|
|
154
|
+
*/
|
|
155
|
+
function parseRetention(value) {
|
|
156
|
+
const retention = Number(value);
|
|
157
|
+
if (!Number.isInteger(retention) || retention < 1 || retention > 8760) throw new ValidationError(`Invalid retention "${String(value)}": use a number of backups from 1 to 8760`, "retention");
|
|
158
|
+
return retention;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Resolve a storage provider ID or exact name to its numeric ID.
|
|
162
|
+
*/
|
|
163
|
+
async function resolveStorageProviderId(value, execCtx) {
|
|
164
|
+
if (isId(value)) return Number(value);
|
|
165
|
+
const { data } = await listStorageProviders({}, execCtx);
|
|
166
|
+
const matches = data.filter((p) => p.name === value);
|
|
167
|
+
if (matches.length === 0) throw new ValidationError(`No storage provider named "${value}"`, "storage-provider", ["List the storage providers with: forge storage-providers list"]);
|
|
168
|
+
if (matches.length > 1) throw new ValidationError(`${matches.length} storage providers are named "${value}": use the ID (${matches.map((p) => p.id).join(", ")})`, "storage-provider");
|
|
169
|
+
return matches[0].id;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Resolve database IDs or exact names to numeric IDs. Names are looked up on the server.
|
|
173
|
+
*/
|
|
174
|
+
async function resolveDatabaseIds(values, server_id, execCtx) {
|
|
175
|
+
if (values.every(isId)) return values.map(Number);
|
|
176
|
+
const { data } = await listDatabases({ server_id }, execCtx);
|
|
177
|
+
return values.map((value) => {
|
|
178
|
+
if (isId(value)) return Number(value);
|
|
179
|
+
const database = data.find((db) => db.name === value);
|
|
180
|
+
if (!database) throw new ValidationError(`No database named "${value}" on the server`, "databases", [`List the databases with: forge databases list --server ${server_id}`]);
|
|
181
|
+
return database.id;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Read the optional backup configuration fields that map one to one to the API.
|
|
186
|
+
*/
|
|
187
|
+
function optionalBackupFields(options) {
|
|
188
|
+
const fields = {};
|
|
189
|
+
for (const [option, field] of Object.entries({
|
|
190
|
+
name: "name",
|
|
191
|
+
bucket: "bucket",
|
|
192
|
+
directory: "directory",
|
|
193
|
+
day: "day",
|
|
194
|
+
time: "time",
|
|
195
|
+
cron: "cron",
|
|
196
|
+
"notification-email": "notification_email"
|
|
197
|
+
})) {
|
|
198
|
+
const value = options[option];
|
|
199
|
+
if (typeof value === "string") fields[field] = value;
|
|
200
|
+
}
|
|
201
|
+
return fields;
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
125
204
|
//#region src/commands/backups/handlers.ts
|
|
205
|
+
/** Retention when --retention is not given: Forge requires a value. */
|
|
206
|
+
var DEFAULT_RETENTION = 5;
|
|
126
207
|
async function backupsList(ctx) {
|
|
127
208
|
const server = String(ctx.options.server ?? "");
|
|
128
209
|
if (!server) exitWithValidationError("server_id", "forge backups list --server <server_id>", ctx.formatter);
|
|
@@ -153,7 +234,10 @@ async function backupsGet(args, ctx) {
|
|
|
153
234
|
ctx.formatter.outputOne(result.data, [
|
|
154
235
|
"id",
|
|
155
236
|
"name",
|
|
237
|
+
"storage_provider_id",
|
|
156
238
|
"provider",
|
|
239
|
+
"bucket",
|
|
240
|
+
"directory",
|
|
157
241
|
"schedule",
|
|
158
242
|
"displayable_schedule",
|
|
159
243
|
"retention",
|
|
@@ -162,58 +246,65 @@ async function backupsGet(args, ctx) {
|
|
|
162
246
|
]);
|
|
163
247
|
}, ctx.formatter);
|
|
164
248
|
}
|
|
249
|
+
var CREATE_USAGE$2 = "forge backups create --server <server> --storage-provider <id|name> --frequency <hourly|daily|weekly|custom> --databases <ids|names> [--retention <n>]";
|
|
165
250
|
async function backupsCreate(ctx) {
|
|
166
251
|
const server = String(ctx.options.server ?? "");
|
|
167
|
-
const
|
|
168
|
-
const frequency =
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
if (!
|
|
172
|
-
if (
|
|
173
|
-
if (
|
|
252
|
+
const storageProvider = ctx.options["storage-provider"];
|
|
253
|
+
const frequency = ctx.options.frequency;
|
|
254
|
+
const databases = listValues(ctx.options.databases);
|
|
255
|
+
if (!server) exitWithValidationError("server_id", CREATE_USAGE$2, ctx.formatter);
|
|
256
|
+
if (typeof storageProvider !== "string" || !storageProvider) exitWithValidationError("storage-provider", CREATE_USAGE$2, ctx.formatter);
|
|
257
|
+
if (typeof frequency !== "string" || !frequency) exitWithValidationError("frequency", CREATE_USAGE$2, ctx.formatter);
|
|
258
|
+
if (databases.length === 0) exitWithValidationError("databases", CREATE_USAGE$2, ctx.formatter);
|
|
174
259
|
await runCommand(async () => {
|
|
260
|
+
checkFrequency(frequency);
|
|
261
|
+
const retention = ctx.options.retention === void 0 ? DEFAULT_RETENTION : parseRetention(ctx.options.retention);
|
|
175
262
|
const token = ctx.getToken();
|
|
176
263
|
const execCtx = ctx.createExecutorContext(token);
|
|
264
|
+
const server_id = await resolveServerId(server, execCtx);
|
|
265
|
+
const storage_provider_id = await resolveStorageProviderId(storageProvider, execCtx);
|
|
266
|
+
const database_ids = await resolveDatabaseIds(databases, server_id, execCtx);
|
|
177
267
|
await createBackupConfig({
|
|
178
|
-
server_id
|
|
179
|
-
storage_provider_id
|
|
268
|
+
server_id,
|
|
269
|
+
storage_provider_id,
|
|
180
270
|
frequency,
|
|
181
|
-
retention
|
|
182
|
-
database_ids
|
|
271
|
+
retention,
|
|
272
|
+
database_ids,
|
|
273
|
+
...ctx.options["include-new-databases"] === true ? { include_new_databases: true } : {},
|
|
274
|
+
...optionalBackupFields(ctx.options)
|
|
183
275
|
}, execCtx);
|
|
184
276
|
ctx.formatter.success("Backup configuration created.");
|
|
185
277
|
}, ctx.formatter);
|
|
186
278
|
}
|
|
279
|
+
var UPDATE_USAGE = "forge backups update <backup_id> --server <server> --storage-provider <id|name> --frequency <hourly|daily|weekly|custom> --retention <n> --databases <ids|names>";
|
|
187
280
|
async function backupsUpdate(args, ctx) {
|
|
188
281
|
const [id] = args;
|
|
189
282
|
const server = String(ctx.options.server ?? "");
|
|
190
|
-
const
|
|
191
|
-
const frequency =
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (!
|
|
196
|
-
if (
|
|
197
|
-
if (
|
|
198
|
-
if (
|
|
199
|
-
if (!retention) exitWithValidationError("retention", "forge backups update <backup_id> --server <server_id> --provider <provider> --frequency <frequency> --retention <retention>", ctx.formatter);
|
|
283
|
+
const storageProvider = ctx.options["storage-provider"];
|
|
284
|
+
const frequency = ctx.options.frequency;
|
|
285
|
+
const databases = listValues(ctx.options.databases);
|
|
286
|
+
if (!id) exitWithValidationError("backup_id", UPDATE_USAGE, ctx.formatter);
|
|
287
|
+
if (!server) exitWithValidationError("server_id", UPDATE_USAGE, ctx.formatter);
|
|
288
|
+
if (typeof storageProvider !== "string" || !storageProvider) exitWithValidationError("storage-provider", UPDATE_USAGE, ctx.formatter);
|
|
289
|
+
if (typeof frequency !== "string" || !frequency) exitWithValidationError("frequency", UPDATE_USAGE, ctx.formatter);
|
|
290
|
+
if (ctx.options.retention === void 0) exitWithValidationError("retention", UPDATE_USAGE, ctx.formatter);
|
|
291
|
+
if (databases.length === 0) exitWithValidationError("databases", UPDATE_USAGE, ctx.formatter);
|
|
200
292
|
await runCommand(async () => {
|
|
293
|
+
checkFrequency(frequency);
|
|
294
|
+
const retention = parseRetention(ctx.options.retention);
|
|
201
295
|
const token = ctx.getToken();
|
|
202
296
|
const execCtx = ctx.createExecutorContext(token);
|
|
297
|
+
const server_id = await resolveServerId(server, execCtx);
|
|
298
|
+
const storage_provider_id = await resolveStorageProviderId(storageProvider, execCtx);
|
|
299
|
+
const database_ids = await resolveDatabaseIds(databases, server_id, execCtx);
|
|
203
300
|
await updateBackupConfig({
|
|
204
|
-
server_id
|
|
301
|
+
server_id,
|
|
205
302
|
id,
|
|
206
|
-
storage_provider_id
|
|
303
|
+
storage_provider_id,
|
|
207
304
|
frequency,
|
|
208
305
|
retention,
|
|
209
306
|
database_ids,
|
|
210
|
-
...
|
|
211
|
-
...ctx.options.bucket != null ? { bucket: String(ctx.options.bucket) } : {},
|
|
212
|
-
...ctx.options.directory != null ? { directory: String(ctx.options.directory) } : {},
|
|
213
|
-
...ctx.options.day != null ? { day: String(ctx.options.day) } : {},
|
|
214
|
-
...ctx.options.time != null ? { time: String(ctx.options.time) } : {},
|
|
215
|
-
...ctx.options.cron != null ? { cron: String(ctx.options.cron) } : {},
|
|
216
|
-
...ctx.options["notification-email"] != null ? { notification_email: String(ctx.options["notification-email"]) } : {}
|
|
307
|
+
...optionalBackupFields(ctx.options)
|
|
217
308
|
}, execCtx);
|
|
218
309
|
ctx.formatter.success(`Backup configuration ${id} updated.`);
|
|
219
310
|
}, ctx.formatter);
|
|
@@ -271,37 +362,50 @@ ${colors.bold("USAGE:")}
|
|
|
271
362
|
forge backups get <backup_id> --server <server_id> [options]
|
|
272
363
|
`);
|
|
273
364
|
else if (subcommand === "create") console.log(`
|
|
274
|
-
${colors.bold("forge backups create")} - Create a
|
|
365
|
+
${colors.bold("forge backups create")} - Create a database backup configuration
|
|
275
366
|
|
|
276
367
|
${colors.bold("USAGE:")}
|
|
277
|
-
forge backups create --server <
|
|
368
|
+
forge backups create --server <server> --storage-provider <id|name> --frequency <frequency> --databases <ids|names> [options]
|
|
278
369
|
|
|
279
370
|
${colors.bold("OPTIONS:")}
|
|
280
|
-
--server <id>
|
|
281
|
-
--provider <name>
|
|
282
|
-
--frequency <freq>
|
|
283
|
-
--databases <ids>
|
|
284
|
-
|
|
371
|
+
--server <id|name> Server ID or name (required)
|
|
372
|
+
--storage-provider <id|name> Storage provider ID or exact name (required, see forge storage-providers list)
|
|
373
|
+
--frequency <freq> hourly, daily, weekly or custom (required)
|
|
374
|
+
--databases <ids|names> Database IDs or exact names, comma separated or repeated (required)
|
|
375
|
+
--retention <n> Number of backups to keep, 1 to 8760 (default: 5)
|
|
376
|
+
--time <HH:MM> Daily and weekly: time on the hour or half hour, e.g. 02:00
|
|
377
|
+
--day <0-6> Weekly: day of week, 0 is Sunday
|
|
378
|
+
--cron <expr> Custom: cron expression
|
|
379
|
+
--bucket <bucket> Bucket (default: the storage provider bucket)
|
|
380
|
+
--directory <dir> Directory in the bucket
|
|
381
|
+
--name <name> Configuration name
|
|
382
|
+
--notification-email <email> Email notified when a backup fails
|
|
383
|
+
--include-new-databases Also back up databases created later
|
|
384
|
+
-f, --format <fmt> Output format: json, human, table
|
|
385
|
+
|
|
386
|
+
${colors.bold("EXAMPLE:")}
|
|
387
|
+
forge backups create --server web-1 --storage-provider Backups --frequency daily \\
|
|
388
|
+
--time 02:00 --retention 7 --directory web-1 --databases app,wordpress
|
|
285
389
|
`);
|
|
286
390
|
else if (subcommand === "update") console.log(`
|
|
287
|
-
${colors.bold("forge backups update")} - Update a backup configuration
|
|
391
|
+
${colors.bold("forge backups update")} - Update a database backup configuration
|
|
288
392
|
|
|
289
393
|
${colors.bold("USAGE:")}
|
|
290
|
-
forge backups update <backup_id> --server <
|
|
394
|
+
forge backups update <backup_id> --server <server> --storage-provider <id|name> --frequency <frequency> --retention <n> --databases <ids|names> [options]
|
|
291
395
|
|
|
292
396
|
${colors.bold("OPTIONS:")}
|
|
293
|
-
--server <id>
|
|
294
|
-
--provider <id>
|
|
295
|
-
--frequency <freq>
|
|
296
|
-
--retention <n>
|
|
297
|
-
--databases <ids>
|
|
298
|
-
--
|
|
299
|
-
--
|
|
300
|
-
--
|
|
301
|
-
--
|
|
302
|
-
--
|
|
303
|
-
--
|
|
304
|
-
--notification-email <email>
|
|
397
|
+
--server <id|name> Server ID or name (required)
|
|
398
|
+
--storage-provider <id|name> Storage provider ID or exact name (required)
|
|
399
|
+
--frequency <freq> hourly, daily, weekly or custom (required)
|
|
400
|
+
--retention <n> Number of backups to keep, 1 to 8760 (required)
|
|
401
|
+
--databases <ids|names> Database IDs or exact names, comma separated or repeated (required)
|
|
402
|
+
--time <HH:MM> Daily and weekly: time on the hour or half hour
|
|
403
|
+
--day <0-6> Weekly: day of week, 0 is Sunday
|
|
404
|
+
--cron <expr> Custom: cron expression
|
|
405
|
+
--bucket <bucket> Bucket
|
|
406
|
+
--directory <dir> Directory in the bucket
|
|
407
|
+
--name <name> Configuration name
|
|
408
|
+
--notification-email <email> Email notified when a backup fails
|
|
305
409
|
-f, --format <fmt> Output format: json, human, table
|
|
306
410
|
`);
|
|
307
411
|
else if (subcommand === "delete") console.log(`
|
|
@@ -345,11 +449,11 @@ _forge_completions() {
|
|
|
345
449
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
346
450
|
|
|
347
451
|
# Main commands
|
|
348
|
-
commands="config servers s sites deployments d databases db database-users daemons env nginx certificates certs firewall-rules fw ssh ssh-keys backups commands scheduled-jobs services php user monitors nginx-templates security-rules redirect-rules recipes completion help"
|
|
452
|
+
commands="config servers s sites deployments d databases db database-users daemons env nginx certificates certs firewall-rules fw ssh ssh-keys backups storage-providers commands scheduled-jobs services php user monitors nginx-templates security-rules redirect-rules recipes completion help"
|
|
349
453
|
|
|
350
454
|
# Subcommands for each command
|
|
351
455
|
local config_cmds="set get delete"
|
|
352
|
-
local servers_cmds="list ls get reboot"
|
|
456
|
+
local servers_cmds="list ls get create reboot"
|
|
353
457
|
local sites_cmds="list ls get"
|
|
354
458
|
local deployments_cmds="list ls deploy logs log script update-script"
|
|
355
459
|
local databases_cmds="list ls get"
|
|
@@ -360,7 +464,8 @@ _forge_completions() {
|
|
|
360
464
|
local certificates_cmds="get activate"
|
|
361
465
|
local firewall_rules_cmds="list ls get"
|
|
362
466
|
local ssh_keys_cmds="list ls get sync"
|
|
363
|
-
local backups_cmds="list ls get create delete"
|
|
467
|
+
local backups_cmds="list ls get create update delete"
|
|
468
|
+
local storage_providers_cmds="list ls get create delete"
|
|
364
469
|
local commands_cmds="list ls get create"
|
|
365
470
|
local scheduled_jobs_cmds="list ls get create delete"
|
|
366
471
|
local services_cmds="list ls restart"
|
|
@@ -374,7 +479,7 @@ _forge_completions() {
|
|
|
374
479
|
local completion_cmds="bash zsh fish"
|
|
375
480
|
|
|
376
481
|
# Global options
|
|
377
|
-
options="--token --server --site -f --format --no-color -h --help -v --version --name --command --content --from --to --provider --frequency --type --operator --threshold --minutes --key --port --ip-address --user --private --dry-run --script --servers --domain --password --credential-id --region --size --project-type --directory --file --script-file --check --all-servers --prune"
|
|
482
|
+
options="--token --server --site -f --format --no-color -h --help -v --version --name --command --content --from --to --provider --frequency --cron --type --operator --threshold --minutes --key --port --ip-address --user --private --dry-run --script --servers --domain --password --credential-id --region --size --project-type --directory --file --script-file --check --all-servers --prune --ubuntu-version --php-version --database --private-ip --ssh-port --tag --storage-provider --databases --retention --time --day --bucket --notification-email --include-new-databases --endpoint --access-key --secret-key-file --assume-role"
|
|
378
483
|
|
|
379
484
|
# Format options
|
|
380
485
|
local formats="json human table"
|
|
@@ -427,6 +532,9 @@ _forge_completions() {
|
|
|
427
532
|
backups)
|
|
428
533
|
COMPREPLY=( $(compgen -W "\${backups_cmds}" -- "\${cur}") )
|
|
429
534
|
;;
|
|
535
|
+
storage-providers)
|
|
536
|
+
COMPREPLY=( $(compgen -W "\${storage_providers_cmds}" -- "\${cur}") )
|
|
537
|
+
;;
|
|
430
538
|
commands)
|
|
431
539
|
COMPREPLY=( $(compgen -W "\${commands_cmds}" -- "\${cur}") )
|
|
432
540
|
;;
|
|
@@ -475,7 +583,7 @@ _forge_completions() {
|
|
|
475
583
|
# Complete file paths
|
|
476
584
|
COMPREPLY=( $(compgen -f -- "\${cur}") )
|
|
477
585
|
;;
|
|
478
|
-
--token|--server|--site|--version|--name|--command|--content|--from|--to|--provider|--frequency|--type|--operator|--threshold|--minutes|--key|--port|--ip-address|--user|--script|--servers|--domain|--password|--credential-id|--region|--size|--project-type|--directory)
|
|
586
|
+
--token|--server|--site|--version|--name|--command|--content|--from|--to|--provider|--frequency|--cron|--type|--operator|--threshold|--minutes|--key|--port|--ip-address|--user|--script|--servers|--domain|--password|--credential-id|--region|--size|--project-type|--directory|--ubuntu-version|--php-version|--database|--private-ip|--ssh-port|--tag|--storage-provider|--databases|--retention|--time|--day|--bucket|--notification-email|--endpoint|--access-key)
|
|
479
587
|
# No completion for values requiring arguments
|
|
480
588
|
COMPREPLY=()
|
|
481
589
|
;;
|
|
@@ -523,7 +631,8 @@ _forge() {
|
|
|
523
631
|
'fw:Manage firewall rules (alias)'
|
|
524
632
|
'ssh:Open an SSH session to a server'
|
|
525
633
|
'ssh-keys:Manage SSH keys'
|
|
526
|
-
'backups:Manage backup configurations'
|
|
634
|
+
'backups:Manage database backup configurations'
|
|
635
|
+
'storage-providers:Manage the storage providers of database backups'
|
|
527
636
|
'commands:Manage site commands'
|
|
528
637
|
'scheduled-jobs:Manage scheduled jobs'
|
|
529
638
|
'services:List and restart server services'
|
|
@@ -556,6 +665,7 @@ _forge() {
|
|
|
556
665
|
'list:List all servers'
|
|
557
666
|
'ls:List all servers (alias)'
|
|
558
667
|
'get:Get server details'
|
|
668
|
+
'create:Create a server'
|
|
559
669
|
'reboot:Reboot a server'
|
|
560
670
|
)
|
|
561
671
|
_describe 'servers command' servers_cmds
|
|
@@ -664,10 +774,22 @@ _forge() {
|
|
|
664
774
|
'ls:List backup configurations (alias)'
|
|
665
775
|
'get:Get backup configuration details'
|
|
666
776
|
'create:Create a backup configuration'
|
|
777
|
+
'update:Update a backup configuration'
|
|
667
778
|
'delete:Delete a backup configuration'
|
|
668
779
|
)
|
|
669
780
|
_describe 'backups command' backups_cmds
|
|
670
781
|
;;
|
|
782
|
+
storage-providers)
|
|
783
|
+
local -a storage_providers_cmds
|
|
784
|
+
storage_providers_cmds=(
|
|
785
|
+
'list:List storage providers'
|
|
786
|
+
'ls:List storage providers (alias)'
|
|
787
|
+
'get:Get storage provider details'
|
|
788
|
+
'create:Create a storage provider'
|
|
789
|
+
'delete:Delete a storage provider'
|
|
790
|
+
)
|
|
791
|
+
_describe 'storage-providers command' storage_providers_cmds
|
|
792
|
+
;;
|
|
671
793
|
commands)
|
|
672
794
|
local -a commands_cmds
|
|
673
795
|
commands_cmds=(
|
|
@@ -805,6 +927,7 @@ _forge() {
|
|
|
805
927
|
'--to[To]:to:' \\
|
|
806
928
|
'--provider[Provider]:provider:' \\
|
|
807
929
|
'--frequency[Frequency]:frequency:' \\
|
|
930
|
+
'--cron[Cron expression]:cron:' \\
|
|
808
931
|
'--type[Type]:type:' \\
|
|
809
932
|
'--operator[Operator]:operator:' \\
|
|
810
933
|
'--threshold[Threshold]:threshold:' \\
|
|
@@ -822,12 +945,31 @@ _forge() {
|
|
|
822
945
|
'--size[Size]:size:' \\
|
|
823
946
|
'--project-type[Project type]:project type:' \\
|
|
824
947
|
'--directory[Directory]:directory:' \\
|
|
948
|
+
'--ubuntu-version[Ubuntu version]:ubuntu version:' \\
|
|
949
|
+
'--php-version[PHP version]:php version:' \\
|
|
950
|
+
'--database[Database type]:database type:' \\
|
|
951
|
+
'--private-ip[Private IP address]:private ip address:' \\
|
|
952
|
+
'--ssh-port[SSH port]:ssh port:' \\
|
|
953
|
+
'--tag[Tag]:tag:' \\
|
|
825
954
|
'--file[File path (- for stdin)]:file:_files' \\
|
|
826
955
|
'--script-file[Deployment script file]:file:_files' \\
|
|
827
956
|
'--all-servers[Target every server]' \\
|
|
828
957
|
'--prune[Remove SSH keys that are not in the file]' \\
|
|
829
958
|
'--dry-run[Print the plan and change nothing]' \\
|
|
830
|
-
'--check[Compare with Forge, do not write]'
|
|
959
|
+
'--check[Compare with Forge, do not write]' \\
|
|
960
|
+
'--storage-provider[Storage provider ID or name]:storage provider:' \\
|
|
961
|
+
'--databases[Database IDs or names]:databases:' \\
|
|
962
|
+
'--retention[Number of backups to keep]:retention:' \\
|
|
963
|
+
'--time[Backup time (HH:MM)]:time:' \\
|
|
964
|
+
'--day[Backup day of week (0-6)]:day:' \\
|
|
965
|
+
'--cron[Cron expression]:cron:' \\
|
|
966
|
+
'--bucket[Bucket]:bucket:' \\
|
|
967
|
+
'--notification-email[Notification email]:email:' \\
|
|
968
|
+
'--include-new-databases[Also back up new databases]' \\
|
|
969
|
+
'--endpoint[S3 endpoint URL]:endpoint:' \\
|
|
970
|
+
'--access-key[Access key ID]:access key:' \\
|
|
971
|
+
'--secret-key-file[Secret key file (- for stdin)]:file:_files' \\
|
|
972
|
+
'--assume-role[Use the EC2 instance role]'
|
|
831
973
|
;;
|
|
832
974
|
esac
|
|
833
975
|
}
|
|
@@ -855,7 +997,8 @@ complete -c forge -f -n "__fish_use_subcommand" -a "firewall-rules" -d "Manage f
|
|
|
855
997
|
complete -c forge -f -n "__fish_use_subcommand" -a "fw" -d "Manage firewall rules (alias)"
|
|
856
998
|
complete -c forge -f -n "__fish_use_subcommand" -a "ssh" -d "Open an SSH session to a server"
|
|
857
999
|
complete -c forge -f -n "__fish_use_subcommand" -a "ssh-keys" -d "Manage SSH keys"
|
|
858
|
-
complete -c forge -f -n "__fish_use_subcommand" -a "backups" -d "Manage backup configurations"
|
|
1000
|
+
complete -c forge -f -n "__fish_use_subcommand" -a "backups" -d "Manage database backup configurations"
|
|
1001
|
+
complete -c forge -f -n "__fish_use_subcommand" -a "storage-providers" -d "Manage the storage providers of database backups"
|
|
859
1002
|
complete -c forge -f -n "__fish_use_subcommand" -a "commands" -d "Manage site commands"
|
|
860
1003
|
complete -c forge -f -n "__fish_use_subcommand" -a "scheduled-jobs" -d "Manage scheduled jobs"
|
|
861
1004
|
complete -c forge -f -n "__fish_use_subcommand" -a "services" -d "List and restart server services"
|
|
@@ -878,6 +1021,7 @@ complete -c forge -f -n "__fish_seen_subcommand_from config" -a "delete" -d "Del
|
|
|
878
1021
|
complete -c forge -f -n "__fish_seen_subcommand_from servers s" -a "list" -d "List all servers"
|
|
879
1022
|
complete -c forge -f -n "__fish_seen_subcommand_from servers s" -a "ls" -d "List all servers (alias)"
|
|
880
1023
|
complete -c forge -f -n "__fish_seen_subcommand_from servers s" -a "get" -d "Get server details"
|
|
1024
|
+
complete -c forge -f -n "__fish_seen_subcommand_from servers s" -a "create" -d "Create a server"
|
|
881
1025
|
complete -c forge -f -n "__fish_seen_subcommand_from servers s" -a "reboot" -d "Reboot a server"
|
|
882
1026
|
|
|
883
1027
|
# sites subcommands
|
|
@@ -943,8 +1087,16 @@ complete -c forge -f -n "__fish_seen_subcommand_from backups" -a "list" -d "List
|
|
|
943
1087
|
complete -c forge -f -n "__fish_seen_subcommand_from backups" -a "ls" -d "List backup configurations (alias)"
|
|
944
1088
|
complete -c forge -f -n "__fish_seen_subcommand_from backups" -a "get" -d "Get backup configuration details"
|
|
945
1089
|
complete -c forge -f -n "__fish_seen_subcommand_from backups" -a "create" -d "Create a backup configuration"
|
|
1090
|
+
complete -c forge -f -n "__fish_seen_subcommand_from backups" -a "update" -d "Update a backup configuration"
|
|
946
1091
|
complete -c forge -f -n "__fish_seen_subcommand_from backups" -a "delete" -d "Delete a backup configuration"
|
|
947
1092
|
|
|
1093
|
+
# storage-providers subcommands
|
|
1094
|
+
complete -c forge -f -n "__fish_seen_subcommand_from storage-providers" -a "list" -d "List storage providers"
|
|
1095
|
+
complete -c forge -f -n "__fish_seen_subcommand_from storage-providers" -a "ls" -d "List storage providers (alias)"
|
|
1096
|
+
complete -c forge -f -n "__fish_seen_subcommand_from storage-providers" -a "get" -d "Get storage provider details"
|
|
1097
|
+
complete -c forge -f -n "__fish_seen_subcommand_from storage-providers" -a "create" -d "Create a storage provider"
|
|
1098
|
+
complete -c forge -f -n "__fish_seen_subcommand_from storage-providers" -a "delete" -d "Delete a storage provider"
|
|
1099
|
+
|
|
948
1100
|
# commands subcommands
|
|
949
1101
|
complete -c forge -f -n "__fish_seen_subcommand_from commands" -a "list" -d "List commands"
|
|
950
1102
|
complete -c forge -f -n "__fish_seen_subcommand_from commands" -a "ls" -d "List commands (alias)"
|
|
@@ -1034,6 +1186,7 @@ complete -c forge -l from -d "From" -r
|
|
|
1034
1186
|
complete -c forge -l to -d "To" -r
|
|
1035
1187
|
complete -c forge -l provider -d "Provider" -r
|
|
1036
1188
|
complete -c forge -l frequency -d "Frequency" -r
|
|
1189
|
+
complete -c forge -l cron -d "Cron expression" -r
|
|
1037
1190
|
complete -c forge -l type -d "Type" -r
|
|
1038
1191
|
complete -c forge -l operator -d "Operator" -r
|
|
1039
1192
|
complete -c forge -l threshold -d "Threshold" -r
|
|
@@ -1055,8 +1208,27 @@ complete -c forge -l file -d "File path (- for stdin)" -r -F
|
|
|
1055
1208
|
complete -c forge -l script-file -d "Deployment script file" -r -F
|
|
1056
1209
|
complete -c forge -l all-servers -d "Target every server"
|
|
1057
1210
|
complete -c forge -l prune -d "Remove SSH keys that are not in the file"
|
|
1211
|
+
complete -c forge -l ubuntu-version -d "Ubuntu version" -r
|
|
1212
|
+
complete -c forge -l php-version -d "PHP version" -r
|
|
1213
|
+
complete -c forge -l database -d "Database type" -r
|
|
1214
|
+
complete -c forge -l private-ip -d "Private IP address" -r
|
|
1215
|
+
complete -c forge -l ssh-port -d "SSH port" -r
|
|
1216
|
+
complete -c forge -l tag -d "Tag" -r
|
|
1058
1217
|
complete -c forge -l dry-run -d "Print the plan and change nothing"
|
|
1059
1218
|
complete -c forge -l check -d "Compare with Forge, do not write"
|
|
1219
|
+
complete -c forge -l storage-provider -d "Storage provider ID or name" -r
|
|
1220
|
+
complete -c forge -l databases -d "Database IDs or names" -r
|
|
1221
|
+
complete -c forge -l retention -d "Number of backups to keep" -r
|
|
1222
|
+
complete -c forge -l time -d "Backup time (HH:MM)" -r
|
|
1223
|
+
complete -c forge -l day -d "Backup day of week (0-6)" -r
|
|
1224
|
+
complete -c forge -l cron -d "Cron expression" -r
|
|
1225
|
+
complete -c forge -l bucket -d "Bucket" -r
|
|
1226
|
+
complete -c forge -l notification-email -d "Notification email" -r
|
|
1227
|
+
complete -c forge -l include-new-databases -d "Also back up new databases"
|
|
1228
|
+
complete -c forge -l endpoint -d "S3 endpoint URL" -r
|
|
1229
|
+
complete -c forge -l access-key -d "Access key ID" -r
|
|
1230
|
+
complete -c forge -l secret-key-file -d "Secret key file (- for stdin)" -r -F
|
|
1231
|
+
complete -c forge -l assume-role -d "Use the EC2 instance role"
|
|
1060
1232
|
`;
|
|
1061
1233
|
/**
|
|
1062
1234
|
* Get the appropriate completion file path for the shell
|
|
@@ -1434,7 +1606,7 @@ function handleConfigCommand(subcommand, args, options) {
|
|
|
1434
1606
|
const rawFormat = options.format ?? options.f ?? "human";
|
|
1435
1607
|
const format = isOutputFormat(rawFormat) ? rawFormat : "human";
|
|
1436
1608
|
const formatter = new OutputFormatter(format, options["no-color"] === true);
|
|
1437
|
-
const store = createConfigStore();
|
|
1609
|
+
const store = createConfigStore$1();
|
|
1438
1610
|
switch (subcommand) {
|
|
1439
1611
|
case "set": {
|
|
1440
1612
|
const [key, value] = args;
|
|
@@ -1461,7 +1633,7 @@ function handleConfigCommand(subcommand, args, options) {
|
|
|
1461
1633
|
break;
|
|
1462
1634
|
}
|
|
1463
1635
|
case "get": {
|
|
1464
|
-
const token = getToken(store);
|
|
1636
|
+
const token = getToken$1(store);
|
|
1465
1637
|
const orgSlug = getOrganizationSlug(store);
|
|
1466
1638
|
if (format === "json") formatter.output({
|
|
1467
1639
|
apiToken: token ? maskToken(token) : null,
|
|
@@ -2058,6 +2230,18 @@ function reportTextDrift(label, remote, local, formatter) {
|
|
|
2058
2230
|
} : null, formatter);
|
|
2059
2231
|
}
|
|
2060
2232
|
//#endregion
|
|
2233
|
+
//#region src/utils/print-content.ts
|
|
2234
|
+
/**
|
|
2235
|
+
* Print file content fetched from Forge, such as a configuration or a script.
|
|
2236
|
+
*/
|
|
2237
|
+
/**
|
|
2238
|
+
* Write the content as it is, with a newline only when it does not end with one,
|
|
2239
|
+
* so `forge <command> get > file` round-trips with `--file file --check` without drift.
|
|
2240
|
+
*/
|
|
2241
|
+
function printContent(content) {
|
|
2242
|
+
process.stdout.write(content.endsWith("\n") ? content : `${content}\n`);
|
|
2243
|
+
}
|
|
2244
|
+
//#endregion
|
|
2061
2245
|
//#region src/commands/deployments/handlers.ts
|
|
2062
2246
|
async function deploymentsList(ctx) {
|
|
2063
2247
|
const server = String(ctx.options.server ?? "");
|
|
@@ -2126,7 +2310,7 @@ async function deploymentsScript(ctx) {
|
|
|
2126
2310
|
site_id: await resolveSiteId(site, server_id, execCtx)
|
|
2127
2311
|
}, execCtx);
|
|
2128
2312
|
if (ctx.formatter.isJson()) console.log(JSON.stringify({ content: result.data }));
|
|
2129
|
-
else
|
|
2313
|
+
else printContent(result.data);
|
|
2130
2314
|
}, ctx.formatter);
|
|
2131
2315
|
}
|
|
2132
2316
|
async function deploymentsUpdateScript(ctx) {
|
|
@@ -2414,7 +2598,7 @@ async function envGet(ctx) {
|
|
|
2414
2598
|
site_id: await resolveSiteId(site, server_id, execCtx)
|
|
2415
2599
|
}, execCtx);
|
|
2416
2600
|
if (ctx.formatter.isJson()) console.log(JSON.stringify({ content: result.data }));
|
|
2417
|
-
else
|
|
2601
|
+
else printContent(result.data);
|
|
2418
2602
|
}, ctx.formatter);
|
|
2419
2603
|
}
|
|
2420
2604
|
async function envUpdate(ctx) {
|
|
@@ -2775,7 +2959,7 @@ async function nginxGet(ctx) {
|
|
|
2775
2959
|
site_id: await resolveSiteId(site, server_id, execCtx)
|
|
2776
2960
|
}, execCtx);
|
|
2777
2961
|
if (ctx.formatter.isJson()) console.log(JSON.stringify({ content: result.data }));
|
|
2778
|
-
else
|
|
2962
|
+
else printContent(result.data);
|
|
2779
2963
|
}, ctx.formatter);
|
|
2780
2964
|
}
|
|
2781
2965
|
async function nginxUpdate(ctx) {
|
|
@@ -3127,7 +3311,7 @@ async function phpConfig(ctx) {
|
|
|
3127
3311
|
...target
|
|
3128
3312
|
}, execCtx);
|
|
3129
3313
|
if (ctx.formatter.isJson()) console.log(JSON.stringify({ content: result.data }));
|
|
3130
|
-
else
|
|
3314
|
+
else printContent(result.data);
|
|
3131
3315
|
});
|
|
3132
3316
|
}
|
|
3133
3317
|
/**
|
|
@@ -3820,7 +4004,16 @@ ${colors.bold("USAGE:")}
|
|
|
3820
4004
|
${colors.bold("OPTIONS:")}
|
|
3821
4005
|
--server <id> Server ID (required)
|
|
3822
4006
|
--command <cmd> Command to run (required)
|
|
4007
|
+
--site <id> Site ID, to create the job on a site
|
|
4008
|
+
--user <user> User that runs the command (default: forge)
|
|
4009
|
+
--frequency <freq> minutely, hourly, nightly, weekly, monthly, reboot
|
|
4010
|
+
or custom (default: custom)
|
|
4011
|
+
--cron <expr> Cron expression, used when --frequency is custom
|
|
3823
4012
|
-f, --format <fmt> Output format: json, human, table
|
|
4013
|
+
|
|
4014
|
+
${colors.bold("EXAMPLES:")}
|
|
4015
|
+
forge scheduled-jobs create --server web-1 --command "php artisan schedule:run" --frequency minutely
|
|
4016
|
+
forge scheduled-jobs create --server web-1 --command "php artisan backup:run" --cron "0 3 * * *"
|
|
3824
4017
|
`);
|
|
3825
4018
|
else if (subcommand === "delete") console.log(`
|
|
3826
4019
|
${colors.bold("forge scheduled-jobs delete")} - Delete a scheduled job
|
|
@@ -4056,10 +4249,6 @@ Run ${colors.cyan("forge security-rules <subcommand> --help")} for details.
|
|
|
4056
4249
|
//#endregion
|
|
4057
4250
|
//#region src/commands/servers/handlers.ts
|
|
4058
4251
|
/**
|
|
4059
|
-
* CLI handlers for servers commands.
|
|
4060
|
-
* Delegates all HTTP logic to forge-core executors.
|
|
4061
|
-
*/
|
|
4062
|
-
/**
|
|
4063
4252
|
* List all servers.
|
|
4064
4253
|
*/
|
|
4065
4254
|
async function serversList(ctx) {
|
|
@@ -4090,6 +4279,57 @@ async function serversGet(args, ctx) {
|
|
|
4090
4279
|
ctx.formatter.output(result.data);
|
|
4091
4280
|
}, ctx.formatter);
|
|
4092
4281
|
}
|
|
4282
|
+
var CREATE_USAGE$1 = "forge servers create --name <name> --ip-address <ip> [options]";
|
|
4283
|
+
/**
|
|
4284
|
+
* Create a server. Defaults to a Custom VPS app server on Ubuntu 26.04.
|
|
4285
|
+
*/
|
|
4286
|
+
async function serversCreate(ctx) {
|
|
4287
|
+
const name = String(ctx.options.name ?? "");
|
|
4288
|
+
const provider = String(ctx.options.provider ?? "custom");
|
|
4289
|
+
const ip_address = String(ctx.options["ip-address"] ?? "");
|
|
4290
|
+
if (!name) exitWithValidationError("name", CREATE_USAGE$1, ctx.formatter);
|
|
4291
|
+
if (provider === "custom" && !ip_address) exitWithValidationError("ip-address", CREATE_USAGE$1, ctx.formatter);
|
|
4292
|
+
if (provider !== "custom") {
|
|
4293
|
+
for (const option of [
|
|
4294
|
+
"credential-id",
|
|
4295
|
+
"region",
|
|
4296
|
+
"size"
|
|
4297
|
+
]) if (!ctx.options[option]) exitWithValidationError(option, CREATE_USAGE$1, ctx.formatter);
|
|
4298
|
+
}
|
|
4299
|
+
const options = {
|
|
4300
|
+
name,
|
|
4301
|
+
provider,
|
|
4302
|
+
type: String(ctx.options.type ?? "app"),
|
|
4303
|
+
ubuntu_version: String(ctx.options["ubuntu-version"] ?? "26.04"),
|
|
4304
|
+
php_version: String(ctx.options["php-version"] ?? "php84")
|
|
4305
|
+
};
|
|
4306
|
+
if (ctx.options.database) options.database_type = String(ctx.options.database);
|
|
4307
|
+
if (ctx.options["credential-id"]) options.credential_id = String(ctx.options["credential-id"]);
|
|
4308
|
+
const tags = ctx.options.tag;
|
|
4309
|
+
if (tags) options.tags = (Array.isArray(tags) ? tags : [tags]).map(String);
|
|
4310
|
+
if (provider === "custom") {
|
|
4311
|
+
options.custom = {
|
|
4312
|
+
ip_address,
|
|
4313
|
+
ssh_port: String(ctx.options["ssh-port"] ?? "22")
|
|
4314
|
+
};
|
|
4315
|
+
if (ctx.options["private-ip"]) options.custom.private_ip_address = String(ctx.options["private-ip"]);
|
|
4316
|
+
} else options[provider] = {
|
|
4317
|
+
region_id: String(ctx.options.region),
|
|
4318
|
+
size_id: String(ctx.options.size)
|
|
4319
|
+
};
|
|
4320
|
+
await runCommand(async () => {
|
|
4321
|
+
const token = ctx.getToken();
|
|
4322
|
+
const execCtx = ctx.createExecutorContext(token);
|
|
4323
|
+
const result = await createServer(options, execCtx);
|
|
4324
|
+
const format = String(ctx.options.format ?? ctx.options.f ?? "human");
|
|
4325
|
+
if (format === "table" || format === "json") {
|
|
4326
|
+
ctx.formatter.output(result.data);
|
|
4327
|
+
return;
|
|
4328
|
+
}
|
|
4329
|
+
ctx.formatter.success(`Server ${result.data.name} created (ID ${result.data.id}).`);
|
|
4330
|
+
if (provider === "custom") ctx.formatter.info(`Run the provisioning command from the Forge server page as root on ${ip_address}.`);
|
|
4331
|
+
}, ctx.formatter);
|
|
4332
|
+
}
|
|
4093
4333
|
/**
|
|
4094
4334
|
* Reboot a server by ID or name.
|
|
4095
4335
|
*/
|
|
@@ -4112,9 +4352,10 @@ var handleServersCommand = createCommandRouter({
|
|
|
4112
4352
|
list: serversList,
|
|
4113
4353
|
ls: serversList,
|
|
4114
4354
|
get: [serversGet, "args"],
|
|
4355
|
+
create: serversCreate,
|
|
4115
4356
|
reboot: [serversReboot, "args"]
|
|
4116
4357
|
},
|
|
4117
|
-
writeSubcommands: ["reboot"]
|
|
4358
|
+
writeSubcommands: ["create", "reboot"]
|
|
4118
4359
|
});
|
|
4119
4360
|
//#endregion
|
|
4120
4361
|
//#region src/commands/servers/help.ts
|
|
@@ -4150,6 +4391,35 @@ ${colors.bold("OPTIONS:")}
|
|
|
4150
4391
|
${colors.bold("EXAMPLES:")}
|
|
4151
4392
|
forge servers get 123
|
|
4152
4393
|
forge servers get 123 --format json
|
|
4394
|
+
`);
|
|
4395
|
+
else if (subcommand === "create") console.log(`
|
|
4396
|
+
${colors.bold("forge servers create")} - Create a server
|
|
4397
|
+
|
|
4398
|
+
${colors.bold("USAGE:")}
|
|
4399
|
+
forge servers create --name <name> --ip-address <ip> [options]
|
|
4400
|
+
|
|
4401
|
+
${colors.bold("OPTIONS:")}
|
|
4402
|
+
--name <name> Server name (required, 30 characters max)
|
|
4403
|
+
--provider <provider> Server provider (default: custom)
|
|
4404
|
+
--ip-address <ip> Public IP address (required for custom)
|
|
4405
|
+
--private-ip <ip> Private IP address (custom only)
|
|
4406
|
+
--ssh-port <port> SSH port (custom only, default: 22)
|
|
4407
|
+
--region <id> Region ID (cloud providers)
|
|
4408
|
+
--size <id> Size ID (cloud providers)
|
|
4409
|
+
--credential-id <id> Server provider credential ID (cloud providers)
|
|
4410
|
+
--type <type> Server type: app, web, loadbalancer, database... (default: app)
|
|
4411
|
+
--ubuntu-version <ver> Ubuntu version (default: 26.04)
|
|
4412
|
+
--php-version <ver> PHP version (default: php84)
|
|
4413
|
+
--database <type> Database type, e.g. mariadb1011
|
|
4414
|
+
--tag <tag> Tag, repeatable
|
|
4415
|
+
-f, --format <fmt> Output format: json, human, table
|
|
4416
|
+
|
|
4417
|
+
A Custom VPS must run a fresh Ubuntu with root SSH access. After the
|
|
4418
|
+
command, run the provisioning command from the Forge server page as root.
|
|
4419
|
+
|
|
4420
|
+
${colors.bold("EXAMPLES:")}
|
|
4421
|
+
forge servers create --name web-1 --ip-address 203.0.113.10 --php-version php84 --database mariadb1011
|
|
4422
|
+
forge servers create --name worker-1 --provider hetzner --credential-id 42 --region fsn1 --size cpx31
|
|
4153
4423
|
`);
|
|
4154
4424
|
else if (subcommand === "reboot") console.log(`
|
|
4155
4425
|
${colors.bold("forge servers reboot")} - Reboot a server
|
|
@@ -4175,6 +4445,7 @@ ${colors.bold("ALIASES:")}
|
|
|
4175
4445
|
${colors.bold("SUBCOMMANDS:")}
|
|
4176
4446
|
list, ls List all servers
|
|
4177
4447
|
get <id> Get server details
|
|
4448
|
+
create Create a server (requires --name, and --ip-address for custom)
|
|
4178
4449
|
reboot <id> Reboot a server
|
|
4179
4450
|
|
|
4180
4451
|
${colors.bold("OPTIONS:")}
|
|
@@ -4995,6 +5266,183 @@ Run ${colors.cyan("forge ssh-keys <subcommand> --help")} for details.
|
|
|
4995
5266
|
`);
|
|
4996
5267
|
}
|
|
4997
5268
|
//#endregion
|
|
5269
|
+
//#region src/commands/storage-providers/handlers.ts
|
|
5270
|
+
var FIELDS = [
|
|
5271
|
+
"id",
|
|
5272
|
+
"name",
|
|
5273
|
+
"provider",
|
|
5274
|
+
"provider_name",
|
|
5275
|
+
"endpoint",
|
|
5276
|
+
"region",
|
|
5277
|
+
"bucket",
|
|
5278
|
+
"directory",
|
|
5279
|
+
"in_use"
|
|
5280
|
+
];
|
|
5281
|
+
var CREATE_USAGE = "forge storage-providers create --name <name> --provider <type> [--endpoint <url>] [--region <region>] [--bucket <bucket>] [--access-key <id>] [--secret-key-file <path|->]";
|
|
5282
|
+
async function storageProvidersList(ctx) {
|
|
5283
|
+
const provider = typeof ctx.options.provider === "string" ? ctx.options.provider : void 0;
|
|
5284
|
+
await runCommand(async () => {
|
|
5285
|
+
const token = ctx.getToken();
|
|
5286
|
+
const execCtx = ctx.createExecutorContext(token);
|
|
5287
|
+
const result = await listStorageProviders(provider ? { provider } : {}, execCtx);
|
|
5288
|
+
ctx.formatter.outputList(result.data, [
|
|
5289
|
+
"id",
|
|
5290
|
+
"name",
|
|
5291
|
+
"provider",
|
|
5292
|
+
"bucket",
|
|
5293
|
+
"in_use"
|
|
5294
|
+
], "No storage providers found.", (p) => `${String(p.id).padEnd(8)} ${p.name.padEnd(24)} ${p.provider.padEnd(10)} ${p.bucket ?? "—"}${p.in_use ? " (in use)" : ""}`);
|
|
5295
|
+
}, ctx.formatter);
|
|
5296
|
+
}
|
|
5297
|
+
async function storageProvidersGet(args, ctx) {
|
|
5298
|
+
const [id] = args;
|
|
5299
|
+
if (!id) exitWithValidationError("id", "forge storage-providers get <id>", ctx.formatter);
|
|
5300
|
+
await runCommand(async () => {
|
|
5301
|
+
const token = ctx.getToken();
|
|
5302
|
+
const execCtx = ctx.createExecutorContext(token);
|
|
5303
|
+
const result = await getStorageProvider({ id }, execCtx);
|
|
5304
|
+
ctx.formatter.outputOne(result.data, FIELDS);
|
|
5305
|
+
}, ctx.formatter);
|
|
5306
|
+
}
|
|
5307
|
+
/**
|
|
5308
|
+
* Read an optional string option. A flag given without a value is a validation error.
|
|
5309
|
+
*/
|
|
5310
|
+
function stringOption(ctx, name) {
|
|
5311
|
+
const value = ctx.options[name];
|
|
5312
|
+
if (value === void 0) return void 0;
|
|
5313
|
+
if (typeof value !== "string" || !value) exitWithError(new ValidationError(`--${name} requires a value`, name, [`Usage: ${CREATE_USAGE}`]), ctx.formatter);
|
|
5314
|
+
return value;
|
|
5315
|
+
}
|
|
5316
|
+
async function storageProvidersCreate(ctx) {
|
|
5317
|
+
const name = ctx.options.name;
|
|
5318
|
+
const provider = ctx.options.provider;
|
|
5319
|
+
if (typeof name !== "string" || !name) exitWithValidationError("name", CREATE_USAGE, ctx.formatter);
|
|
5320
|
+
if (typeof provider !== "string" || !provider) exitWithValidationError("provider", CREATE_USAGE, ctx.formatter);
|
|
5321
|
+
if (!STORAGE_PROVIDER_TYPES.includes(provider)) exitWithError(new ValidationError(`Invalid provider "${provider}": use ${STORAGE_PROVIDER_TYPES.join(", ")}`, "provider"), ctx.formatter);
|
|
5322
|
+
const endpoint = stringOption(ctx, "endpoint");
|
|
5323
|
+
const region = stringOption(ctx, "region");
|
|
5324
|
+
const bucket = stringOption(ctx, "bucket");
|
|
5325
|
+
const directory = stringOption(ctx, "directory");
|
|
5326
|
+
const accessKey = stringOption(ctx, "access-key");
|
|
5327
|
+
const secretKey = ctx.options["secret-key-file"] === void 0 ? void 0 : readContentFile(ctx.options["secret-key-file"], "secret-key-file", {
|
|
5328
|
+
label: "secret key",
|
|
5329
|
+
usage: CREATE_USAGE,
|
|
5330
|
+
formatter: ctx.formatter
|
|
5331
|
+
}).replace(/\r?\n$/, "");
|
|
5332
|
+
await runCommand(async () => {
|
|
5333
|
+
const token = ctx.getToken();
|
|
5334
|
+
const execCtx = ctx.createExecutorContext(token);
|
|
5335
|
+
const result = await createStorageProvider({
|
|
5336
|
+
name,
|
|
5337
|
+
provider,
|
|
5338
|
+
...endpoint !== void 0 ? { endpoint } : {},
|
|
5339
|
+
...region !== void 0 ? { region } : {},
|
|
5340
|
+
...bucket !== void 0 ? { bucket } : {},
|
|
5341
|
+
...directory !== void 0 ? { directory } : {},
|
|
5342
|
+
...accessKey !== void 0 ? { access_key: accessKey } : {},
|
|
5343
|
+
...secretKey !== void 0 ? { secret_key: secretKey } : {},
|
|
5344
|
+
...ctx.options["assume-role"] === true ? { assume_role: true } : {}
|
|
5345
|
+
}, execCtx);
|
|
5346
|
+
ctx.formatter.outputOne(result.data, FIELDS);
|
|
5347
|
+
}, ctx.formatter);
|
|
5348
|
+
}
|
|
5349
|
+
async function storageProvidersDelete(args, ctx) {
|
|
5350
|
+
const [id] = args;
|
|
5351
|
+
if (!id) exitWithValidationError("id", "forge storage-providers delete <id>", ctx.formatter);
|
|
5352
|
+
await runCommand(async () => {
|
|
5353
|
+
const token = ctx.getToken();
|
|
5354
|
+
const execCtx = ctx.createExecutorContext(token);
|
|
5355
|
+
await deleteStorageProvider({ id }, execCtx);
|
|
5356
|
+
ctx.formatter.success(`Storage provider ${id} deleted.`);
|
|
5357
|
+
}, ctx.formatter);
|
|
5358
|
+
}
|
|
5359
|
+
//#endregion
|
|
5360
|
+
//#region src/commands/storage-providers/command.ts
|
|
5361
|
+
var handleStorageProvidersCommand = createCommandRouter({
|
|
5362
|
+
resource: "storage-providers",
|
|
5363
|
+
handlers: {
|
|
5364
|
+
list: storageProvidersList,
|
|
5365
|
+
ls: storageProvidersList,
|
|
5366
|
+
get: [storageProvidersGet, "args"],
|
|
5367
|
+
create: storageProvidersCreate,
|
|
5368
|
+
delete: [storageProvidersDelete, "args"]
|
|
5369
|
+
},
|
|
5370
|
+
writeSubcommands: ["create", "delete"]
|
|
5371
|
+
});
|
|
5372
|
+
//#endregion
|
|
5373
|
+
//#region src/commands/storage-providers/help.ts
|
|
5374
|
+
function showStorageProvidersHelp(subcommand) {
|
|
5375
|
+
if (subcommand === "list" || subcommand === "ls") console.log(`
|
|
5376
|
+
${colors.bold("forge storage-providers list")} - List the organization's storage providers
|
|
5377
|
+
|
|
5378
|
+
${colors.bold("USAGE:")}
|
|
5379
|
+
forge storage-providers list [options]
|
|
5380
|
+
|
|
5381
|
+
${colors.bold("OPTIONS:")}
|
|
5382
|
+
--provider <type> Only list one provider type (s3, spaces, hetzner, ovh, scaleway, custom)
|
|
5383
|
+
-f, --format <fmt> Output format: json, human, table
|
|
5384
|
+
`);
|
|
5385
|
+
else if (subcommand === "get") console.log(`
|
|
5386
|
+
${colors.bold("forge storage-providers get")} - Get storage provider details
|
|
5387
|
+
|
|
5388
|
+
${colors.bold("USAGE:")}
|
|
5389
|
+
forge storage-providers get <id> [options]
|
|
5390
|
+
|
|
5391
|
+
Forge never returns the access key or the secret key.
|
|
5392
|
+
`);
|
|
5393
|
+
else if (subcommand === "create") console.log(`
|
|
5394
|
+
${colors.bold("forge storage-providers create")} - Create a storage provider for database backups
|
|
5395
|
+
|
|
5396
|
+
${colors.bold("USAGE:")}
|
|
5397
|
+
forge storage-providers create --name <name> --provider <type> [options]
|
|
5398
|
+
|
|
5399
|
+
${colors.bold("OPTIONS:")}
|
|
5400
|
+
--name <name> Provider name (required)
|
|
5401
|
+
--provider <type> s3, spaces, hetzner, ovh, scaleway or custom (S3 compatible) (required)
|
|
5402
|
+
--endpoint <url> S3 endpoint URL (custom providers)
|
|
5403
|
+
--region <region> Bucket region
|
|
5404
|
+
--bucket <bucket> Default bucket
|
|
5405
|
+
--directory <dir> Default directory in the bucket
|
|
5406
|
+
--access-key <id> Access key ID
|
|
5407
|
+
--secret-key-file <path> Read the secret key from a file, or from stdin with -
|
|
5408
|
+
--assume-role Use the EC2 instance role instead of keys (s3)
|
|
5409
|
+
-f, --format <fmt> Output format: json, human, table
|
|
5410
|
+
|
|
5411
|
+
The secret key is not accepted as an option value, so it does not show in the
|
|
5412
|
+
shell history or the process list. One trailing newline is removed.
|
|
5413
|
+
|
|
5414
|
+
${colors.bold("EXAMPLE:")}
|
|
5415
|
+
printf '%s' "$S3_SECRET_KEY" | forge storage-providers create --name Backups \\
|
|
5416
|
+
--provider custom --endpoint https://s3.example.com --region eu-west-1 \\
|
|
5417
|
+
--bucket my-bucket --access-key "$S3_ACCESS_KEY" --secret-key-file - --format json
|
|
5418
|
+
`);
|
|
5419
|
+
else if (subcommand === "delete") console.log(`
|
|
5420
|
+
${colors.bold("forge storage-providers delete")} - Delete a storage provider
|
|
5421
|
+
|
|
5422
|
+
${colors.bold("USAGE:")}
|
|
5423
|
+
forge storage-providers delete <id> [options]
|
|
5424
|
+
|
|
5425
|
+
Forge refuses to delete a provider that a backup configuration uses.
|
|
5426
|
+
`);
|
|
5427
|
+
else console.log(`
|
|
5428
|
+
${colors.bold("forge storage-providers")} - Manage the storage providers of database backups
|
|
5429
|
+
|
|
5430
|
+
${colors.bold("USAGE:")}
|
|
5431
|
+
forge storage-providers <subcommand> [options]
|
|
5432
|
+
|
|
5433
|
+
${colors.bold("SUBCOMMANDS:")}
|
|
5434
|
+
list, ls List storage providers
|
|
5435
|
+
get <id> Get storage provider details
|
|
5436
|
+
create Create a storage provider
|
|
5437
|
+
delete <id> Delete a storage provider
|
|
5438
|
+
|
|
5439
|
+
${colors.bold("OPTIONS:")}
|
|
5440
|
+
-f, --format <fmt> Output format: json, human, table
|
|
5441
|
+
|
|
5442
|
+
Run ${colors.cyan("forge storage-providers <subcommand> --help")} for details.
|
|
5443
|
+
`);
|
|
5444
|
+
}
|
|
5445
|
+
//#endregion
|
|
4998
5446
|
//#region src/commands/user/handlers.ts
|
|
4999
5447
|
async function userGet(ctx) {
|
|
5000
5448
|
await runCommand(async () => {
|
|
@@ -5048,7 +5496,11 @@ function isOptionValue(arg) {
|
|
|
5048
5496
|
/**
|
|
5049
5497
|
* Options that accept several values. Every other option keeps the last value.
|
|
5050
5498
|
*/
|
|
5051
|
-
var REPEATABLE = /* @__PURE__ */ new Set([
|
|
5499
|
+
var REPEATABLE = /* @__PURE__ */ new Set([
|
|
5500
|
+
"server",
|
|
5501
|
+
"tag",
|
|
5502
|
+
"databases"
|
|
5503
|
+
]);
|
|
5052
5504
|
/**
|
|
5053
5505
|
* Set an option value. A repeatable option given more than once collects its string
|
|
5054
5506
|
* values into an array (`--server 1 --server 2` → `["1", "2"]`).
|
|
@@ -5102,7 +5554,7 @@ function isVersionRequest({ command, options }) {
|
|
|
5102
5554
|
}
|
|
5103
5555
|
//#endregion
|
|
5104
5556
|
//#region src/cli.ts
|
|
5105
|
-
var VERSION = "0.
|
|
5557
|
+
var VERSION = "0.5.0";
|
|
5106
5558
|
function showHelp() {
|
|
5107
5559
|
console.log(`
|
|
5108
5560
|
${colors.bold("forge")} v${VERSION}
|
|
@@ -5119,6 +5571,7 @@ ${colors.bold("COMMANDS:")}
|
|
|
5119
5571
|
servers, s Manage servers
|
|
5120
5572
|
list, ls List all servers
|
|
5121
5573
|
get <id> Get server details
|
|
5574
|
+
create Create a server (requires --name, and --ip-address for custom)
|
|
5122
5575
|
reboot <id> Reboot a server
|
|
5123
5576
|
|
|
5124
5577
|
sites Manage sites
|
|
@@ -5172,12 +5625,19 @@ ${colors.bold("COMMANDS:")}
|
|
|
5172
5625
|
get <id> Get SSH key details (requires --server)
|
|
5173
5626
|
sync Make server SSH keys match a file (requires --file, --server|--all-servers)
|
|
5174
5627
|
|
|
5175
|
-
backups Manage backup configurations
|
|
5628
|
+
backups Manage database backup configurations
|
|
5176
5629
|
list, ls List backup configurations (requires --server)
|
|
5177
5630
|
get <id> Get backup configuration details (requires --server)
|
|
5178
|
-
create Create a backup configuration (requires --server --provider --frequency)
|
|
5631
|
+
create Create a backup configuration (requires --server --storage-provider --frequency --databases)
|
|
5632
|
+
update <id> Update a backup configuration (requires --server --storage-provider --frequency --retention --databases)
|
|
5179
5633
|
delete <id> Delete a backup configuration (requires --server)
|
|
5180
5634
|
|
|
5635
|
+
storage-providers Manage the storage providers of database backups
|
|
5636
|
+
list, ls List storage providers
|
|
5637
|
+
get <id> Get storage provider details
|
|
5638
|
+
create Create a storage provider (requires --name --provider)
|
|
5639
|
+
delete <id> Delete a storage provider
|
|
5640
|
+
|
|
5181
5641
|
commands Manage site commands
|
|
5182
5642
|
list, ls List commands (requires --server --site)
|
|
5183
5643
|
get <id> Get command details (requires --server --site)
|
|
@@ -5410,6 +5870,13 @@ async function main() {
|
|
|
5410
5870
|
}
|
|
5411
5871
|
await handleBackupsCommand(subcommand ?? "list", positional, options);
|
|
5412
5872
|
break;
|
|
5873
|
+
case "storage-providers":
|
|
5874
|
+
if (wantsHelp) {
|
|
5875
|
+
showStorageProvidersHelp(subcommand);
|
|
5876
|
+
process.exit(0);
|
|
5877
|
+
}
|
|
5878
|
+
await handleStorageProvidersCommand(subcommand ?? "list", positional, options);
|
|
5879
|
+
break;
|
|
5413
5880
|
case "commands":
|
|
5414
5881
|
if (wantsHelp) {
|
|
5415
5882
|
showCommandsHelp(subcommand);
|