ninedeploy 0.10.5 → 0.16.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/LICENSE +21 -21
- package/README.md +1 -0
- package/dist/client.d.ts +34 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +81 -22
- package/dist/client.js.map +1 -1
- package/dist/commands/backupPolicy.d.ts +29 -0
- package/dist/commands/backupPolicy.d.ts.map +1 -0
- package/dist/commands/backupPolicy.js +111 -0
- package/dist/commands/backupPolicy.js.map +1 -0
- package/dist/commands/certificates.d.ts +18 -7
- package/dist/commands/certificates.d.ts.map +1 -1
- package/dist/commands/certificates.js +121 -1
- package/dist/commands/certificates.js.map +1 -1
- package/dist/commands/databaseAccess.d.ts +37 -0
- package/dist/commands/databaseAccess.d.ts.map +1 -0
- package/dist/commands/databaseAccess.js +306 -0
- package/dist/commands/databaseAccess.js.map +1 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +28 -7
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/githubApps.d.ts +40 -0
- package/dist/commands/githubApps.d.ts.map +1 -0
- package/dist/commands/githubApps.js +278 -0
- package/dist/commands/githubApps.js.map +1 -0
- package/dist/commands/housekeeping.d.ts +7 -0
- package/dist/commands/housekeeping.d.ts.map +1 -1
- package/dist/commands/housekeeping.js +16 -0
- package/dist/commands/housekeeping.js.map +1 -1
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +17 -2
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/manage.d.ts +26 -10
- package/dist/commands/manage.d.ts.map +1 -1
- package/dist/commands/manage.js +219 -65
- package/dist/commands/manage.js.map +1 -1
- package/dist/commands/manifest.d.ts.map +1 -1
- package/dist/commands/manifest.js +9 -1
- package/dist/commands/manifest.js.map +1 -1
- package/dist/commands/misc.d.ts +4 -2
- package/dist/commands/misc.d.ts.map +1 -1
- package/dist/commands/misc.js +25 -11
- package/dist/commands/misc.js.map +1 -1
- package/dist/commands/multiNode.d.ts +55 -0
- package/dist/commands/multiNode.d.ts.map +1 -0
- package/dist/commands/multiNode.js +438 -0
- package/dist/commands/multiNode.js.map +1 -0
- package/dist/commands/notifications.d.ts +1 -1
- package/dist/commands/operations.d.ts +89 -0
- package/dist/commands/operations.d.ts.map +1 -0
- package/dist/commands/operations.js +468 -0
- package/dist/commands/operations.js.map +1 -0
- package/dist/commands/panelBackup.d.ts +28 -0
- package/dist/commands/panelBackup.d.ts.map +1 -0
- package/dist/commands/panelBackup.js +153 -0
- package/dist/commands/panelBackup.js.map +1 -0
- package/dist/commands/plugins.d.ts +1 -1
- package/dist/commands/plugins.d.ts.map +1 -1
- package/dist/commands/plugins.js +7 -3
- package/dist/commands/plugins.js.map +1 -1
- package/dist/commands/proxyConfig.d.ts +16 -0
- package/dist/commands/proxyConfig.d.ts.map +1 -0
- package/dist/commands/proxyConfig.js +122 -0
- package/dist/commands/proxyConfig.js.map +1 -0
- package/dist/commands/secretProviders.d.ts +59 -0
- package/dist/commands/secretProviders.d.ts.map +1 -0
- package/dist/commands/secretProviders.js +212 -0
- package/dist/commands/secretProviders.js.map +1 -0
- package/dist/commands/services.d.ts.map +1 -1
- package/dist/commands/services.js +3 -5
- package/dist/commands/services.js.map +1 -1
- package/dist/commands/setup.d.ts +3 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +19 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/sources.d.ts +10 -3
- package/dist/commands/sources.d.ts.map +1 -1
- package/dist/commands/sources.js +116 -18
- package/dist/commands/sources.js.map +1 -1
- package/dist/commands/templates.d.ts.map +1 -1
- package/dist/commands/templates.js +8 -1
- package/dist/commands/templates.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/index.js +427 -23
- package/dist/index.js.map +1 -1
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/lib/format.js +5 -4
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/panelBackupFile.d.ts +3 -0
- package/dist/lib/panelBackupFile.d.ts.map +1 -0
- package/dist/lib/panelBackupFile.js +71 -0
- package/dist/lib/panelBackupFile.js.map +1 -0
- package/dist/lib/serverRunner.d.ts +6 -0
- package/dist/lib/serverRunner.d.ts.map +1 -1
- package/dist/lib/serverRunner.js +18 -3
- package/dist/lib/serverRunner.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +9 -2
- package/dist/prompts.js.map +1 -1
- package/package.json +6 -5
- package/src/client.ts +137 -0
- package/src/commands/backupPolicy.ts +123 -0
- package/src/commands/branding.ts +83 -0
- package/src/commands/buildCache.ts +72 -0
- package/src/commands/certificates.ts +208 -0
- package/src/commands/communityTemplates.ts +97 -0
- package/src/commands/configCenter.ts +71 -0
- package/src/commands/configPresets.ts +215 -0
- package/src/commands/databaseAccess.ts +333 -0
- package/src/commands/demo.ts +22 -0
- package/src/commands/deploy.ts +284 -0
- package/src/commands/doctor.ts +170 -0
- package/src/commands/domains.ts +170 -0
- package/src/commands/egress.ts +135 -0
- package/src/commands/emailTemplates.ts +112 -0
- package/src/commands/firewall.ts +82 -0
- package/src/commands/githubApps.ts +292 -0
- package/src/commands/housekeeping.ts +106 -0
- package/src/commands/login.ts +58 -0
- package/src/commands/logs.ts +91 -0
- package/src/commands/manage.ts +714 -0
- package/src/commands/manifest.ts +314 -0
- package/src/commands/metrics.ts +69 -0
- package/src/commands/misc.ts +307 -0
- package/src/commands/multiNode.ts +442 -0
- package/src/commands/notifications.ts +174 -0
- package/src/commands/operations.ts +519 -0
- package/src/commands/panelBackup.ts +167 -0
- package/src/commands/pgbouncer.ts +65 -0
- package/src/commands/plugins.ts +123 -0
- package/src/commands/proxyConfig.ts +117 -0
- package/src/commands/secretProviders.ts +228 -0
- package/src/commands/server.ts +126 -0
- package/src/commands/services.ts +249 -0
- package/src/commands/setup.ts +94 -0
- package/src/commands/sources.ts +317 -0
- package/src/commands/sso.ts +106 -0
- package/src/commands/templates.ts +158 -0
- package/src/commands/token.ts +66 -0
- package/src/commands/webhooks.ts +93 -0
- package/src/commands/workspaces.ts +63 -0
- package/src/config.ts +53 -0
- package/src/index.ts +1273 -0
- package/src/lib/format.ts +182 -0
- package/src/lib/panelBackupFile.ts +67 -0
- package/src/lib/serverRunner.ts +223 -0
- package/src/prompts.ts +238 -0
package/dist/index.js
CHANGED
|
@@ -8,22 +8,28 @@ import { loginAction } from './commands/login.js';
|
|
|
8
8
|
import { setupAction } from './commands/setup.js';
|
|
9
9
|
import { servicesCompose, servicesCreate, servicesDelete, servicesDeploy, servicesExport, servicesGet, servicesInspect, servicesLifecycle, servicesList, servicesLogs, servicesStickyAction, } from './commands/services.js';
|
|
10
10
|
import { dbCreate, dbList, deploysCancel, deploysList, deploysQueue, deploysRemove, deploysRollback, systemDashboard, systemInfo, systemRotateKeys, systemUpdateCheck, tplDeploy, tplList, tokenCreate, tokenList, } from './commands/misc.js';
|
|
11
|
-
import { activityList, alertsCreate, alertsList, alertsRemove, backupsCreate, backupsDrill, backupsDrills, backupsList, backupsRestore, deploysWatch, domainsAcceptTransfer, domainsAdd, domainsCancelTransfer, domainsList, domainsPreviewTransfer, domainsRemove, domainsTransfer, envList, envRemove, envSet, networksCreate, networksList, networksRemove, sessionsList, sessionsRevoke, systemExport, systemImport, usersList, usersResetLink, volumesList, volumesRemove, } from './commands/manage.js';
|
|
11
|
+
import { activityList, alertsCreate, alertsList, alertsRemove, backupsCreate, backupsDrill, backupsDrills, backupsList, backupsRestore, deploysWatch, domainsAcceptTransfer, domainsAdd, domainsCancelTransfer, domainsList, domainsPreviewTransfer, domainsRemove, domainsTransfer, domainsVerify, envList, envRemove, envSet, networksCreate, networksList, networksRemove, sessionsList, sessionsRevoke, systemExport, systemImport, usersList, usersResetLink, volumesList, volumesRemove, } from './commands/manage.js';
|
|
12
12
|
import { notificationsCreateFcm as _notifCreateFcm, notificationsCreateWebhook as _notifCreateWebhook, notificationsList as _notifList, notificationsRemove as _notifRemove, notificationsTest as _notifTest, } from './commands/notifications.js';
|
|
13
13
|
import { databasePgbouncer } from './commands/pgbouncer.js';
|
|
14
|
+
import { backupPolicyGet, backupPolicySet } from './commands/backupPolicy.js';
|
|
14
15
|
import { logsSearch } from './commands/logs.js';
|
|
15
16
|
import { emailTemplatesList, emailTemplatesPreview, emailTemplatesReset, emailTemplatesSet, } from './commands/emailTemplates.js';
|
|
16
|
-
import { certificatesExpiring, certificatesList, } from './commands/certificates.js';
|
|
17
|
+
import { certificatesCustomDelete, certificatesCustomList, certificatesCustomReplace, certificatesCustomUpload, certificatesExpiring, certificatesList, } from './commands/certificates.js';
|
|
18
|
+
import { databaseImport, databaseImports, databasePublicAccess, } from './commands/databaseAccess.js';
|
|
19
|
+
import { proxyConfigClear, proxyConfigGet, proxyConfigSet, proxyConfigValidate } from './commands/proxyConfig.js';
|
|
20
|
+
import { secretProvidersDelete, secretProvidersList, secretProvidersSetAws, secretProvidersSetVault, secretProvidersTest, } from './commands/secretProviders.js';
|
|
21
|
+
import { accessGrantsAdd, accessGrantsList, accessGrantsRemove, accessGrantsUpdate, accessMe, terminalOpen, terminalsKill, terminalsList, terminalsShow, trafficService, trafficSettings, trafficSummary, } from './commands/operations.js';
|
|
17
22
|
import { communityTemplatesImport, communityTemplatesList, communityTemplatesRemove, } from './commands/communityTemplates.js';
|
|
18
23
|
import { pluginsList, pluginsMarketplace, pluginsMarketplaceRefresh, pluginsInstall, pluginsEnable, pluginsDisable, pluginsUninstall, pluginsInspect, pluginsReload, } from './commands/plugins.js';
|
|
19
24
|
import { configCenterList, configCenterGet, configCenterSet, configCenterDelete, } from './commands/configCenter.js';
|
|
20
25
|
import { demoSeed } from './commands/demo.js';
|
|
21
26
|
import { workspacesList, workspacesGet, workspacesCreate, workspacesDelete, } from './commands/workspaces.js';
|
|
22
|
-
import { housekeepingPrune, imagesList, imagesPrune } from './commands/housekeeping.js';
|
|
27
|
+
import { housekeepingPrune, imagesList, imagesPrune, positiveIntOption } from './commands/housekeeping.js';
|
|
23
28
|
import { serverStartAction, serverStopAction, serverStatusAction, serverLogsAction, } from './commands/server.js';
|
|
24
29
|
import { doctorAction } from './commands/doctor.js';
|
|
25
30
|
import { firewallStatus, firewallToggle, firewallAddRule, firewallDeleteRule, firewallApplyRecommended, } from './commands/firewall.js';
|
|
26
31
|
import { sourcesAdd, sourcesKeygen, sourcesList, sourcesRemove, sourcesShow, sourcesTest, } from './commands/sources.js';
|
|
32
|
+
import { githubAppAddManual, githubAppList, githubAppRemove, githubAppRotateKey, githubAppShow, githubAppSync, serviceGithub, } from './commands/githubApps.js';
|
|
27
33
|
import { webhooksAdd, webhooksList, webhooksRemove, webhooksShow, } from './commands/webhooks.js';
|
|
28
34
|
import { deployFromGithub } from './commands/deploy.js';
|
|
29
35
|
import { manifestApply, manifestInit, manifestShow, manifestValidate } from './commands/manifest.js';
|
|
@@ -35,6 +41,10 @@ import { buildCacheStatsAction, } from './commands/buildCache.js';
|
|
|
35
41
|
import { brandingGetAction, brandingSetAction, } from './commands/branding.js';
|
|
36
42
|
import { egressClearAction, egressListAction, egressSetAction, } from './commands/egress.js';
|
|
37
43
|
import { ssoAddAction, ssoListAction, ssoRemoveAction, } from './commands/sso.js';
|
|
44
|
+
import { panelBackupDecryptAction, panelBackupListAction, panelBackupNowAction, panelBackupSetAction, panelBackupStatusAction, } from './commands/panelBackup.js';
|
|
45
|
+
// ── 0.16 T8 surfaces ──
|
|
46
|
+
import { serversList, serversRoles, servicesPlacement, servicesSwarm, servicesTransfers, sourcesAllowOnNodes, sourcesHostKeys, sourcesPinHostKeys, sourcesResetHostKeys, swarmInit, swarmJoin, swarmLeave, swarmSetEnabled, swarmStatus, volumesCreate, } from './commands/multiNode.js';
|
|
47
|
+
// ── end 0.16 T8 ──
|
|
38
48
|
const program = new Command();
|
|
39
49
|
program
|
|
40
50
|
.name('ninedeploy')
|
|
@@ -46,27 +56,51 @@ program
|
|
|
46
56
|
program
|
|
47
57
|
.command('init')
|
|
48
58
|
.description('Initialize local instance and setup admin user (alias for setup)')
|
|
49
|
-
.
|
|
59
|
+
.option('--setup-token <token>', 'One-time setup token printed by the installer (or NINEDEPLOY_SETUP_TOKEN)')
|
|
60
|
+
.action((opts) => setupAction(opts));
|
|
50
61
|
program
|
|
51
62
|
.command('setup')
|
|
52
63
|
.description('Create the first admin user on a fresh instance')
|
|
53
|
-
.
|
|
64
|
+
.option('--setup-token <token>', 'One-time setup token printed by the installer (or NINEDEPLOY_SETUP_TOKEN)')
|
|
65
|
+
.action((opts) => setupAction(opts));
|
|
54
66
|
program
|
|
55
67
|
.command('login')
|
|
56
68
|
.description('Authenticate against a NineDeploy server')
|
|
57
69
|
.action(() => loginAction());
|
|
58
70
|
program
|
|
59
71
|
.command('logout')
|
|
60
|
-
.description('
|
|
72
|
+
.description('Revoke this CLI session and clear stored credentials')
|
|
61
73
|
.action(async () => {
|
|
62
74
|
const cfg = loadConfig();
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
// r551: revoke ONLY this CLI's session. `POST /auth/logout` bumps the
|
|
76
|
+
// user's tokenVersion and revokes every session — signing out of the CLI
|
|
77
|
+
// used to sign the user out of every browser too. It is also on the
|
|
78
|
+
// no-refresh list, so with an expired access token it failed silently and
|
|
79
|
+
// the long-lived refresh token stayed live server-side. The sessions list
|
|
80
|
+
// is refresh-aware and flags the caller's own row (`current`), which
|
|
81
|
+
// `DELETE /auth/sessions/:id` then revokes. Best-effort: local credentials
|
|
82
|
+
// are always cleared, and the outcome is printed either way.
|
|
83
|
+
let outcome = 'No stored credentials — nothing to revoke on the server.';
|
|
84
|
+
if (cfg.token || cfg.refreshToken) {
|
|
85
|
+
try {
|
|
86
|
+
const client = getClient();
|
|
87
|
+
const current = (await client.auth.sessions.list()).find((s) => s.current);
|
|
88
|
+
if (current) {
|
|
89
|
+
await client.auth.sessions.revoke(current.id);
|
|
90
|
+
outcome = `Revoked this CLI session (#${current.id}) on ${cfg.baseUrl}; other sessions stay signed in.`;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
outcome = 'The stored credential is not a login session (API token?) — nothing was revoked server-side; delete the token from the dashboard if it should stop working.';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
outcome = `Could not revoke the session on the server (${msg}). Revoke it from the dashboard or \`ninedeploy sessions revoke <id>\` after logging in again.`;
|
|
99
|
+
}
|
|
67
100
|
}
|
|
68
101
|
saveConfig({ baseUrl: cfg.baseUrl });
|
|
69
|
-
console.log(' ✓ Signed out.');
|
|
102
|
+
console.log(' ✓ Signed out (local credentials cleared).');
|
|
103
|
+
console.log(` ${outcome}`);
|
|
70
104
|
});
|
|
71
105
|
program
|
|
72
106
|
.command('whoami')
|
|
@@ -112,7 +146,7 @@ program
|
|
|
112
146
|
}
|
|
113
147
|
});
|
|
114
148
|
// ── Services ──────────────────────────────────────────────────────────────
|
|
115
|
-
const services = program.command('services').description('Manage services');
|
|
149
|
+
const services = program.command('services').alias('service').description('Manage services');
|
|
116
150
|
services.command('list').description('List all services').action(() => servicesList(getClient()));
|
|
117
151
|
services.command('create').description('Create a service (interactive wizard)').action(() => servicesCreate(getClient()));
|
|
118
152
|
services.command('get <id>').description('Show service details').action((id) => servicesGet(getClient(), id));
|
|
@@ -130,16 +164,97 @@ services.command('sticky <id>')
|
|
|
130
164
|
.option('--enable', 'Route every request to the same backend container')
|
|
131
165
|
.option('--disable', 'Remove the sticky-cookie middleware')
|
|
132
166
|
.action((id, opts) => servicesStickyAction(getClient(), id, opts));
|
|
167
|
+
// 0.13: the service's GitHub App link (show, feedback, migrate/finalize/revert).
|
|
168
|
+
services.command('github <id>')
|
|
169
|
+
.description('Show or change the GitHub App link (commit statuses, PR comments, migrate, finalize, unlink)')
|
|
170
|
+
.option('--status <onOff>', 'Commit statuses for deploys: on | off')
|
|
171
|
+
.option('--pr-comment <onOff>', 'PR preview comments: on | off')
|
|
172
|
+
.option('--migrate <sourceId>', 'Link to the GitHub App installation behind this gh-app source')
|
|
173
|
+
.option('--finalize', 'Detach the previous source and switch the per-service webhooks off')
|
|
174
|
+
.option('--unlink', 'Revert: restore the previous source and webhooks, remove the link')
|
|
175
|
+
.action((id, opts) => serviceGithub(getClient(), id, opts));
|
|
176
|
+
// ── 0.16 T8: build placement, image transfers and Swarm tasks (multi-node) ──
|
|
177
|
+
/* v8 ignore start -- the FakeCommand in test/index.test.ts records but never invokes the action;
|
|
178
|
+
* the implementations are exercised by test/multiNode.test.ts. */
|
|
179
|
+
services.command('placement <id>')
|
|
180
|
+
.description('Show or (operator) change where the image is built and which orchestrator runs it')
|
|
181
|
+
.option('--build-on <where>', 'target (where it runs) | panel | server')
|
|
182
|
+
.option('--build-server <id>', 'The build server for --build-on server (none clears it)')
|
|
183
|
+
.option('--push-registry <sourceId>', 'Ship the image through this registry credential (none = stream relay)')
|
|
184
|
+
.option('--push-repo <repo>', 'Repository for --push-registry, e.g. team/app')
|
|
185
|
+
.option('--orchestrator <name>', 'container | swarm')
|
|
186
|
+
.action((id, opts) => servicesPlacement(getClient(), id, opts));
|
|
187
|
+
services.command('transfers <id>')
|
|
188
|
+
.description('Image transfers of a service, newest first')
|
|
189
|
+
.option('--limit <n>', 'How many (1-100, default 20)')
|
|
190
|
+
.action((id, opts) => servicesTransfers(getClient(), id, opts));
|
|
191
|
+
services.command('swarm <id>').description('Swarm tasks of a service').action((id) => servicesSwarm(getClient(), id));
|
|
192
|
+
/* v8 ignore stop */
|
|
133
193
|
// ── Databases ────────────────────────────────────────────────────────────
|
|
134
194
|
const databases = program.command('databases').description('Manage databases');
|
|
135
195
|
databases.command('list').description('List all databases').action(() => dbList(getClient()));
|
|
136
|
-
databases.command('create')
|
|
196
|
+
databases.command('create')
|
|
197
|
+
.description('Create a database (interactive)')
|
|
198
|
+
.option('--server <id>', 'Run it on this node (operator; fixed for its life)')
|
|
199
|
+
.action((opts) => dbCreate(getClient(), opts));
|
|
137
200
|
// ── PgBouncer sidecar (G-32) ─────────────────────────────────────────────
|
|
138
201
|
databases
|
|
139
202
|
.command('pgbouncer <dbId> <action>')
|
|
140
203
|
.description('Manage the PgBouncer sidecar: enable | disable | status (postgres only)')
|
|
141
204
|
.option('--port <port>', 'Override the listen port (enable only)', (v) => Number(v))
|
|
142
205
|
.action((dbId, action, opts) => databasePgbouncer(getClient(), dbId, action, opts));
|
|
206
|
+
// ── Per-database backup policy (0.12) ────────────────────────────────────
|
|
207
|
+
const backupPolicyCmd = databases
|
|
208
|
+
.command('backup-policy')
|
|
209
|
+
.description('Per-database backup schedule, retention and destination');
|
|
210
|
+
backupPolicyCmd
|
|
211
|
+
.command('get <dbId>')
|
|
212
|
+
.description('Show the backup policy (or the built-in daily schedule)')
|
|
213
|
+
.action((dbId) => backupPolicyGet(getClient(), dbId));
|
|
214
|
+
backupPolicyCmd
|
|
215
|
+
.command('set <dbId>')
|
|
216
|
+
.description('Save the backup policy; unset flags keep their current value')
|
|
217
|
+
.option('--cron <expr>', '5-field cron (minute hour day month weekday), server local time')
|
|
218
|
+
.option('--preset <name>', 'daily | 6h | weekly')
|
|
219
|
+
.option('--keep <n>', 'Scheduled dumps kept locally (1-365)')
|
|
220
|
+
.option('--keep-remote <n>', 'Remote copies kept (1-365; default: same as --keep)')
|
|
221
|
+
.option('--destination <dest>', 'Backup destination id, "active" or "local"')
|
|
222
|
+
.option('--enable', 'Turn scheduled backups on')
|
|
223
|
+
.option('--disable', 'Turn scheduled backups off for this database')
|
|
224
|
+
.action((dbId, opts) => backupPolicySet(getClient(), dbId, opts));
|
|
225
|
+
// ── Public access and dump import (0.14) ────────────────────────────────
|
|
226
|
+
/* v8 ignore start -- the FakeCommand in test/index.test.ts records but never invokes the action;
|
|
227
|
+
* the implementations are exercised by test/databaseAccess.test.ts. */
|
|
228
|
+
databases
|
|
229
|
+
.command('public-access <id>')
|
|
230
|
+
.description('Show, enable/update (--enable) or disable (--disable) public access through a TCP sidecar (operator)')
|
|
231
|
+
.option('--enable', 'Enable or update public access; unset flags keep their current value')
|
|
232
|
+
.option('--disable', 'Remove the sidecar (the settings are kept)')
|
|
233
|
+
.option('--port <port>', 'Host port, 1024-65535')
|
|
234
|
+
.option('--allow <cidr...>', 'Allowed source addresses or CIDRs (replaces the allow-list)')
|
|
235
|
+
.option('--tls <mode>', 'none | terminate (not for mysql/mariadb)')
|
|
236
|
+
.option('--tls-host <host>', 'Hostname clients connect to (and the TLS name in terminate mode)')
|
|
237
|
+
.action((id, opts) => databasePublicAccess(getClient(), id, opts));
|
|
238
|
+
databases
|
|
239
|
+
.command('import <id>')
|
|
240
|
+
.description('Import a dump from a local file (chunked, resumable) or a backup destination object (operator)')
|
|
241
|
+
.option('--file <path>', 'Dump file to upload')
|
|
242
|
+
.option('--from-s3 <destinationId>', 'Backup destination to read the object from (operator)')
|
|
243
|
+
.option('--key <key>', 'Object key inside the destination prefix (with --from-s3)')
|
|
244
|
+
.option('--clean', 'postgres custom format: drop objects before recreating them')
|
|
245
|
+
.option('--no-single-transaction', 'postgres: do not wrap the restore in one transaction')
|
|
246
|
+
.option('--drop', 'mongo: drop each collection before restoring it')
|
|
247
|
+
.option('--confirm-replace', 'redis/valkey/keydb/dragonfly: required, the RDB replaces the whole dataset')
|
|
248
|
+
.option('--no-safety-backup', 'Skip the pre-import backup (operator, or a database created in the last 10 minutes)')
|
|
249
|
+
.option('--resume <importId>', 'Continue an interrupted upload of the same file')
|
|
250
|
+
.option('--no-wait', 'Return once the import has started')
|
|
251
|
+
.action((id, opts) => databaseImport(getClient(), id, opts));
|
|
252
|
+
databases
|
|
253
|
+
.command('imports <id>')
|
|
254
|
+
.description('List a database\'s imports')
|
|
255
|
+
.option('--watch', 'Keep polling until no import is uploading, pending or running')
|
|
256
|
+
.action((id, opts) => databaseImports(getClient(), id, opts));
|
|
257
|
+
/* v8 ignore stop */
|
|
143
258
|
// ── Templates ─────────────────────────────────────────────────────────────
|
|
144
259
|
const templates = program.command('templates').description('Browse the template hub and scaffold starter manifests from it');
|
|
145
260
|
templates.command('list').description('List all templates').action(() => tplList(getClient()));
|
|
@@ -188,12 +303,21 @@ system.command('rotate-keys')
|
|
|
188
303
|
.action(() => systemRotateKeys(getClient()));
|
|
189
304
|
// ── Env vars ──────────────────────────────────────────────────────────────
|
|
190
305
|
const envCmd = program.command('env').description('Manage service environment variables');
|
|
191
|
-
|
|
306
|
+
// 0.12 `--preview`: the service's preview-only set — injected ONLY into its
|
|
307
|
+
// PR previews (overriding the parent's non-secret values there), never into
|
|
308
|
+
// the service itself. Previews still never receive the service's secrets.
|
|
309
|
+
const PREVIEW_FLAG = 'Target the preview-only set: values only this service\'s PR previews receive';
|
|
310
|
+
envCmd.command('list <serviceId>').description('List a service\'s env vars')
|
|
311
|
+
.option('--preview', PREVIEW_FLAG)
|
|
312
|
+
.action((id, opts) => envList(getClient(), id, opts));
|
|
192
313
|
envCmd.command('set <serviceId> <key> <value>')
|
|
193
314
|
.description('Create or update an env var (secret by default)')
|
|
194
315
|
.option('--public', 'Store as a plain (non-secret) value')
|
|
316
|
+
.option('--preview', PREVIEW_FLAG)
|
|
195
317
|
.action((id, key, value, opts) => envSet(getClient(), id, key, value, opts));
|
|
196
|
-
envCmd.command('rm <serviceId> <key>').description('Remove an env var by key')
|
|
318
|
+
envCmd.command('rm <serviceId> <key>').description('Remove an env var by key')
|
|
319
|
+
.option('--preview', PREVIEW_FLAG)
|
|
320
|
+
.action((id, key, opts) => envRemove(getClient(), id, key, opts));
|
|
197
321
|
// ── Domains ────────────────────────────────────────────────────────────────
|
|
198
322
|
const domainsCmd = program.command('domains').description('Manage domains & routing');
|
|
199
323
|
domainsCmd.command('list').description('List all domains').action(() => domainsList(getClient()));
|
|
@@ -203,6 +327,9 @@ domainsCmd.command('add <serviceId> <host>')
|
|
|
203
327
|
.option('--no-ssl', 'Serve over plain HTTP (no TLS)')
|
|
204
328
|
.action((id, host, opts) => domainsAdd(getClient(), id, host, opts));
|
|
205
329
|
domainsCmd.command('rm <serviceId> <domainId>').description('Remove a domain').action((svcId, domId) => domainsRemove(getClient(), svcId, domId));
|
|
330
|
+
domainsCmd.command('verify <serviceId> <domainId>')
|
|
331
|
+
.description('Check a pending domain TXT ownership challenge and bring it live')
|
|
332
|
+
.action((svcId, domId) => domainsVerify(getClient(), svcId, domId));
|
|
206
333
|
// Domain transfer (G-29) — start / preview / accept / cancel.
|
|
207
334
|
domainsCmd
|
|
208
335
|
.command('transfer <domainId>')
|
|
@@ -347,8 +474,20 @@ ssoCmd
|
|
|
347
474
|
ssoCmd.command('remove <id>').description('Remove a provider by id').action((id) => ssoRemoveAction(getClient(), id));
|
|
348
475
|
// ── Volumes ────────────────────────────────────────────────────────────────
|
|
349
476
|
const volumesCmd = program.command('volumes').description('Manage Docker volumes');
|
|
350
|
-
volumesCmd.command('list')
|
|
351
|
-
|
|
477
|
+
volumesCmd.command('list')
|
|
478
|
+
.description('List all volumes')
|
|
479
|
+
.option('--server <id>', 'A node\'s volumes instead of the panel host\'s')
|
|
480
|
+
.action((opts) => volumesList(getClient(), opts));
|
|
481
|
+
volumesCmd.command('rm <name>')
|
|
482
|
+
.description('Delete a volume (with confirmation)')
|
|
483
|
+
.option('--server <id>', 'Delete it on this node')
|
|
484
|
+
.action((name, opts) => volumesRemove(getClient(), name, opts));
|
|
485
|
+
/* v8 ignore start -- exercised by test/multiNode.test.ts. */
|
|
486
|
+
volumesCmd.command('create <name>')
|
|
487
|
+
.description('Create a managed volume (nd-svc-* / nd-db-*) on the panel host or a node')
|
|
488
|
+
.option('--server <id>', 'Create it on this node')
|
|
489
|
+
.action((name, opts) => volumesCreate(getClient(), name, opts));
|
|
490
|
+
/* v8 ignore stop */
|
|
352
491
|
// ── Networks ───────────────────────────────────────────────────────────────
|
|
353
492
|
const networksCmd = program.command('networks').description('Manage Docker networks');
|
|
354
493
|
networksCmd.command('list').description('List user-defined networks').action(() => networksList(getClient()));
|
|
@@ -375,7 +514,7 @@ backupsCmd
|
|
|
375
514
|
const alertsCmd = program.command('alerts').description('Manage alert rules');
|
|
376
515
|
alertsCmd.command('list').description('List alert rules').action(() => alertsList(getClient()));
|
|
377
516
|
alertsCmd.command('create <name> <metric> <operator> <threshold>')
|
|
378
|
-
.description('Create an alert rule (metric: cpu|memory|cert-expiry, operator: > or <)')
|
|
517
|
+
.description('Create an alert rule (metric: cpu|memory|cert-expiry|disk|server_offline, operator: > or <)')
|
|
379
518
|
.option('-w, --windows <n>', 'Consecutive 30s samples before firing', '1')
|
|
380
519
|
.option('-s, --service <id>', 'Scope to a service (default: host-wide)')
|
|
381
520
|
.action((name, metric, op, threshold, opts) => alertsCreate(getClient(), name, metric, op, threshold, opts));
|
|
@@ -410,8 +549,10 @@ plugins.command('list').description('List all installed plugins').action(() => p
|
|
|
410
549
|
plugins.command('marketplace').description('Browse verified marketplace extensions').action(() => pluginsMarketplace(getClient()));
|
|
411
550
|
plugins.command('marketplace-refresh').description('Bypass the cache and re-fetch the live signed marketplace index').action(() => pluginsMarketplaceRefresh(getClient()));
|
|
412
551
|
plugins.command('inspect <id>').description('Inspect plugin manifest and runtime telemetry').action((id) => pluginsInspect(getClient(), id));
|
|
413
|
-
plugins.command('install <target>').description('Install a plugin (marketplace,
|
|
414
|
-
|
|
552
|
+
plugins.command('install <target>').description('Install a plugin (marketplace, sandbox)')
|
|
553
|
+
// r440: only what the server can actually load — npm/git/local were
|
|
554
|
+
// advertised here and always answered 400 UnsupportedPluginSourceError.
|
|
555
|
+
.option('-s, --source <source>', 'Source type (marketplace, sandbox)', 'marketplace')
|
|
415
556
|
.option('-n, --name <name>', 'Custom display name')
|
|
416
557
|
.option('-v, --version <version>', 'Custom version')
|
|
417
558
|
.option('-d, --desc <description>', 'Description')
|
|
@@ -454,8 +595,10 @@ images
|
|
|
454
595
|
images
|
|
455
596
|
.command('prune')
|
|
456
597
|
.description('Prune images. Refuses to run with no filter; pass --dry-run first.')
|
|
457
|
-
|
|
458
|
-
.
|
|
598
|
+
// r554: validated whole numbers (the server's bounds) — `Number(v)` let
|
|
599
|
+
// `1.5` / `abc` through to an opaque 422.
|
|
600
|
+
.option('--keep-last <n>', 'Keep the newest N images per repo:tag (rest are candidates)', positiveIntOption(1000))
|
|
601
|
+
.option('--older-than <hours>', 'Only prune images older than N hours', positiveIntOption(8760))
|
|
459
602
|
.option('--dangling', 'Only prune dangling images (repo/tag both <none>)')
|
|
460
603
|
.option('--dry-run', 'Report what would be deleted without actually deleting')
|
|
461
604
|
.action((opts) => imagesPrune(getClient(), {
|
|
@@ -523,6 +666,33 @@ emailTemplatesCmd
|
|
|
523
666
|
// ── System export/import + deploy log streaming ────────────────────────────
|
|
524
667
|
system.command('export [file]').description('Export the full system state as JSON').action((file) => systemExport(file));
|
|
525
668
|
system.command('import <file>').description('Import a system bundle (destructive)').action((file) => systemImport(file));
|
|
669
|
+
// ── Panel self-backup (0.12) ────────────────────────────────────────────────
|
|
670
|
+
const panelBackupCmd = system.command('panel-backup').description("Scheduled, passphrase-sealed backups of the panel's own state");
|
|
671
|
+
panelBackupCmd.command('status').description('Show the schedule, destination, retention and the last run').action(() => panelBackupStatusAction(getClient()));
|
|
672
|
+
panelBackupCmd
|
|
673
|
+
.command('set')
|
|
674
|
+
.description('Change the panel backup settings (omitted options keep their value)')
|
|
675
|
+
.option('--enable', 'Turn scheduled panel backups on')
|
|
676
|
+
.option('--disable', 'Turn scheduled panel backups off')
|
|
677
|
+
.option('--cron <expr>', '5-field cron expression, e.g. "0 3 * * *"')
|
|
678
|
+
.option('--destination <id>', 'Backup destination id (see Settings → Integrations)')
|
|
679
|
+
.option('--retain <n>', 'How many panel backups to keep in the destination')
|
|
680
|
+
.option('--passphrase', 'Prompt for a new recovery passphrase (keep it off this server)')
|
|
681
|
+
.action((opts) => panelBackupSetAction(getClient(), opts));
|
|
682
|
+
panelBackupCmd
|
|
683
|
+
.command('now')
|
|
684
|
+
.description('Back up the panel now')
|
|
685
|
+
.option('--wait', 'Wait for the run to finish and report its outcome')
|
|
686
|
+
.action((opts) => panelBackupNowAction(getClient(), opts));
|
|
687
|
+
panelBackupCmd
|
|
688
|
+
.command('list')
|
|
689
|
+
.description('List the panel backups in the destination, newest first')
|
|
690
|
+
.option('--destination <id>', 'Destination to list (default: the configured one)')
|
|
691
|
+
.action((opts) => panelBackupListAction(getClient(), opts));
|
|
692
|
+
panelBackupCmd
|
|
693
|
+
.command('decrypt <file> [out]')
|
|
694
|
+
.description('Offline: open a downloaded .ndpb with the recovery passphrase into a .tar.gz for `system import`')
|
|
695
|
+
.action((file, out) => panelBackupDecryptAction(file, out));
|
|
526
696
|
// ── Certificates (G-15) ─────────────────────────────────────────────────────
|
|
527
697
|
const certificates = program.command('certificates').description('Traefik certificate inventory and renewal status');
|
|
528
698
|
certificates
|
|
@@ -535,6 +705,201 @@ certificates
|
|
|
535
705
|
.description('Focused list of certificates expiring within N days')
|
|
536
706
|
.option('--days <days>', 'Window in days (default 30)')
|
|
537
707
|
.action((opts) => certificatesExpiring(getClient(), opts));
|
|
708
|
+
// 0.14: uploaded certificates (operator).
|
|
709
|
+
const customCerts = certificates.command('custom').description('Uploaded TLS certificates (operator)');
|
|
710
|
+
/* v8 ignore start -- exercised by test/certificates.test.ts. */
|
|
711
|
+
customCerts.command('list').description('List uploaded certificates, their hostnames, expiry and covered domains')
|
|
712
|
+
.action(() => certificatesCustomList(getClient()));
|
|
713
|
+
customCerts.command('upload')
|
|
714
|
+
.description('Upload a certificate chain and its unencrypted private key')
|
|
715
|
+
.option('--name <name>', 'Display name')
|
|
716
|
+
.option('--cert <path>', 'PEM certificate chain, leaf first')
|
|
717
|
+
.option('--key <path>', 'PEM private key (unencrypted)')
|
|
718
|
+
.action((opts) => certificatesCustomUpload(getClient(), opts));
|
|
719
|
+
customCerts.command('replace <id>')
|
|
720
|
+
.description('Replace a certificate (e.g. after renewal)')
|
|
721
|
+
.option('--name <name>', 'New display name')
|
|
722
|
+
.option('--cert <path>', 'PEM certificate chain, leaf first')
|
|
723
|
+
.option('--key <path>', 'PEM private key (unencrypted)')
|
|
724
|
+
.action((id, opts) => certificatesCustomReplace(getClient(), id, opts));
|
|
725
|
+
customCerts.command('delete <id>')
|
|
726
|
+
.description('Delete a certificate (covered domains fall back to ACME)')
|
|
727
|
+
.alias('rm')
|
|
728
|
+
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
729
|
+
.action((id, opts) => certificatesCustomDelete(getClient(), id, opts));
|
|
730
|
+
/* v8 ignore stop */
|
|
731
|
+
// ── Proxy custom config (0.14) ──────────────────────────────────────────────
|
|
732
|
+
const proxyCmd = program.command('proxy').description('Traefik proxy management (operator)');
|
|
733
|
+
const proxyConfigCmd = proxyCmd.command('config').description('Custom Traefik dynamic config (custom.yml)');
|
|
734
|
+
/* v8 ignore start -- exercised by test/proxyConfig.test.ts. */
|
|
735
|
+
proxyConfigCmd.command('get').description('Show the custom config, its status and the last error').action(() => proxyConfigGet(getClient()));
|
|
736
|
+
proxyConfigCmd.command('validate')
|
|
737
|
+
.description('Validate a YAML file without applying it')
|
|
738
|
+
.option('--file <path>', 'YAML file')
|
|
739
|
+
.action((opts) => proxyConfigValidate(getClient(), opts));
|
|
740
|
+
proxyConfigCmd.command('set')
|
|
741
|
+
.description('Validate and apply a YAML file')
|
|
742
|
+
.option('--file <path>', 'YAML file')
|
|
743
|
+
.action((opts) => proxyConfigSet(getClient(), opts));
|
|
744
|
+
proxyConfigCmd.command('clear')
|
|
745
|
+
.description('Remove the custom config')
|
|
746
|
+
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
747
|
+
.action((opts) => proxyConfigClear(getClient(), opts));
|
|
748
|
+
/* v8 ignore stop */
|
|
749
|
+
// ── Secret managers (0.14) ──────────────────────────────────────────────────
|
|
750
|
+
const secretsCmd = program.command('secrets').description('Deploy-time secret references');
|
|
751
|
+
const providersCmd = secretsCmd.command('providers').description('HashiCorp Vault / OpenBao and AWS Secrets Manager (operator)');
|
|
752
|
+
/* v8 ignore start -- exercised by test/secretProviders.test.ts. */
|
|
753
|
+
providersCmd.command('list').description('List the secret managers and their last test').action(() => secretProvidersList(getClient()));
|
|
754
|
+
providersCmd.command('set-vault')
|
|
755
|
+
.description('Configure Vault / OpenBao (KV v2); token from --token-file or NINEDEPLOY_VAULT_TOKEN')
|
|
756
|
+
.option('--address <url>', 'Vault address (https)')
|
|
757
|
+
.option('--namespace <ns>', 'Vault Enterprise / OpenBao namespace')
|
|
758
|
+
.option('--mount <path>', 'KV v2 mount (default secret)')
|
|
759
|
+
.option('--auth <method>', 'token | approle (default token)')
|
|
760
|
+
.option('--approle-mount <path>', 'AppRole auth mount (default approle)')
|
|
761
|
+
.option('--token-file <path>', 'File holding the token (or NINEDEPLOY_VAULT_TOKEN)')
|
|
762
|
+
.option('--role-id-file <path>', 'File holding the AppRole role id (or NINEDEPLOY_VAULT_ROLE_ID)')
|
|
763
|
+
.option('--secret-id-file <path>', 'File holding the AppRole secret id (or NINEDEPLOY_VAULT_SECRET_ID)')
|
|
764
|
+
.option('--enable', 'Enable the provider')
|
|
765
|
+
.option('--disable', 'Disable the provider (references stay literal)')
|
|
766
|
+
.action((opts) => secretProvidersSetVault(getClient(), opts));
|
|
767
|
+
providersCmd.command('set-aws')
|
|
768
|
+
.description('Configure AWS Secrets Manager; secret key from --secret-access-key-file or NINEDEPLOY_AWS_SECRET_ACCESS_KEY')
|
|
769
|
+
.option('--region <region>', 'AWS region, e.g. eu-west-1')
|
|
770
|
+
.option('--endpoint <url>', 'VPC endpoint or compatible service (https)')
|
|
771
|
+
.option('--role-arn <arn>', 'IAM role to assume')
|
|
772
|
+
.option('--external-id <id>', 'External id for the role (with --role-arn)')
|
|
773
|
+
.option('--role-session-name <name>', 'Session name (default ninedeploy)')
|
|
774
|
+
.option('--access-key-id <id>', 'Access key id (or NINEDEPLOY_AWS_ACCESS_KEY_ID)')
|
|
775
|
+
.option('--secret-access-key-file <path>', 'File holding the secret access key (or NINEDEPLOY_AWS_SECRET_ACCESS_KEY)')
|
|
776
|
+
.option('--session-token-file <path>', 'File holding a session token (or NINEDEPLOY_AWS_SESSION_TOKEN)')
|
|
777
|
+
.option('--enable', 'Enable the provider')
|
|
778
|
+
.option('--disable', 'Disable the provider (references stay literal)')
|
|
779
|
+
.action((opts) => secretProvidersSetAws(getClient(), opts));
|
|
780
|
+
providersCmd.command('test <kind>')
|
|
781
|
+
.description('Test a provider (vault | aws), optionally reading one secret')
|
|
782
|
+
.option('--probe-path <path>', 'vault: also read this path (relative to the mount)')
|
|
783
|
+
.option('--probe-secret-id <id>', 'aws: also read this secret')
|
|
784
|
+
.action((kind, opts) => secretProvidersTest(getClient(), kind, opts));
|
|
785
|
+
providersCmd.command('delete <kind>')
|
|
786
|
+
.description('Remove a provider (vault | aws); its references stay literal at deploy')
|
|
787
|
+
.alias('rm')
|
|
788
|
+
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
789
|
+
.action((kind, opts) => secretProvidersDelete(getClient(), kind, opts));
|
|
790
|
+
/* v8 ignore stop */
|
|
791
|
+
// ── 0.15: terminals, traffic analytics, access grants ────────────────────
|
|
792
|
+
const terminalsCmd = program.command('terminals').description('Terminal session history and termination (operator)');
|
|
793
|
+
/* v8 ignore start -- exercised by test/operations.test.ts. */
|
|
794
|
+
terminalsCmd.command('list')
|
|
795
|
+
.description('List terminal sessions, newest first (metadata only)')
|
|
796
|
+
.option('--status <status>', 'pending | active | ended | failed | expired')
|
|
797
|
+
.option('--target <kind>', 'service | database | container | host')
|
|
798
|
+
.option('--limit <n>', 'Page size (1-200, default 50)')
|
|
799
|
+
.option('--before <id>', 'Page back from this session id')
|
|
800
|
+
.action((opts) => terminalsList(getClient(), opts));
|
|
801
|
+
terminalsCmd.command('show <id>').description('Show one terminal session').action((id) => terminalsShow(getClient(), id));
|
|
802
|
+
terminalsCmd.command('kill <id>')
|
|
803
|
+
.description('Terminate a live session (or revoke a pending one)')
|
|
804
|
+
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
805
|
+
.action((id, opts) => terminalsKill(getClient(), id, opts));
|
|
806
|
+
/* v8 ignore stop */
|
|
807
|
+
const terminalCmd = program.command('terminal').alias('shell').description('Open an interactive shell (operator; needs a TTY)');
|
|
808
|
+
/* v8 ignore start -- exercised by test/operations.test.ts. */
|
|
809
|
+
terminalCmd.command('service <id>')
|
|
810
|
+
.description('Shell into the container of a service')
|
|
811
|
+
.option('--replica <n>', 'Replica number (1-based)')
|
|
812
|
+
.option('--node <serverId>', 'A node the service runs on (fan-out target)')
|
|
813
|
+
.action((id, opts) => terminalOpen(getClient(), 'service', id, opts));
|
|
814
|
+
terminalCmd.command('db <id>')
|
|
815
|
+
.description('Shell into the container of a database')
|
|
816
|
+
.option('--client', 'Open the engine client (psql, mysql, redis-cli) with the stored credentials')
|
|
817
|
+
.action((id, opts) => terminalOpen(getClient(), 'db', id, opts));
|
|
818
|
+
terminalCmd.command('container <name>')
|
|
819
|
+
.description('Shell into a managed container on the panel host')
|
|
820
|
+
.action((name) => terminalOpen(getClient(), 'container', name));
|
|
821
|
+
terminalCmd.command('host [serverId]')
|
|
822
|
+
.description('Root shell on the panel host or a node (off by default; asks for your password)')
|
|
823
|
+
.action((serverId) => terminalOpen(getClient(), 'host', serverId));
|
|
824
|
+
/* v8 ignore stop */
|
|
825
|
+
const trafficCmd = program.command('traffic').description('Traffic analytics (opt-in)');
|
|
826
|
+
/* v8 ignore start -- exercised by test/operations.test.ts. */
|
|
827
|
+
trafficCmd.command('settings')
|
|
828
|
+
.description('Show or change traffic analytics (enabling or disabling recreates Traefik once)')
|
|
829
|
+
.option('--enable', 'Turn analytics on')
|
|
830
|
+
.option('--disable', 'Turn analytics off')
|
|
831
|
+
.option('--retention <days>', 'Keep hourly rows this many days (1-400)')
|
|
832
|
+
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
833
|
+
.action((opts) => trafficSettings(getClient(), opts));
|
|
834
|
+
trafficCmd.command('summary')
|
|
835
|
+
.description('Instance traffic: totals, percentiles and top domains (operator)')
|
|
836
|
+
.option('--range <range>', '1h | 24h | 7d | 30d (default 24h)')
|
|
837
|
+
.option('--top <n>', 'Top domains (1-50, default 10)')
|
|
838
|
+
.action((opts) => trafficSummary(getClient(), opts));
|
|
839
|
+
trafficCmd.command('service <id>')
|
|
840
|
+
.description('Traffic of one service, by domain')
|
|
841
|
+
.option('--range <range>', '1h | 24h | 7d | 30d (default 24h)')
|
|
842
|
+
.action((id, opts) => trafficService(getClient(), id, opts));
|
|
843
|
+
/* v8 ignore stop */
|
|
844
|
+
const accessCmd = program.command('access').description('Project and environment access grants');
|
|
845
|
+
const grantsCmd = accessCmd.command('grants').description('Manage access grants in a workspace (workspace admin)');
|
|
846
|
+
/* v8 ignore start -- exercised by test/operations.test.ts. */
|
|
847
|
+
grantsCmd.command('list')
|
|
848
|
+
.description('List the grants in a workspace')
|
|
849
|
+
.option('--workspace <id>', 'Workspace id')
|
|
850
|
+
.option('--user <id>', 'Only this user')
|
|
851
|
+
.option('--project <id>', 'Only this project')
|
|
852
|
+
.option('--environment <id>', 'Only this environment')
|
|
853
|
+
.action((opts) => accessGrantsList(getClient(), opts));
|
|
854
|
+
grantsCmd.command('add')
|
|
855
|
+
.description('Grant a role on a project, an environment, or both')
|
|
856
|
+
.option('--workspace <id>', 'Workspace id')
|
|
857
|
+
.option('--email <email>', 'The user, by email')
|
|
858
|
+
.option('--user <id>', 'The user, by id')
|
|
859
|
+
.option('--project <id>', 'Project id')
|
|
860
|
+
.option('--environment <id>', 'Environment id')
|
|
861
|
+
.option('--role <role>', 'viewer | member | admin')
|
|
862
|
+
.action((opts) => accessGrantsAdd(getClient(), opts));
|
|
863
|
+
grantsCmd.command('update <grantId>')
|
|
864
|
+
.description('Change the role of a grant, or suspend or reinstate it')
|
|
865
|
+
.option('--workspace <id>', 'Workspace id')
|
|
866
|
+
.option('--role <role>', 'viewer | member | admin')
|
|
867
|
+
.option('--suspend', 'Keep the grant but stop it giving access')
|
|
868
|
+
.option('--reinstate', 'Make a suspended grant give access again')
|
|
869
|
+
.action((grantId, opts) => accessGrantsUpdate(getClient(), grantId, opts));
|
|
870
|
+
grantsCmd.command('remove <grantId>')
|
|
871
|
+
.alias('rm')
|
|
872
|
+
.description('Revoke a grant')
|
|
873
|
+
.option('--workspace <id>', 'Workspace id')
|
|
874
|
+
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
875
|
+
.action((grantId, opts) => accessGrantsRemove(getClient(), grantId, opts));
|
|
876
|
+
accessCmd.command('me').description('Your own grants and guest workspaces').action(() => accessMe(getClient()));
|
|
877
|
+
/* v8 ignore stop */
|
|
878
|
+
// ── 0.16 T8: remote nodes and Swarm (operator) ───────────────────────────
|
|
879
|
+
const serversCmd = program.command('servers').description('Remote nodes: agent capabilities, build-server role (operator)');
|
|
880
|
+
/* v8 ignore start -- exercised by test/multiNode.test.ts. */
|
|
881
|
+
serversCmd.command('list').description('List nodes with their agent version, build role, databases and Swarm membership').action(() => serversList(getClient()));
|
|
882
|
+
serversCmd.command('roles <id>')
|
|
883
|
+
.description('Set the build-server role of a node')
|
|
884
|
+
.option('--build-server <onOff>', 'on | off')
|
|
885
|
+
.option('--build-concurrency <n>', 'Builds at once on this node (1-8)')
|
|
886
|
+
.action((id, opts) => serversRoles(getClient(), id, opts));
|
|
887
|
+
/* v8 ignore stop */
|
|
888
|
+
const swarmCmd = program.command('swarm').description('Docker Swarm on the panel host (operator, opt-in per service)');
|
|
889
|
+
/* v8 ignore start -- exercised by test/multiNode.test.ts. */
|
|
890
|
+
swarmCmd.command('status').description('Swarm state, the join address and the nodes').action(() => swarmStatus(getClient()));
|
|
891
|
+
swarmCmd.command('init')
|
|
892
|
+
.description('Initialise Swarm on the panel host (asks for your password)')
|
|
893
|
+
.option('--advertise-addr <ip>', 'The address nodes reach the panel host on')
|
|
894
|
+
.action((opts) => swarmInit(getClient(), opts));
|
|
895
|
+
swarmCmd.command('enable').description('Allow Swarm deploys (asks for your password)').action(() => swarmSetEnabled(getClient(), true));
|
|
896
|
+
swarmCmd.command('disable').description('Refuse new Swarm deploys; running stacks keep running').action(() => swarmSetEnabled(getClient(), false));
|
|
897
|
+
swarmCmd.command('join <serverId>').description('Join a node to the swarm as a worker').action((serverId) => swarmJoin(getClient(), serverId));
|
|
898
|
+
swarmCmd.command('leave <serverId>')
|
|
899
|
+
.description('Drain a node and take it out of the swarm')
|
|
900
|
+
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
901
|
+
.action((serverId, opts) => swarmLeave(getClient(), serverId, opts));
|
|
902
|
+
/* v8 ignore stop */
|
|
538
903
|
deploys.command('watch <serviceId> <deployId>').description('Stream a deployment\'s build logs live').action((svcId, depId) => deploysWatch(svcId, depId));
|
|
539
904
|
// ── Diagnostics ───────────────────────────────────────────────────────────
|
|
540
905
|
program
|
|
@@ -548,10 +913,49 @@ const sources = program.command('sources').alias('creds').description('Manage pr
|
|
|
548
913
|
* the implementation is exercised end-to-end by test/sources.test.ts. */
|
|
549
914
|
sources.command('list').description('List configured sources').action(() => sourcesList(getClient()));
|
|
550
915
|
sources.command('show <id>').description('Show one source in detail').action((id) => sourcesShow(getClient(), id));
|
|
551
|
-
sources.command('add [name]')
|
|
916
|
+
sources.command('add [name]')
|
|
917
|
+
.description('Add a new source (interactive; PAT/SSH key from env or masked prompt)')
|
|
918
|
+
.option('--base-url <url>', 'Gitea base URL, e.g. https://git.example.com (enables the live test and repo list)')
|
|
919
|
+
.action((name, opts) => sourcesAdd(getClient(), name, opts));
|
|
552
920
|
sources.command('test [id]').description('Verify that a stored source token still authenticates').action((id) => sourcesTest(getClient(), id));
|
|
553
921
|
sources.command('keygen [id]').description('Generate an ed25519 deploy-key pair on the panel and print the public key').action((id) => sourcesKeygen(getClient(), id));
|
|
554
922
|
sources.command('remove [id]').description('Remove a source (with confirmation)').alias('rm').action((id) => sourcesRemove(getClient(), id));
|
|
923
|
+
// 0.16 T8 (multi-node): send this PAT or deploy key to nodes for clones there.
|
|
924
|
+
sources.command('allow-on-nodes <id> <onOff>')
|
|
925
|
+
.description('Allow (on, asks for your password) or stop (off) sending this credential to nodes for clones')
|
|
926
|
+
.action((id, onOff) => sourcesAllowOnNodes(getClient(), id, onOff));
|
|
927
|
+
// 0.15.8: pin the git host's SSH keys for deploy-key clones the panel makes.
|
|
928
|
+
sources.command('pin-host-keys <id> <onOff>')
|
|
929
|
+
.description('Pin (on) or stop pinning (off, asks for your password) the git host SSH keys of a deploy-key source')
|
|
930
|
+
.action((id, onOff) => sourcesPinHostKeys(getClient(), id, onOff));
|
|
931
|
+
sources.command('reset-host-keys <id>')
|
|
932
|
+
.description('Forget the pinned git host keys so the next clone learns them again (asks for your password)')
|
|
933
|
+
.action((id) => sourcesResetHostKeys(getClient(), id));
|
|
934
|
+
sources.command('host-keys <id>').description('Show the git host key fingerprints a source has pinned').action((id) => sourcesHostKeys(getClient(), id));
|
|
935
|
+
/* v8 ignore stop */
|
|
936
|
+
// ── GitHub Apps (0.13) ─────────────────────────────────────────────────────
|
|
937
|
+
const githubApp = program.command('github-app').description('Manage GitHub Apps (one-click setup runs in the panel: Sources → GitHub Apps)');
|
|
938
|
+
/* v8 ignore start -- see sources.ts note above; exercised by test/githubApps.test.ts. */
|
|
939
|
+
githubApp.command('list').description('List registered GitHub Apps').action(() => githubAppList(getClient()));
|
|
940
|
+
githubApp.command('show <id>').description('Show a GitHub App and its installations').action((id) => githubAppShow(getClient(), id));
|
|
941
|
+
githubApp.command('add-manual')
|
|
942
|
+
.description('Register an existing GitHub App or one on GitHub Enterprise Server (key from --key-file or NINEDEPLOY_GITHUB_APP_KEY)')
|
|
943
|
+
.option('--name <name>', 'Display name')
|
|
944
|
+
.option('--app-id <id>', 'GitHub App ID')
|
|
945
|
+
.option('--key-file <path>', 'Path to the App private key (.pem)')
|
|
946
|
+
.option('--web-base-url <url>', 'GHES web URL, e.g. https://github.example.com (with --api-base-url)')
|
|
947
|
+
.option('--api-base-url <url>', 'GHES API URL, e.g. https://github.example.com/api/v3 (with --web-base-url)')
|
|
948
|
+
.action((opts) => githubAppAddManual(getClient(), opts));
|
|
949
|
+
githubApp.command('sync <id>').description('Sync installations from GitHub (creates their gh-app sources)').action((id) => githubAppSync(getClient(), id));
|
|
950
|
+
githubApp.command('rotate-key <id>')
|
|
951
|
+
.description('Replace the App private key (verified with GitHub first)')
|
|
952
|
+
.option('--key-file <path>', 'Path to the new private key (.pem)')
|
|
953
|
+
.action((id, opts) => githubAppRotateKey(getClient(), id, opts));
|
|
954
|
+
githubApp.command('remove <id>')
|
|
955
|
+
.description('Forget a GitHub App (its installation sources stay)')
|
|
956
|
+
.alias('rm')
|
|
957
|
+
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
958
|
+
.action((id, opts) => githubAppRemove(getClient(), id, opts));
|
|
555
959
|
/* v8 ignore stop */
|
|
556
960
|
// ── Deploy (one-shot private GitHub deploy) ────────────────────────────────
|
|
557
961
|
const deploy = program.command('deploy').description('End-to-end deploy helpers');
|