@celilo/cli 0.5.0-alpha.7 → 0.5.0-alpha.9
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/package.json +2 -2
- package/src/api-clients/proxmox.test.ts +78 -0
- package/src/api-clients/proxmox.ts +96 -1
- package/src/cli/command-registry.ts +32 -3
- package/src/cli/commands/backup-delete.ts +10 -7
- package/src/cli/commands/backup-import.ts +11 -8
- package/src/cli/commands/backup-restore.ts +11 -8
- package/src/cli/commands/events.ts +8 -3
- package/src/cli/commands/machine-add.ts +178 -163
- package/src/cli/commands/machine-remove.ts +10 -7
- package/src/cli/commands/module-config.test.ts +78 -0
- package/src/cli/commands/module-config.ts +18 -3
- package/src/cli/commands/module-import.ts +9 -5
- package/src/cli/commands/module-remove.ts +20 -9
- package/src/cli/commands/module-status.ts +15 -0
- package/src/cli/commands/module-upgrade.ts +10 -6
- package/src/cli/commands/proxmox-node-list.ts +101 -0
- package/src/cli/commands/proxmox-template-selection.ts +16 -15
- package/src/cli/commands/service-add-digitalocean.ts +120 -109
- package/src/cli/commands/service-add-proxmox.ts +275 -260
- package/src/cli/commands/service-reconfigure.ts +171 -153
- package/src/cli/commands/service-remove.ts +19 -13
- package/src/cli/commands/service-verify.ts +9 -10
- package/src/cli/commands/storage-add-local.ts +120 -107
- package/src/cli/commands/storage-add-s3.ts +145 -131
- package/src/cli/commands/storage-remove.ts +11 -8
- package/src/cli/commands/system-init.ts +119 -128
- package/src/cli/completion.ts +15 -0
- package/src/cli/index.ts +25 -0
- package/src/cli/service-credential.ts +54 -0
- package/src/services/bus-interview.ts +232 -0
- package/src/services/deploy-validation.test.ts +52 -2
- package/src/services/deploy-validation.ts +27 -36
- package/src/services/fleet-checks.test.ts +13 -0
- package/src/services/fleet-checks.ts +15 -0
- package/src/services/module-config.ts +12 -0
- package/src/services/module-deploy.ts +7 -6
- package/src/services/placement-reconcile.test.ts +86 -0
- package/src/services/placement-reconcile.ts +108 -0
- package/src/services/programmatic-responder.ts +34 -0
- package/src/services/terminal-responder.ts +113 -0
- package/src/templates/generator.test.ts +30 -0
- package/src/templates/generator.ts +86 -31
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Configure a Proxmox container service
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import * as p from '@clack/prompts';
|
|
7
6
|
import {
|
|
8
7
|
buildProxmoxApiUrl,
|
|
9
8
|
buildTemplatePath,
|
|
@@ -11,20 +10,32 @@ import {
|
|
|
11
10
|
listNodeStorage,
|
|
12
11
|
} from '../../api-clients/proxmox';
|
|
13
12
|
import type { NetworkZone } from '../../db/schema';
|
|
13
|
+
import {
|
|
14
|
+
askConfirm,
|
|
15
|
+
askMultiselect,
|
|
16
|
+
askSelect,
|
|
17
|
+
askText,
|
|
18
|
+
withInterviewSession,
|
|
19
|
+
} from '../../services/bus-interview';
|
|
14
20
|
import {
|
|
15
21
|
addContainerService,
|
|
16
22
|
testConnection as testServiceConnection,
|
|
17
23
|
updateVerificationStatus,
|
|
18
24
|
} from '../../services/container-service';
|
|
19
|
-
import { celiloIntro, celiloOutro
|
|
25
|
+
import { celiloIntro, celiloOutro } from '../prompts';
|
|
26
|
+
import { resolveServiceCredential } from '../service-credential';
|
|
20
27
|
import type { CommandResult } from '../types';
|
|
21
|
-
import { validateRequired } from '../validators';
|
|
22
28
|
import {
|
|
23
29
|
runApplianceDownload,
|
|
24
30
|
selectUbuntuApplianceFromCatalog,
|
|
25
31
|
} from './proxmox-template-selection';
|
|
26
32
|
import { buildCloudInitTemplate, detectExistingVmTemplates } from './proxmox-vm-template-build';
|
|
27
33
|
|
|
34
|
+
// Sentinel VM-template choices returned by the select. Any other value is an
|
|
35
|
+
// existing template name.
|
|
36
|
+
const VM_BUILD = '__build__';
|
|
37
|
+
const VM_SKIP = '__skip__';
|
|
38
|
+
|
|
28
39
|
/**
|
|
29
40
|
* Handle service add proxmox command
|
|
30
41
|
*
|
|
@@ -33,291 +44,295 @@ import { buildCloudInitTemplate, detectExistingVmTemplates } from './proxmox-vm-
|
|
|
33
44
|
*/
|
|
34
45
|
export async function handleServiceAddProxmox(
|
|
35
46
|
_args: string[],
|
|
36
|
-
|
|
47
|
+
flags: Record<string, boolean | string> = {},
|
|
37
48
|
): Promise<CommandResult> {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{ value: 'app' as NetworkZone, label: 'app' },
|
|
56
|
-
{ value: 'secure' as NetworkZone, label: 'secure' },
|
|
57
|
-
{ value: 'external' as NetworkZone, label: 'external' },
|
|
58
|
-
],
|
|
59
|
-
required: true,
|
|
60
|
-
initialValues: ['internal', 'dmz', 'app', 'secure'] as NetworkZone[], // Common defaults
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (p.isCancel(zones)) {
|
|
64
|
-
p.cancel('Operation cancelled');
|
|
65
|
-
return { success: false, error: 'Cancelled by user' };
|
|
66
|
-
}
|
|
49
|
+
// Non-secret prompts route through the bus interview (ISS-0127); the API
|
|
50
|
+
// token id + secret are service credentials that travel by flag/env only
|
|
51
|
+
// (D7). `withInterviewSession` renders bus questions locally on a TTY.
|
|
52
|
+
const scope = 'service-add:proxmox';
|
|
53
|
+
|
|
54
|
+
return withInterviewSession(async () => {
|
|
55
|
+
try {
|
|
56
|
+
celiloIntro('Add Proxmox Container Service');
|
|
57
|
+
|
|
58
|
+
const name = await askText({
|
|
59
|
+
scope,
|
|
60
|
+
key: 'name',
|
|
61
|
+
message: 'Human-readable name',
|
|
62
|
+
defaultValue: 'Proxmox Home Lab',
|
|
63
|
+
placeholder: 'Proxmox Home Lab',
|
|
64
|
+
required: true,
|
|
65
|
+
});
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
message: 'Proxmox IP address:',
|
|
73
|
-
placeholder: 'e.g., 192.168.1.100 or proxmox.local',
|
|
74
|
-
validate: validateRequired('IP address'),
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const port = await promptText({
|
|
78
|
-
message: 'Proxmox API port:',
|
|
79
|
-
defaultValue: '8006',
|
|
80
|
-
placeholder: '8006',
|
|
81
|
-
validate: (value): string | Error | undefined => {
|
|
82
|
-
// validateRequired returns undefined for valid inputs (including undefined with defaults)
|
|
83
|
-
const requiredError = validateRequired('Port')(value);
|
|
84
|
-
if (requiredError !== undefined) return requiredError;
|
|
85
|
-
|
|
86
|
-
// If value is undefined, default will be used - that's valid
|
|
87
|
-
if (value === undefined) return undefined;
|
|
88
|
-
|
|
89
|
-
const portNum = Number(value);
|
|
90
|
-
if (Number.isNaN(portNum) || portNum < 1 || portNum > 65535) {
|
|
91
|
-
return 'Port must be a number between 1 and 65535';
|
|
92
|
-
}
|
|
93
|
-
return undefined;
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// Build the full API URL
|
|
98
|
-
const apiUrl = buildProxmoxApiUrl(ipAddress, Number(port));
|
|
99
|
-
|
|
100
|
-
const apiTokenId = await promptText({
|
|
101
|
-
message: 'API Token ID:',
|
|
102
|
-
defaultValue: 'root@pam!celilo',
|
|
103
|
-
placeholder: 'root@pam!celilo',
|
|
104
|
-
validate: validateRequired('API token ID'),
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const apiTokenSecret = await promptPassword({
|
|
108
|
-
message: 'API Token Secret:',
|
|
109
|
-
validate: validateRequired('API token secret'),
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const targetNode = await promptText({
|
|
113
|
-
message: 'Default target node:',
|
|
114
|
-
defaultValue: 'pve',
|
|
115
|
-
placeholder: 'pve',
|
|
116
|
-
validate: validateRequired('Target node'),
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
const storage = await promptText({
|
|
120
|
-
message: 'Default storage:',
|
|
121
|
-
defaultValue: 'local-lvm',
|
|
122
|
-
placeholder: 'local-lvm',
|
|
123
|
-
validate: validateRequired('Storage'),
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// Build credentials here — needed for VM template detection below and
|
|
127
|
-
// for LXC template catalog lookup further down.
|
|
128
|
-
const credentials = {
|
|
129
|
-
api_url: apiUrl,
|
|
130
|
-
api_token_id: apiTokenId,
|
|
131
|
-
api_token_secret: apiTokenSecret,
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
// VM template for `requires.system.type: vm` modules.
|
|
135
|
-
// Auto-detect existing templates on the node; offer to build if none found.
|
|
136
|
-
// See v2/PROXMOX_VM_TEMPLATE.md.
|
|
137
|
-
let vmTemplate: string | undefined;
|
|
138
|
-
const existingTemplates = await detectExistingVmTemplates(credentials, targetNode);
|
|
139
|
-
if (existingTemplates.length > 0) {
|
|
140
|
-
const templateChoice = await p.select({
|
|
141
|
-
message: 'VM template for VM-type modules:',
|
|
67
|
+
const zones = await askMultiselect({
|
|
68
|
+
scope,
|
|
69
|
+
key: 'zones',
|
|
70
|
+
message: 'Zones this service can provision to',
|
|
142
71
|
options: [
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
{ value: '
|
|
148
|
-
{ value: '__skip__', label: 'Skip — I only need LXC modules' },
|
|
72
|
+
{ value: 'internal', label: 'internal' },
|
|
73
|
+
{ value: 'dmz', label: 'dmz' },
|
|
74
|
+
{ value: 'app', label: 'app' },
|
|
75
|
+
{ value: 'secure', label: 'secure' },
|
|
76
|
+
{ value: 'external', label: 'external' },
|
|
149
77
|
],
|
|
78
|
+
required: true,
|
|
150
79
|
});
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
} else if (templateChoice !== '__skip__') {
|
|
162
|
-
vmTemplate = templateChoice;
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
const shouldBuild = await p.confirm({
|
|
166
|
-
message: 'No VM templates found. Build a Ubuntu 24.04 cloud-init template now? (~2–10 min)',
|
|
167
|
-
initialValue: false,
|
|
80
|
+
|
|
81
|
+
console.log('\nProxmox Configuration');
|
|
82
|
+
console.log('─────────────────────');
|
|
83
|
+
|
|
84
|
+
const ipAddress = await askText({
|
|
85
|
+
scope,
|
|
86
|
+
key: 'ip_address',
|
|
87
|
+
message: 'Proxmox IP address',
|
|
88
|
+
placeholder: 'e.g., 192.168.1.100 or proxmox.local',
|
|
89
|
+
required: true,
|
|
168
90
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
91
|
+
|
|
92
|
+
const port = await askText({
|
|
93
|
+
scope,
|
|
94
|
+
key: 'port',
|
|
95
|
+
message: 'Proxmox API port',
|
|
96
|
+
defaultValue: '8006',
|
|
97
|
+
placeholder: '8006',
|
|
98
|
+
required: true,
|
|
99
|
+
type: 'integer',
|
|
100
|
+
pattern: '^[0-9]+$',
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Build the full API URL
|
|
104
|
+
const apiUrl = buildProxmoxApiUrl(ipAddress, Number(port));
|
|
105
|
+
|
|
106
|
+
// API token ID + secret are service credentials — flag/env only (D7).
|
|
107
|
+
const apiTokenId = await resolveServiceCredential({
|
|
108
|
+
field: 'API token ID',
|
|
109
|
+
flag: 'api-token-id',
|
|
110
|
+
envVar: 'PROXMOX_API_TOKEN_ID',
|
|
111
|
+
flagValue: flags['api-token-id'],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const apiTokenSecret = await resolveServiceCredential({
|
|
115
|
+
field: 'API token secret',
|
|
116
|
+
flag: 'api-token-secret',
|
|
117
|
+
envVar: 'PROXMOX_API_TOKEN_SECRET',
|
|
118
|
+
flagValue: flags['api-token-secret'],
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const targetNode = await askText({
|
|
122
|
+
scope,
|
|
123
|
+
key: 'default_target_node',
|
|
124
|
+
message: 'Default target node',
|
|
125
|
+
defaultValue: 'pve',
|
|
126
|
+
placeholder: 'pve',
|
|
127
|
+
required: true,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const storage = await askText({
|
|
131
|
+
scope,
|
|
132
|
+
key: 'storage',
|
|
133
|
+
message: 'Default storage',
|
|
134
|
+
defaultValue: 'local-lvm',
|
|
135
|
+
placeholder: 'local-lvm',
|
|
136
|
+
required: true,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Build credentials here — needed for VM template detection below and
|
|
140
|
+
// for LXC template catalog lookup further down.
|
|
141
|
+
const credentials = {
|
|
142
|
+
api_url: apiUrl,
|
|
143
|
+
api_token_id: apiTokenId,
|
|
144
|
+
api_token_secret: apiTokenSecret,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// VM template for `requires.system.type: vm` modules.
|
|
148
|
+
// Auto-detect existing templates on the node; offer to build if none found.
|
|
149
|
+
// See v2/PROXMOX_VM_TEMPLATE.md.
|
|
150
|
+
let vmTemplate: string | undefined;
|
|
151
|
+
const existingTemplates = await detectExistingVmTemplates(credentials, targetNode);
|
|
152
|
+
if (existingTemplates.length > 0) {
|
|
153
|
+
const templateChoice = await askSelect({
|
|
154
|
+
scope,
|
|
155
|
+
key: 'vm_template',
|
|
156
|
+
message: 'VM template for VM-type modules',
|
|
157
|
+
options: [
|
|
158
|
+
...existingTemplates.map((t) => ({
|
|
159
|
+
value: t.name,
|
|
160
|
+
label: `${t.name} (VMID ${t.vmid})`,
|
|
161
|
+
})),
|
|
162
|
+
{ value: VM_BUILD, label: 'Build a new Ubuntu 24.04 cloud-init template now' },
|
|
163
|
+
{ value: VM_SKIP, label: 'Skip — I only need LXC modules' },
|
|
164
|
+
],
|
|
178
165
|
});
|
|
166
|
+
if (templateChoice === VM_BUILD) {
|
|
167
|
+
vmTemplate = await buildCloudInitTemplate({
|
|
168
|
+
credentials,
|
|
169
|
+
nodeName: targetNode,
|
|
170
|
+
diskStorage: storage,
|
|
171
|
+
});
|
|
172
|
+
} else if (templateChoice !== VM_SKIP) {
|
|
173
|
+
vmTemplate = templateChoice;
|
|
174
|
+
}
|
|
179
175
|
} else {
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
const shouldBuild = await askConfirm({
|
|
177
|
+
scope,
|
|
178
|
+
key: 'vm_template_build',
|
|
179
|
+
message:
|
|
180
|
+
'No VM templates found. Build a Ubuntu 24.04 cloud-init template now? (~2–10 min)',
|
|
181
|
+
defaultValue: false,
|
|
183
182
|
});
|
|
184
|
-
|
|
183
|
+
if (shouldBuild) {
|
|
184
|
+
vmTemplate = await buildCloudInitTemplate({
|
|
185
|
+
credentials,
|
|
186
|
+
nodeName: targetNode,
|
|
187
|
+
diskStorage: storage,
|
|
188
|
+
});
|
|
189
|
+
} else {
|
|
190
|
+
const manualEntry = await askText({
|
|
191
|
+
scope,
|
|
192
|
+
key: 'vm_template_manual',
|
|
193
|
+
message: 'VM template name (optional — leave blank to skip)',
|
|
194
|
+
placeholder: 'e.g., ubuntu-2404-cloudinit',
|
|
195
|
+
});
|
|
196
|
+
vmTemplate = manualEntry.trim() || undefined;
|
|
197
|
+
}
|
|
185
198
|
}
|
|
186
|
-
}
|
|
187
199
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
200
|
+
// Find storage that supports vztmpl content. We do this BEFORE prompting
|
|
201
|
+
// for a template so the user only ever sees one storage in subsequent
|
|
202
|
+
// messages, and so the saved volid uses the right storage.
|
|
203
|
+
console.log('\nFinding storage for templates...');
|
|
204
|
+
const storageListResult = await listNodeStorage(credentials, targetNode);
|
|
193
205
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
206
|
+
let templateStorage = 'local';
|
|
207
|
+
if (storageListResult.success) {
|
|
208
|
+
const vztmplStorage = storageListResult.data.find(
|
|
209
|
+
(s) => s.active && s.enabled && s.content.includes('vztmpl'),
|
|
210
|
+
);
|
|
211
|
+
if (vztmplStorage) {
|
|
212
|
+
templateStorage = vztmplStorage.storage;
|
|
213
|
+
console.log(`✓ Using storage '${templateStorage}' for templates`);
|
|
214
|
+
} else {
|
|
215
|
+
console.log(`⚠ No storage found with 'vztmpl' support, using '${templateStorage}'`);
|
|
216
|
+
}
|
|
204
217
|
}
|
|
205
|
-
}
|
|
206
218
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
// Pick a template from Proxmox's live catalog (replaces the old hardcoded
|
|
220
|
+
// version select that built `-1`-revision URLs by hand).
|
|
221
|
+
const selectionResult = await selectUbuntuApplianceFromCatalog(credentials, targetNode, {
|
|
222
|
+
scope,
|
|
223
|
+
});
|
|
224
|
+
if (selectionResult.kind === 'cancelled') {
|
|
225
|
+
return { success: false, error: 'Cancelled by user' };
|
|
226
|
+
}
|
|
227
|
+
if (selectionResult.kind === 'error') {
|
|
228
|
+
return { success: false, error: selectionResult.message };
|
|
229
|
+
}
|
|
230
|
+
const templateFilename = selectionResult.choice.template;
|
|
231
|
+
const lxcTemplate = buildTemplatePath(templateStorage, templateFilename);
|
|
232
|
+
|
|
233
|
+
// Check if the template is already cached in the chosen storage.
|
|
234
|
+
console.log(`\nChecking if template '${templateFilename}' exists...`);
|
|
235
|
+
const templatesResult = await listAvailableTemplates(
|
|
236
|
+
credentials,
|
|
237
|
+
targetNode,
|
|
238
|
+
templateStorage,
|
|
239
|
+
);
|
|
223
240
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
241
|
+
let templateExists = false;
|
|
242
|
+
if (templatesResult.success) {
|
|
243
|
+
templateExists = templatesResult.data.some((t) => t.volid.includes(templateFilename));
|
|
244
|
+
}
|
|
228
245
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
console.log(`✓ Service '${service.serviceId}' saved\n`);
|
|
244
|
-
|
|
245
|
-
let templateReady = templateExists;
|
|
246
|
-
|
|
247
|
-
if (!templateExists) {
|
|
248
|
-
console.log(`✗ Template '${templateFilename}' not found in storage '${templateStorage}'`);
|
|
249
|
-
|
|
250
|
-
const shouldDownload = await p.confirm({
|
|
251
|
-
message: 'Download template now?',
|
|
252
|
-
initialValue: true,
|
|
246
|
+
// Save the service FIRST so credentials aren't lost if the download fails.
|
|
247
|
+
const service = await addContainerService({
|
|
248
|
+
name,
|
|
249
|
+
providerName: 'proxmox',
|
|
250
|
+
zones: zones as unknown as NetworkZone[],
|
|
251
|
+
apiCredentials: credentials,
|
|
252
|
+
providerConfig: {
|
|
253
|
+
default_target_node: targetNode,
|
|
254
|
+
lxc_template: lxcTemplate,
|
|
255
|
+
storage,
|
|
256
|
+
...(vmTemplate ? { vm_template: vmTemplate } : {}),
|
|
257
|
+
},
|
|
253
258
|
});
|
|
254
259
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
260
|
+
console.log(`✓ Service '${service.serviceId}' saved\n`);
|
|
261
|
+
|
|
262
|
+
let templateReady = templateExists;
|
|
263
|
+
|
|
264
|
+
if (!templateExists) {
|
|
265
|
+
console.log(`✗ Template '${templateFilename}' not found in storage '${templateStorage}'`);
|
|
259
266
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
templateFilename,
|
|
267
|
+
const shouldDownload = await askConfirm({
|
|
268
|
+
scope,
|
|
269
|
+
key: 'download_template',
|
|
270
|
+
message: 'Download template now?',
|
|
271
|
+
defaultValue: true,
|
|
266
272
|
});
|
|
267
|
-
|
|
273
|
+
|
|
274
|
+
if (shouldDownload) {
|
|
275
|
+
const downloadOutcome = await runApplianceDownload({
|
|
276
|
+
credentials,
|
|
277
|
+
targetNode,
|
|
278
|
+
templateStorage,
|
|
279
|
+
templateFilename,
|
|
280
|
+
});
|
|
281
|
+
templateReady = downloadOutcome.ready;
|
|
282
|
+
} else {
|
|
283
|
+
console.log(
|
|
284
|
+
'\nTemplate not downloaded. Service saved but will need a template before deployment.',
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!templateReady) {
|
|
289
|
+
console.log(
|
|
290
|
+
'\nTemplate is not available. The service has been saved but needs a template.\n',
|
|
291
|
+
);
|
|
292
|
+
console.log('Next steps:');
|
|
293
|
+
console.log(
|
|
294
|
+
` 1. Try a different version: celilo service reconfigure ${service.serviceId}`,
|
|
295
|
+
);
|
|
296
|
+
console.log(
|
|
297
|
+
` 2. Download manually: ssh root@${ipAddress} pveam download ${templateStorage} ${templateFilename}`,
|
|
298
|
+
);
|
|
299
|
+
console.log(` 3. Then verify: celilo service verify ${service.serviceId}`);
|
|
300
|
+
}
|
|
268
301
|
} else {
|
|
269
|
-
console.log(
|
|
270
|
-
'\nTemplate not downloaded. Service saved but will need a template before deployment.',
|
|
271
|
-
);
|
|
302
|
+
console.log(`✓ Template '${templateFilename}' found`);
|
|
272
303
|
}
|
|
273
304
|
|
|
274
|
-
|
|
305
|
+
// Test connection
|
|
306
|
+
console.log('\nTesting connection...');
|
|
307
|
+
const testResult = await testServiceConnection(service);
|
|
308
|
+
await updateVerificationStatus(service.id, testResult);
|
|
309
|
+
|
|
310
|
+
if (!testResult.success) {
|
|
311
|
+
console.log(`✗ Connection test failed: ${testResult.message}`);
|
|
275
312
|
console.log(
|
|
276
|
-
'\
|
|
313
|
+
'\nService saved but not verified. The service will not be used until verification succeeds.',
|
|
277
314
|
);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
`
|
|
315
|
+
|
|
316
|
+
celiloOutro(
|
|
317
|
+
`Service '${service.serviceId}' (${name}) added but not verified.\n\nNext steps:\n - Fix the connection issue\n - Re-verify: celilo service verify ${service.serviceId}\n - Check status: celilo service list`,
|
|
281
318
|
);
|
|
282
|
-
|
|
283
|
-
|
|
319
|
+
} else {
|
|
320
|
+
console.log(`✓ ${testResult.message}`);
|
|
321
|
+
|
|
322
|
+
celiloOutro(
|
|
323
|
+
`Service '${service.serviceId}' (${name}) added and verified successfully!\n\nService ID: ${service.serviceId}\n\nNext steps:\n - Generate module: celilo module generate <module-id>\n - List services: celilo service list`,
|
|
284
324
|
);
|
|
285
|
-
console.log(` 3. Then verify: celilo service verify ${service.serviceId}`);
|
|
286
325
|
}
|
|
287
|
-
} else {
|
|
288
|
-
console.log(`✓ Template '${templateFilename}' found`);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Test connection
|
|
292
|
-
console.log('\nTesting connection...');
|
|
293
|
-
const testResult = await testServiceConnection(service);
|
|
294
|
-
await updateVerificationStatus(service.id, testResult);
|
|
295
|
-
|
|
296
|
-
if (!testResult.success) {
|
|
297
|
-
console.log(`✗ Connection test failed: ${testResult.message}`);
|
|
298
|
-
console.log(
|
|
299
|
-
'\nService saved but not verified. The service will not be used until verification succeeds.',
|
|
300
|
-
);
|
|
301
326
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
`
|
|
310
|
-
|
|
327
|
+
return {
|
|
328
|
+
success: true,
|
|
329
|
+
message: `Added Proxmox service: ${service.id}`,
|
|
330
|
+
};
|
|
331
|
+
} catch (error) {
|
|
332
|
+
return {
|
|
333
|
+
success: false,
|
|
334
|
+
error: `Failed to add Proxmox service: ${error instanceof Error ? error.message : String(error)}`,
|
|
335
|
+
};
|
|
311
336
|
}
|
|
312
|
-
|
|
313
|
-
return {
|
|
314
|
-
success: true,
|
|
315
|
-
message: `Added Proxmox service: ${service.id}`,
|
|
316
|
-
};
|
|
317
|
-
} catch (error) {
|
|
318
|
-
return {
|
|
319
|
-
success: false,
|
|
320
|
-
error: `Failed to add Proxmox service: ${error instanceof Error ? error.message : String(error)}`,
|
|
321
|
-
};
|
|
322
|
-
}
|
|
337
|
+
});
|
|
323
338
|
}
|