@celilo/cli 0.3.16 → 0.3.17
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 +1 -1
- package/src/api-clients/proxmox.ts +77 -45
- package/src/cli/command-registry.ts +12 -12
- package/src/cli/commands/completion.ts +12 -11
- package/src/cli/commands/module-check.ts +158 -0
- package/src/cli/commands/module-import.ts +5 -5
- package/src/cli/commands/module-publish.test.ts +3 -90
- package/src/cli/commands/module-publish.ts +14 -118
- package/src/cli/commands/proxmox-template-selection.test.ts +150 -0
- package/src/cli/commands/proxmox-template-selection.ts +258 -0
- package/src/cli/commands/service-add-proxmox.ts +49 -127
- package/src/cli/commands/service-reconfigure.ts +36 -79
- package/src/cli/commands/service-verify.ts +20 -79
- package/src/cli/commands/{doctor.test.ts → system-doctor.test.ts} +1 -1
- package/src/cli/commands/{doctor.ts → system-doctor.ts} +93 -18
- package/src/cli/completion.ts +29 -2
- package/src/cli/index.ts +16 -7
- package/src/module/import.ts +4 -2
- package/src/registry/client.ts +14 -1
- package/src/services/module-deploy.ts +19 -1
- package/src/services/module-validator/capability-versions.test.ts +90 -0
- package/src/services/module-validator/capability-versions.ts +115 -0
- package/src/services/module-validator/contract-version.test.ts +24 -0
- package/src/services/module-validator/contract-version.ts +69 -0
- package/src/services/module-validator/git-hygiene.test.ts +141 -0
- package/src/services/module-validator/git-hygiene.ts +144 -0
- package/src/services/module-validator/index.test.ts +67 -0
- package/src/services/module-validator/index.ts +74 -0
- package/src/services/module-validator/manifest-schema.ts +42 -0
- package/src/services/module-validator/types.ts +43 -0
- package/src/services/module-validator/typescript-build.test.ts +58 -0
- package/src/services/module-validator/typescript-build.ts +115 -0
- package/src/services/module-validator/workspace-deps.test.ts +137 -0
- package/src/services/module-validator/workspace-deps.ts +187 -0
- package/src/system/prereqs.test.ts +374 -0
- package/src/system/prereqs.ts +377 -0
|
@@ -7,10 +7,6 @@ import * as p from '@clack/prompts';
|
|
|
7
7
|
import {
|
|
8
8
|
buildProxmoxApiUrl,
|
|
9
9
|
buildTemplatePath,
|
|
10
|
-
buildTemplateUrl,
|
|
11
|
-
checkTaskStatus,
|
|
12
|
-
downloadTemplate,
|
|
13
|
-
extractTemplateFilename,
|
|
14
10
|
listAvailableTemplates,
|
|
15
11
|
listNodeStorage,
|
|
16
12
|
} from '../../api-clients/proxmox';
|
|
@@ -20,10 +16,13 @@ import {
|
|
|
20
16
|
testConnection as testServiceConnection,
|
|
21
17
|
updateVerificationStatus,
|
|
22
18
|
} from '../../services/container-service';
|
|
23
|
-
import { FuelGauge } from '../fuel-gauge';
|
|
24
19
|
import { celiloIntro, celiloOutro, promptPassword, promptText } from '../prompts';
|
|
25
20
|
import type { CommandResult } from '../types';
|
|
26
21
|
import { validateRequired } from '../validators';
|
|
22
|
+
import {
|
|
23
|
+
runApplianceDownload,
|
|
24
|
+
selectUbuntuApplianceFromCatalog,
|
|
25
|
+
} from './proxmox-template-selection';
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
28
|
* Handle service add proxmox command
|
|
@@ -123,35 +122,19 @@ export async function handleServiceAddProxmox(
|
|
|
123
122
|
validate: validateRequired('Storage'),
|
|
124
123
|
});
|
|
125
124
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
if (p.isCancel(ubuntuVersion)) {
|
|
137
|
-
p.cancel('Operation cancelled');
|
|
138
|
-
return { success: false, error: 'Cancelled by user' };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Find storage that supports vztmpl content
|
|
125
|
+
// Find storage that supports vztmpl content. We do this BEFORE prompting
|
|
126
|
+
// for a template so the user only ever sees one storage in subsequent
|
|
127
|
+
// messages, and so the saved volid uses the right storage.
|
|
128
|
+
const credentials = {
|
|
129
|
+
api_url: apiUrl,
|
|
130
|
+
api_token_id: apiTokenId,
|
|
131
|
+
api_token_secret: apiTokenSecret,
|
|
132
|
+
};
|
|
142
133
|
console.log('\nFinding storage for templates...');
|
|
143
|
-
const storageListResult = await listNodeStorage(
|
|
144
|
-
{
|
|
145
|
-
api_url: apiUrl,
|
|
146
|
-
api_token_id: apiTokenId,
|
|
147
|
-
api_token_secret: apiTokenSecret,
|
|
148
|
-
},
|
|
149
|
-
targetNode,
|
|
150
|
-
);
|
|
134
|
+
const storageListResult = await listNodeStorage(credentials, targetNode);
|
|
151
135
|
|
|
152
|
-
let templateStorage = 'local';
|
|
136
|
+
let templateStorage = 'local';
|
|
153
137
|
if (storageListResult.success) {
|
|
154
|
-
// Find first active storage that supports 'vztmpl' content
|
|
155
138
|
const vztmplStorage = storageListResult.data.find(
|
|
156
139
|
(s) => s.active && s.enabled && s.content.includes('vztmpl'),
|
|
157
140
|
);
|
|
@@ -163,37 +146,34 @@ export async function handleServiceAddProxmox(
|
|
|
163
146
|
}
|
|
164
147
|
}
|
|
165
148
|
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
const
|
|
149
|
+
// Pick a template from Proxmox's live catalog (replaces the old hardcoded
|
|
150
|
+
// version select that built `-1`-revision URLs by hand).
|
|
151
|
+
const selectionResult = await selectUbuntuApplianceFromCatalog(credentials, targetNode);
|
|
152
|
+
if (selectionResult.kind === 'cancelled') {
|
|
153
|
+
p.cancel('Operation cancelled');
|
|
154
|
+
return { success: false, error: 'Cancelled by user' };
|
|
155
|
+
}
|
|
156
|
+
if (selectionResult.kind === 'error') {
|
|
157
|
+
return { success: false, error: selectionResult.message };
|
|
158
|
+
}
|
|
159
|
+
const templateFilename = selectionResult.choice.template;
|
|
160
|
+
const lxcTemplate = buildTemplatePath(templateStorage, templateFilename);
|
|
169
161
|
|
|
170
|
-
// Check if template
|
|
162
|
+
// Check if the template is already cached in the chosen storage.
|
|
171
163
|
console.log(`\nChecking if template '${templateFilename}' exists...`);
|
|
172
|
-
const templatesResult = await listAvailableTemplates(
|
|
173
|
-
{
|
|
174
|
-
api_url: apiUrl,
|
|
175
|
-
api_token_id: apiTokenId,
|
|
176
|
-
api_token_secret: apiTokenSecret,
|
|
177
|
-
},
|
|
178
|
-
targetNode,
|
|
179
|
-
templateStorage,
|
|
180
|
-
);
|
|
164
|
+
const templatesResult = await listAvailableTemplates(credentials, targetNode, templateStorage);
|
|
181
165
|
|
|
182
166
|
let templateExists = false;
|
|
183
167
|
if (templatesResult.success) {
|
|
184
168
|
templateExists = templatesResult.data.some((t) => t.volid.includes(templateFilename));
|
|
185
169
|
}
|
|
186
170
|
|
|
187
|
-
// Save the service FIRST so credentials aren't lost if
|
|
171
|
+
// Save the service FIRST so credentials aren't lost if the download fails.
|
|
188
172
|
const service = await addContainerService({
|
|
189
173
|
name,
|
|
190
174
|
providerName: 'proxmox',
|
|
191
175
|
zones: zones as unknown as NetworkZone[],
|
|
192
|
-
apiCredentials:
|
|
193
|
-
api_url: apiUrl,
|
|
194
|
-
api_token_id: apiTokenId,
|
|
195
|
-
api_token_secret: apiTokenSecret,
|
|
196
|
-
},
|
|
176
|
+
apiCredentials: credentials,
|
|
197
177
|
providerConfig: {
|
|
198
178
|
default_target_node: targetNode,
|
|
199
179
|
lxc_template: lxcTemplate,
|
|
@@ -203,7 +183,6 @@ export async function handleServiceAddProxmox(
|
|
|
203
183
|
|
|
204
184
|
console.log(`✓ Service '${service.serviceId}' saved\n`);
|
|
205
185
|
|
|
206
|
-
// Now attempt template download (service is already saved)
|
|
207
186
|
let templateReady = templateExists;
|
|
208
187
|
|
|
209
188
|
if (!templateExists) {
|
|
@@ -220,89 +199,32 @@ export async function handleServiceAddProxmox(
|
|
|
220
199
|
}
|
|
221
200
|
|
|
222
201
|
if (shouldDownload) {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
const downloadResult = await downloadTemplate(
|
|
226
|
-
{
|
|
227
|
-
api_url: apiUrl,
|
|
228
|
-
api_token_id: apiTokenId,
|
|
229
|
-
api_token_secret: apiTokenSecret,
|
|
230
|
-
},
|
|
202
|
+
const downloadOutcome = await runApplianceDownload({
|
|
203
|
+
credentials,
|
|
231
204
|
targetNode,
|
|
232
205
|
templateStorage,
|
|
233
|
-
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
if (!downloadResult.success) {
|
|
237
|
-
console.log(`\n✗ Failed to start download: ${downloadResult.message}`);
|
|
238
|
-
} else {
|
|
239
|
-
// Wait for download to complete with fuel-gauge progress
|
|
240
|
-
const upid = downloadResult.data;
|
|
241
|
-
const gauge = new FuelGauge(`Downloading ${templateFilename}`);
|
|
242
|
-
gauge.start();
|
|
243
|
-
|
|
244
|
-
let downloadComplete = false;
|
|
245
|
-
let attempts = 0;
|
|
246
|
-
const maxAttempts = 60;
|
|
247
|
-
|
|
248
|
-
while (!downloadComplete && attempts < maxAttempts) {
|
|
249
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
250
|
-
|
|
251
|
-
const statusResult = await checkTaskStatus(
|
|
252
|
-
{
|
|
253
|
-
api_url: apiUrl,
|
|
254
|
-
api_token_id: apiTokenId,
|
|
255
|
-
api_token_secret: apiTokenSecret,
|
|
256
|
-
},
|
|
257
|
-
targetNode,
|
|
258
|
-
upid,
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
if (statusResult.success) {
|
|
262
|
-
if (statusResult.data.status === 'stopped') {
|
|
263
|
-
if (statusResult.data.exitstatus === 'OK') {
|
|
264
|
-
downloadComplete = true;
|
|
265
|
-
templateReady = true;
|
|
266
|
-
gauge.stop(true);
|
|
267
|
-
} else {
|
|
268
|
-
gauge.stop(false);
|
|
269
|
-
console.log(`\n✗ Template download failed: ${statusResult.data.exitstatus}`);
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
} else {
|
|
273
|
-
gauge.addOutput(`Status: ${statusResult.data.status} (${attempts * 5}s elapsed)`);
|
|
274
|
-
}
|
|
275
|
-
} else {
|
|
276
|
-
gauge.addOutput(`Waiting for status... (${attempts * 5}s elapsed)`);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
attempts++;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (!downloadComplete && !templateReady) {
|
|
283
|
-
gauge.stop(false);
|
|
284
|
-
console.log('\n✗ Template download timed out after 5 minutes');
|
|
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
|
-
}
|
|
206
|
+
templateFilename,
|
|
207
|
+
});
|
|
208
|
+
templateReady = downloadOutcome.ready;
|
|
301
209
|
} else {
|
|
302
210
|
console.log(
|
|
303
211
|
'\nTemplate not downloaded. Service saved but will need a template before deployment.',
|
|
304
212
|
);
|
|
305
213
|
}
|
|
214
|
+
|
|
215
|
+
if (!templateReady) {
|
|
216
|
+
console.log(
|
|
217
|
+
'\nTemplate is not available. The service has been saved but needs a template.\n',
|
|
218
|
+
);
|
|
219
|
+
console.log('Next steps:');
|
|
220
|
+
console.log(
|
|
221
|
+
` 1. Try a different version: celilo service reconfigure ${service.serviceId}`,
|
|
222
|
+
);
|
|
223
|
+
console.log(
|
|
224
|
+
` 2. Download manually: ssh root@${ipAddress} pveam download ${templateStorage} ${templateFilename}`,
|
|
225
|
+
);
|
|
226
|
+
console.log(` 3. Then verify: celilo service verify ${service.serviceId}`);
|
|
227
|
+
}
|
|
306
228
|
} else {
|
|
307
229
|
console.log(`✓ Template '${templateFilename}' found`);
|
|
308
230
|
}
|
|
@@ -5,29 +5,24 @@
|
|
|
5
5
|
|
|
6
6
|
import * as p from '@clack/prompts';
|
|
7
7
|
import {
|
|
8
|
+
type ProxmoxCredentials,
|
|
8
9
|
buildTemplatePath,
|
|
9
|
-
buildTemplateUrl,
|
|
10
|
-
checkTaskStatus,
|
|
11
|
-
downloadTemplate,
|
|
12
10
|
extractTemplateFilename,
|
|
13
11
|
listAvailableTemplates,
|
|
14
12
|
listNodeStorage,
|
|
15
13
|
} from '../../api-clients/proxmox';
|
|
16
14
|
import {
|
|
17
|
-
|
|
15
|
+
getContainerServiceByServiceId,
|
|
18
16
|
getServiceCredentials,
|
|
19
17
|
updateServiceProviderConfig,
|
|
20
18
|
} from '../../services/container-service';
|
|
21
|
-
import { FuelGauge } from '../fuel-gauge';
|
|
22
19
|
import { celiloIntro, celiloOutro, promptText } from '../prompts';
|
|
23
20
|
import type { CommandResult } from '../types';
|
|
24
21
|
import { validateRequired } from '../validators';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
api_token_secret: string;
|
|
30
|
-
}
|
|
22
|
+
import {
|
|
23
|
+
runApplianceDownload,
|
|
24
|
+
selectUbuntuApplianceFromCatalog,
|
|
25
|
+
} from './proxmox-template-selection';
|
|
31
26
|
|
|
32
27
|
interface ProxmoxProviderConfig {
|
|
33
28
|
default_target_node: string;
|
|
@@ -48,7 +43,7 @@ export async function handleServiceReconfigure(
|
|
|
48
43
|
}
|
|
49
44
|
|
|
50
45
|
try {
|
|
51
|
-
const service = await
|
|
46
|
+
const service = await getContainerServiceByServiceId(serviceId);
|
|
52
47
|
if (!service) {
|
|
53
48
|
return { success: false, error: `Service '${serviceId}' not found` };
|
|
54
49
|
}
|
|
@@ -87,27 +82,8 @@ export async function handleServiceReconfigure(
|
|
|
87
82
|
validate: validateRequired('Storage'),
|
|
88
83
|
});
|
|
89
84
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
const versionMatch = currentTemplateFile.match(/ubuntu-(\d+\.\d+)-/);
|
|
93
|
-
const currentVersion = versionMatch?.[1] || '22.04';
|
|
94
|
-
|
|
95
|
-
const ubuntuVersion = await p.select({
|
|
96
|
-
message: 'Ubuntu LTS version for default template:',
|
|
97
|
-
options: [
|
|
98
|
-
{ value: '24.04', label: 'Ubuntu 24.04 LTS (Noble Numbat)' },
|
|
99
|
-
{ value: '22.04', label: 'Ubuntu 22.04 LTS (Jammy Jellyfish)' },
|
|
100
|
-
{ value: '20.04', label: 'Ubuntu 20.04 LTS (Focal Fossa)' },
|
|
101
|
-
],
|
|
102
|
-
initialValue: currentVersion,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
if (p.isCancel(ubuntuVersion)) {
|
|
106
|
-
p.cancel('Operation cancelled');
|
|
107
|
-
return { success: false, error: 'Cancelled by user' };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Find template storage
|
|
85
|
+
// Find template storage. Done before catalog selection so the user only
|
|
86
|
+
// ever sees the chosen storage in subsequent messages.
|
|
111
87
|
console.log('\nFinding storage for templates...');
|
|
112
88
|
const storageListResult = await listNodeStorage(credentials, targetNode);
|
|
113
89
|
|
|
@@ -122,10 +98,22 @@ export async function handleServiceReconfigure(
|
|
|
122
98
|
}
|
|
123
99
|
}
|
|
124
100
|
|
|
125
|
-
|
|
126
|
-
|
|
101
|
+
// Pick a template from Proxmox's live catalog. Pre-selects the family
|
|
102
|
+
// matching the existing volid (e.g. user has 24.04 -1 cached, mirror has -2).
|
|
103
|
+
const selectionResult = await selectUbuntuApplianceFromCatalog(credentials, targetNode, {
|
|
104
|
+
currentTemplate: extractTemplateFilename(currentConfig.lxc_template),
|
|
105
|
+
});
|
|
106
|
+
if (selectionResult.kind === 'cancelled') {
|
|
107
|
+
p.cancel('Operation cancelled');
|
|
108
|
+
return { success: false, error: 'Cancelled by user' };
|
|
109
|
+
}
|
|
110
|
+
if (selectionResult.kind === 'error') {
|
|
111
|
+
return { success: false, error: selectionResult.message };
|
|
112
|
+
}
|
|
113
|
+
const templateFilename = selectionResult.choice.template;
|
|
114
|
+
const lxcTemplate = buildTemplatePath(templateStorage, templateFilename);
|
|
127
115
|
|
|
128
|
-
// Check if new template exists
|
|
116
|
+
// Check if new template exists in storage.
|
|
129
117
|
console.log(`\nChecking if template '${templateFilename}' exists...`);
|
|
130
118
|
const templatesResult = await listAvailableTemplates(credentials, targetNode, templateStorage);
|
|
131
119
|
|
|
@@ -147,54 +135,23 @@ export async function handleServiceReconfigure(
|
|
|
147
135
|
'\nTemplate not downloaded. Service will be updated but may fail verification.',
|
|
148
136
|
);
|
|
149
137
|
} else {
|
|
150
|
-
const
|
|
151
|
-
const downloadResult = await downloadTemplate(
|
|
138
|
+
const outcome = await runApplianceDownload({
|
|
152
139
|
credentials,
|
|
153
140
|
targetNode,
|
|
154
141
|
templateStorage,
|
|
155
|
-
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
if (!
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const upid = downloadResult.data;
|
|
166
|
-
const gauge = new FuelGauge(`Downloading ${templateFilename}`);
|
|
167
|
-
gauge.start();
|
|
168
|
-
|
|
169
|
-
let downloadComplete = false;
|
|
170
|
-
let attempts = 0;
|
|
171
|
-
|
|
172
|
-
while (!downloadComplete && attempts < 60) {
|
|
173
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
174
|
-
const statusResult = await checkTaskStatus(credentials, targetNode, upid);
|
|
175
|
-
|
|
176
|
-
if (statusResult.success && statusResult.data.status === 'stopped') {
|
|
177
|
-
if (statusResult.data.exitstatus === 'OK') {
|
|
178
|
-
downloadComplete = true;
|
|
179
|
-
gauge.stop(true);
|
|
180
|
-
} else {
|
|
181
|
-
gauge.stop(false);
|
|
182
|
-
return {
|
|
183
|
-
success: false,
|
|
184
|
-
error: `Template download failed: ${statusResult.data.exitstatus}\n\nThis usually means the Proxmox host cannot reach download.proxmox.com.\n\nTroubleshooting:\n 1. Try a different Ubuntu version: celilo service reconfigure ${serviceId}\n 2. Download manually: pveam download ${templateStorage} ${templateFilename}\n 3. Check DNS and firewall settings on the Proxmox host`,
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
} else {
|
|
188
|
-
gauge.addOutput(`Status: downloading... (${attempts * 5}s elapsed)`);
|
|
189
|
-
}
|
|
190
|
-
attempts++;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (!downloadComplete) {
|
|
194
|
-
gauge.stop(false);
|
|
142
|
+
templateFilename,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (!outcome.ready) {
|
|
146
|
+
const detail =
|
|
147
|
+
outcome.reason === 'task-failed'
|
|
148
|
+
? `pveam download exited with status: ${outcome.exitStatus ?? 'unknown'}`
|
|
149
|
+
: outcome.reason === 'started-failed'
|
|
150
|
+
? `Proxmox rejected the download request: ${outcome.startError ?? 'unknown error'}`
|
|
151
|
+
: 'Template download did not complete in time';
|
|
195
152
|
return {
|
|
196
153
|
success: false,
|
|
197
|
-
error:
|
|
154
|
+
error: `${detail}\n\nTroubleshooting:\n 1. SSH into your Proxmox host and run: pveam update && pveam download ${templateStorage} ${templateFilename}\n 2. Re-try: celilo service reconfigure ${serviceId}\n 3. Check DNS and firewall settings on the Proxmox host`,
|
|
198
155
|
};
|
|
199
156
|
}
|
|
200
157
|
}
|
|
@@ -4,21 +4,16 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import * as p from '@clack/prompts';
|
|
7
|
-
import {
|
|
8
|
-
buildTemplateUrl,
|
|
9
|
-
checkTaskStatus,
|
|
10
|
-
downloadTemplate,
|
|
11
|
-
extractTemplateFilename,
|
|
12
|
-
} from '../../api-clients/proxmox';
|
|
7
|
+
import { extractTemplateFilename } from '../../api-clients/proxmox';
|
|
13
8
|
import {
|
|
14
9
|
type ProxmoxCredentials,
|
|
15
10
|
getContainerServiceByServiceId,
|
|
16
11
|
getServiceCredentials,
|
|
17
12
|
verifyContainerService,
|
|
18
13
|
} from '../../services/container-service';
|
|
19
|
-
import { FuelGauge } from '../fuel-gauge';
|
|
20
14
|
import { celiloIntro, celiloOutro } from '../prompts';
|
|
21
15
|
import type { CommandResult } from '../types';
|
|
16
|
+
import { runApplianceDownload } from './proxmox-template-selection';
|
|
22
17
|
|
|
23
18
|
/**
|
|
24
19
|
* Handle service verify command
|
|
@@ -108,7 +103,6 @@ export async function handleServiceVerify(
|
|
|
108
103
|
|
|
109
104
|
if (shouldDownload) {
|
|
110
105
|
try {
|
|
111
|
-
// Get credentials and config
|
|
112
106
|
const credentials = (await getServiceCredentials(service.id)) as ProxmoxCredentials;
|
|
113
107
|
const providerConfig = service.providerConfig as {
|
|
114
108
|
default_target_node: string;
|
|
@@ -116,86 +110,33 @@ export async function handleServiceVerify(
|
|
|
116
110
|
storage: string;
|
|
117
111
|
};
|
|
118
112
|
|
|
119
|
-
// Extract template info
|
|
120
113
|
const templateFilename = extractTemplateFilename(providerConfig.lxc_template);
|
|
121
|
-
const
|
|
122
|
-
const templateStorage = templateParts[0] || 'local';
|
|
123
|
-
|
|
124
|
-
// Detect Ubuntu version from template filename
|
|
125
|
-
const versionMatch = templateFilename.match(/ubuntu-(\d+\.\d+)-/);
|
|
126
|
-
if (!versionMatch) {
|
|
127
|
-
return {
|
|
128
|
-
success: false,
|
|
129
|
-
error: 'Cannot auto-download: unrecognized template format',
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
const ubuntuVersion = versionMatch[1];
|
|
133
|
-
|
|
134
|
-
const templateUrl = buildTemplateUrl(ubuntuVersion);
|
|
114
|
+
const templateStorage = providerConfig.lxc_template.split(':')[0] || 'local';
|
|
135
115
|
|
|
136
|
-
|
|
116
|
+
// The saved volid was the canonical filename when the service was
|
|
117
|
+
// created; if Proxmox refreshed the revision since then, this download
|
|
118
|
+
// will fail with `started-failed` and the user can `service reconfigure`
|
|
119
|
+
// to pick a fresh filename from the catalog.
|
|
120
|
+
const outcome = await runApplianceDownload({
|
|
137
121
|
credentials,
|
|
138
|
-
providerConfig.default_target_node,
|
|
122
|
+
targetNode: providerConfig.default_target_node,
|
|
139
123
|
templateStorage,
|
|
140
|
-
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
if (!
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Wait for download to complete with fuel-gauge progress
|
|
151
|
-
const upid = downloadResult.data;
|
|
152
|
-
const gauge = new FuelGauge(`Downloading ${templateFilename}`);
|
|
153
|
-
gauge.start();
|
|
154
|
-
|
|
155
|
-
let downloadComplete = false;
|
|
156
|
-
let attempts = 0;
|
|
157
|
-
const maxAttempts = 60; // 5 minutes
|
|
158
|
-
|
|
159
|
-
while (!downloadComplete && attempts < maxAttempts) {
|
|
160
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
161
|
-
|
|
162
|
-
const statusResult = await checkTaskStatus(
|
|
163
|
-
credentials,
|
|
164
|
-
providerConfig.default_target_node,
|
|
165
|
-
upid,
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
if (statusResult.success) {
|
|
169
|
-
if (statusResult.data.status === 'stopped') {
|
|
170
|
-
if (statusResult.data.exitstatus === 'OK') {
|
|
171
|
-
downloadComplete = true;
|
|
172
|
-
gauge.stop(true);
|
|
173
|
-
} else {
|
|
174
|
-
gauge.stop(false);
|
|
175
|
-
return {
|
|
176
|
-
success: false,
|
|
177
|
-
error: `Template download failed: ${statusResult.data.exitstatus}\n\nThis usually means the Proxmox host cannot reach download.proxmox.com.\n\nTroubleshooting:\n 1. SSH into your Proxmox host and test: curl -I https://download.proxmox.com\n 2. Try downloading manually: pveam download ${templateStorage} ${templateFilename}\n 3. Choose a different Ubuntu version: celilo service reconfigure ${service.serviceId}\n 4. Check DNS and firewall settings on the Proxmox host`,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
} else {
|
|
181
|
-
gauge.addOutput(`Status: ${statusResult.data.status} (${attempts * 5}s elapsed)`);
|
|
182
|
-
}
|
|
183
|
-
} else {
|
|
184
|
-
gauge.addOutput(`Waiting for status... (${attempts * 5}s elapsed)`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
attempts++;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (!downloadComplete) {
|
|
191
|
-
gauge.stop(false);
|
|
124
|
+
templateFilename,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (!outcome.ready) {
|
|
128
|
+
const detail =
|
|
129
|
+
outcome.reason === 'task-failed'
|
|
130
|
+
? `pveam download exited with status: ${outcome.exitStatus ?? 'unknown'}\n\nThis usually means the saved template revision is no longer available on Proxmox's mirror.`
|
|
131
|
+
: outcome.reason === 'started-failed'
|
|
132
|
+
? `Proxmox rejected the download request: ${outcome.startError ?? 'unknown error'}\n\nThis usually means the saved template name does not match Proxmox's current catalog.`
|
|
133
|
+
: 'Template download did not complete in time. The Proxmox host may have slow internet or connectivity issues.';
|
|
192
134
|
return {
|
|
193
135
|
success: false,
|
|
194
|
-
error:
|
|
136
|
+
error: `${detail}\n\nTroubleshooting:\n 1. Pick a fresh template version: celilo service reconfigure ${service.serviceId}\n 2. SSH into your Proxmox host and run: pveam update && pveam download ${templateStorage} ${templateFilename}\n 3. Check DNS and firewall settings on the Proxmox host`,
|
|
195
137
|
};
|
|
196
138
|
}
|
|
197
139
|
|
|
198
|
-
// Retry verification
|
|
199
140
|
console.log('\nRetrying verification...');
|
|
200
141
|
const retryResult = await verifyContainerService(service.id);
|
|
201
142
|
|