@underpostnet/underpost 2.99.8 → 3.0.1
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/.env.production +1 -0
- package/.github/workflows/gitlab.ci.yml +20 -0
- package/.github/workflows/publish.ci.yml +18 -34
- package/.vscode/extensions.json +8 -50
- package/.vscode/settings.json +0 -77
- package/CHANGELOG.md +116 -1
- package/{cli.md → CLI-HELP.md} +48 -41
- package/README.md +3 -3
- package/bin/build.js +1 -15
- package/bin/deploy.js +4 -133
- package/bin/file.js +10 -8
- package/bin/zed.js +63 -2
- package/jsdoc.json +1 -2
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/manifests/deployment/fastapi/initial_data.sh +4 -52
- package/manifests/ipfs/configmap.yaml +57 -0
- package/manifests/ipfs/headless-service.yaml +35 -0
- package/manifests/ipfs/kustomization.yaml +8 -0
- package/manifests/ipfs/statefulset.yaml +149 -0
- package/manifests/ipfs/storage-class.yaml +9 -0
- package/package.json +9 -5
- package/scripts/k3s-node-setup.sh +89 -0
- package/scripts/lxd-vm-setup.sh +23 -0
- package/scripts/rocky-setup.sh +1 -13
- package/src/api/user/user.router.js +0 -47
- package/src/cli/baremetal.js +7 -9
- package/src/cli/cluster.js +72 -121
- package/src/cli/deploy.js +8 -5
- package/src/cli/index.js +31 -30
- package/src/cli/ipfs.js +184 -0
- package/src/cli/lxd.js +192 -237
- package/src/cli/repository.js +4 -1
- package/src/cli/run.js +3 -2
- package/src/client/components/core/Docs.js +92 -6
- package/src/client/components/core/VanillaJs.js +36 -25
- package/src/client/services/user/user.management.js +0 -5
- package/src/client/services/user/user.service.js +1 -1
- package/src/index.js +12 -1
- package/src/runtime/express/Express.js +3 -2
- package/src/server/client-build-docs.js +178 -41
- package/src/server/conf.js +1 -1
- package/src/server/logger.js +22 -10
- package/.vscode/zed.keymap.json +0 -39
- package/.vscode/zed.settings.json +0 -20
- package/manifests/lxd/underpost-setup.sh +0 -163
package/src/cli/lxd.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* LXD module for managing LXD virtual machines
|
|
2
|
+
* LXD module for managing LXD virtual machines as K3s nodes.
|
|
3
3
|
* @module src/cli/lxd.js
|
|
4
4
|
* @namespace UnderpostLxd
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { getNpmRootPath } from '../server/conf.js';
|
|
8
8
|
import { pbcopy, shellExec } from '../server/process.js';
|
|
9
|
+
import walk from 'ignore-walk';
|
|
9
10
|
import fs from 'fs-extra';
|
|
10
11
|
import { loggerFactory } from '../server/logger.js';
|
|
11
12
|
import Underpost from '../index.js';
|
|
@@ -16,34 +17,36 @@ const logger = loggerFactory(import.meta);
|
|
|
16
17
|
* @class UnderpostLxd
|
|
17
18
|
* @description Provides a set of static methods to interact with LXD,
|
|
18
19
|
* encapsulating common LXD operations for VM management and network testing.
|
|
20
|
+
* @memberof UnderpostLxd
|
|
19
21
|
*/
|
|
20
22
|
class UnderpostLxd {
|
|
21
23
|
static API = {
|
|
22
24
|
/**
|
|
23
25
|
* @method callback
|
|
24
|
-
* @description Main entry point for LXD operations
|
|
25
|
-
* @param {object} options
|
|
26
|
-
* @param {boolean} [options.init=false] - Initialize LXD.
|
|
27
|
-
* @param {boolean} [options.reset=false] -
|
|
28
|
-
* @param {boolean} [options.dev=false] -
|
|
26
|
+
* @description Main entry point for LXD VM operations. Each VM is a K3s node (control or worker).
|
|
27
|
+
* @param {object} options
|
|
28
|
+
* @param {boolean} [options.init=false] - Initialize LXD via preseed.
|
|
29
|
+
* @param {boolean} [options.reset=false] - Remove LXD snap and purge data.
|
|
30
|
+
* @param {boolean} [options.dev=false] - Use local paths instead of npm global.
|
|
29
31
|
* @param {boolean} [options.install=false] - Install LXD snap.
|
|
30
|
-
* @param {boolean} [options.createVirtualNetwork=false] - Create
|
|
32
|
+
* @param {boolean} [options.createVirtualNetwork=false] - Create lxdbr0 bridge network.
|
|
33
|
+
* @param {string} [options.ipv4Address='10.250.250.1/24'] - IPv4 address/CIDR for the lxdbr0 bridge network.
|
|
31
34
|
* @param {boolean} [options.createAdminProfile=false] - Create admin-profile for VMs.
|
|
32
|
-
* @param {boolean} [options.control=false] -
|
|
33
|
-
* @param {boolean} [options.worker=false] -
|
|
34
|
-
* @param {
|
|
35
|
-
* @param {string} [options.
|
|
36
|
-
* @param {string} [options.createVm=''] -
|
|
37
|
-
* @param {string} [options.infoVm=''] -
|
|
38
|
-
* @param {string} [options.rootSize=''] - Root disk size for new VMs (e.g
|
|
39
|
-
* @param {string} [options.joinNode=''] - Join
|
|
40
|
-
* @param {string} [options.expose=''] - Expose ports
|
|
41
|
-
* @param {string} [options.deleteExpose=''] -
|
|
42
|
-
* @param {string} [options.test=''] -
|
|
43
|
-
* @param {string} [options.workflowId=''] - Workflow
|
|
44
|
-
* @param {string} [options.vmId=''] - VM
|
|
45
|
-
* @param {string} [options.deployId=''] - Deployment
|
|
46
|
-
* @param {string} [options.namespace=''] -
|
|
35
|
+
* @param {boolean} [options.control=false] - Initialize VM as K3s control plane node.
|
|
36
|
+
* @param {boolean} [options.worker=false] - Initialize VM as K3s worker node.
|
|
37
|
+
* @param {string} [options.initVm=''] - VM name to initialize as a K3s node.
|
|
38
|
+
* @param {string} [options.deleteVm=''] - VM name to stop and delete.
|
|
39
|
+
* @param {string} [options.createVm=''] - VM name to create (copies launch command to clipboard).
|
|
40
|
+
* @param {string} [options.infoVm=''] - VM name to inspect.
|
|
41
|
+
* @param {string} [options.rootSize=''] - Root disk size in GiB for new VMs (e.g. '32').
|
|
42
|
+
* @param {string} [options.joinNode=''] - Join format: 'workerName,controlName' (standalone join). When used with --init-vm --worker, just the control node name.
|
|
43
|
+
* @param {string} [options.expose=''] - Expose VM ports to host: 'vmName:port1,port2'.
|
|
44
|
+
* @param {string} [options.deleteExpose=''] - Remove exposed ports: 'vmName:port1,port2'.
|
|
45
|
+
* @param {string} [options.test=''] - VM name to run connectivity and health checks on.
|
|
46
|
+
* @param {string} [options.workflowId=''] - Workflow ID for runWorkflow.
|
|
47
|
+
* @param {string} [options.vmId=''] - VM name for workflow execution.
|
|
48
|
+
* @param {string} [options.deployId=''] - Deployment ID for workflow context.
|
|
49
|
+
* @param {string} [options.namespace=''] - Kubernetes namespace context.
|
|
47
50
|
* @memberof UnderpostLxd
|
|
48
51
|
*/
|
|
49
52
|
async callback(
|
|
@@ -53,11 +56,12 @@ class UnderpostLxd {
|
|
|
53
56
|
dev: false,
|
|
54
57
|
install: false,
|
|
55
58
|
createVirtualNetwork: false,
|
|
59
|
+
ipv4Address: '10.250.250.1/24',
|
|
56
60
|
createAdminProfile: false,
|
|
57
61
|
control: false,
|
|
58
62
|
worker: false,
|
|
59
|
-
k3s: false,
|
|
60
63
|
initVm: '',
|
|
64
|
+
deleteVm: '',
|
|
61
65
|
createVm: '',
|
|
62
66
|
infoVm: '',
|
|
63
67
|
rootSize: '',
|
|
@@ -73,77 +77,101 @@ class UnderpostLxd {
|
|
|
73
77
|
) {
|
|
74
78
|
const npmRoot = getNpmRootPath();
|
|
75
79
|
const underpostRoot = options?.dev === true ? '.' : `${npmRoot}/underpost`;
|
|
80
|
+
|
|
76
81
|
if (options.reset === true) {
|
|
77
82
|
shellExec(`sudo systemctl stop snap.lxd.daemon`);
|
|
78
83
|
shellExec(`sudo snap remove lxd --purge`);
|
|
79
84
|
}
|
|
85
|
+
|
|
80
86
|
if (options.install === true) shellExec(`sudo snap install lxd`);
|
|
87
|
+
|
|
81
88
|
if (options.init === true) {
|
|
82
89
|
shellExec(`sudo systemctl start snap.lxd.daemon`);
|
|
83
90
|
shellExec(`sudo systemctl status snap.lxd.daemon`);
|
|
84
|
-
const
|
|
91
|
+
const lxdPreseedContent = fs
|
|
85
92
|
.readFileSync(`${underpostRoot}/manifests/lxd/lxd-preseed.yaml`, 'utf8')
|
|
86
93
|
.replaceAll(`127.0.0.1`, Underpost.dns.getLocalIPv4Address());
|
|
87
|
-
shellExec(`echo "${
|
|
94
|
+
shellExec(`echo "${lxdPreseedContent}" | lxd init --preseed`);
|
|
88
95
|
shellExec(`lxc cluster list`);
|
|
89
96
|
}
|
|
97
|
+
|
|
90
98
|
if (options.createVirtualNetwork === true) {
|
|
99
|
+
const ipv4Address = options.ipv4Address ? options.ipv4Address : '10.250.250.1/24';
|
|
91
100
|
shellExec(`lxc network create lxdbr0 \
|
|
92
|
-
ipv4.address
|
|
101
|
+
ipv4.address=${ipv4Address} \
|
|
93
102
|
ipv4.nat=true \
|
|
94
103
|
ipv4.dhcp=true \
|
|
95
104
|
ipv6.address=none`);
|
|
96
105
|
}
|
|
106
|
+
|
|
97
107
|
if (options.createAdminProfile === true) {
|
|
98
108
|
const existingProfiles = await new Promise((resolve) => {
|
|
99
109
|
shellExec(`lxc profile show admin-profile`, {
|
|
100
110
|
silent: true,
|
|
101
|
-
callback: (...args) =>
|
|
102
|
-
return resolve(JSON.stringify(args));
|
|
103
|
-
},
|
|
111
|
+
callback: (...args) => resolve(JSON.stringify(args)),
|
|
104
112
|
});
|
|
105
113
|
});
|
|
106
114
|
if (existingProfiles.toLowerCase().match('error')) {
|
|
107
|
-
logger.warn('Profile does not exist.
|
|
115
|
+
logger.warn('Profile does not exist. Use the following command to create it:');
|
|
108
116
|
pbcopy(`lxc profile create admin-profile`);
|
|
109
117
|
} else {
|
|
110
118
|
shellExec(`cat ${underpostRoot}/manifests/lxd/lxd-admin-profile.yaml | lxc profile edit admin-profile`);
|
|
111
119
|
shellExec(`lxc profile show admin-profile`);
|
|
112
120
|
}
|
|
113
121
|
}
|
|
114
|
-
|
|
122
|
+
|
|
123
|
+
if (options.deleteVm) {
|
|
124
|
+
const vmName = options.deleteVm;
|
|
125
|
+
logger.info(`Stopping VM: ${vmName}`);
|
|
126
|
+
shellExec(`lxc stop ${vmName}`);
|
|
127
|
+
logger.info(`Deleting VM: ${vmName}`);
|
|
128
|
+
shellExec(`lxc delete ${vmName}`);
|
|
129
|
+
logger.info(`VM ${vmName} deleted.`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (options.createVm) {
|
|
115
133
|
pbcopy(
|
|
116
134
|
`lxc launch images:rockylinux/9 ${
|
|
117
135
|
options.createVm
|
|
118
136
|
} --vm --target lxd-node1 -c limits.cpu=2 -c limits.memory=4GB --profile admin-profile -d root,size=${
|
|
119
|
-
options.rootSize
|
|
137
|
+
options.rootSize ? options.rootSize + 'GiB' : '32GiB'
|
|
120
138
|
}`,
|
|
121
139
|
);
|
|
122
140
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (options.
|
|
138
|
-
|
|
141
|
+
|
|
142
|
+
if (options.initVm) {
|
|
143
|
+
const vmName = options.initVm;
|
|
144
|
+
const lxdSetupPath = `${underpostRoot}/scripts/lxd-vm-setup.sh`;
|
|
145
|
+
const k3sSetupPath = `${underpostRoot}/scripts/k3s-node-setup.sh`;
|
|
146
|
+
|
|
147
|
+
// Step 1: OS base setup (disk, packages, kernel modules)
|
|
148
|
+
shellExec(`cat ${lxdSetupPath} | lxc exec ${vmName} -- bash`);
|
|
149
|
+
|
|
150
|
+
// Step 2: Push engine source from host to VM
|
|
151
|
+
await Underpost.lxd.runWorkflow({ workflowId: 'engine', vmName, dev: options.dev });
|
|
152
|
+
|
|
153
|
+
// Step 3: K3s role setup (installs Node, npm deps, then k3s via node bin --dev)
|
|
154
|
+
if (options.worker === true) {
|
|
155
|
+
if (options.joinNode) {
|
|
156
|
+
const controlNode = options.joinNode.includes(',') ? options.joinNode.split(',').pop() : options.joinNode;
|
|
157
|
+
const k3sToken = shellExec(
|
|
158
|
+
`lxc exec ${controlNode} -- bash -c 'sudo cat /var/lib/rancher/k3s/server/node-token'`,
|
|
159
|
+
{ stdout: true },
|
|
160
|
+
).trim();
|
|
161
|
+
const controlPlaneIp = shellExec(
|
|
162
|
+
`lxc list ${controlNode} --format json | jq -r '.[0].state.network.enp5s0.addresses[] | select(.family=="inet") | .address'`,
|
|
163
|
+
{ stdout: true },
|
|
164
|
+
).trim();
|
|
165
|
+
logger.info(`Initializing worker ${vmName} and joining control plane ${controlNode} (${controlPlaneIp})`);
|
|
166
|
+
shellExec(
|
|
167
|
+
`cat ${k3sSetupPath} | lxc exec ${vmName} -- bash -s -- --worker --control-ip=${controlPlaneIp} --token=${k3sToken}`,
|
|
168
|
+
);
|
|
139
169
|
} else {
|
|
140
|
-
|
|
141
|
-
flag = ' -s -- --worker';
|
|
170
|
+
shellExec(`cat ${k3sSetupPath} | lxc exec ${vmName} -- bash -s -- --worker`);
|
|
142
171
|
}
|
|
172
|
+
} else {
|
|
173
|
+
shellExec(`cat ${k3sSetupPath} | lxc exec ${vmName} -- bash -s -- --control`);
|
|
143
174
|
}
|
|
144
|
-
console.log(`Executing underpost-setup.sh on VM: ${options.initVm}`);
|
|
145
|
-
shellExec(`cat ${underpostRoot}/manifests/lxd/underpost-setup.sh | lxc exec ${options.initVm} -- bash${flag}`);
|
|
146
|
-
console.log(`underpost-setup.sh execution completed on VM: ${options.initVm}`);
|
|
147
175
|
}
|
|
148
176
|
|
|
149
177
|
if (options.workflowId) {
|
|
@@ -155,241 +183,168 @@ ipv6.address=none`);
|
|
|
155
183
|
});
|
|
156
184
|
}
|
|
157
185
|
|
|
158
|
-
|
|
186
|
+
// Standalone join: --join-node workerName,controlName (without --init-vm)
|
|
187
|
+
if (options.joinNode && !options.initVm) {
|
|
159
188
|
const [workerNode, controlNode] = options.joinNode.split(',');
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
{ stdout: true },
|
|
174
|
-
).trim();
|
|
175
|
-
|
|
176
|
-
if (!k3sToken || !controlPlaneIp) {
|
|
177
|
-
console.error(`Failed to get K3s token or control plane IP. Cannot join worker.`);
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
const k3sJoinCommand = `K3S_URL=https://${controlPlaneIp}:6443 K3S_TOKEN=${k3sToken} curl -sfL https://get.k3s.io | sh -`;
|
|
181
|
-
shellExec(`lxc exec ${workerNode} -- bash -c '${k3sJoinCommand}'`);
|
|
182
|
-
console.log(`K3s worker node ${workerNode} join command executed.`);
|
|
183
|
-
} else {
|
|
184
|
-
// Kubeadm join
|
|
185
|
-
console.log(`Attempting to join Kubeadm worker node ${workerNode} to control plane ${controlNode}`);
|
|
186
|
-
const token = shellExec(
|
|
187
|
-
`echo "$(lxc exec ${controlNode} -- bash -c 'sudo kubeadm token create --print-join-command')"`,
|
|
188
|
-
{ stdout: true },
|
|
189
|
-
);
|
|
190
|
-
shellExec(`lxc exec ${workerNode} -- bash -c '${token}'`);
|
|
191
|
-
console.log(`Kubeadm worker node ${workerNode} join command executed.`);
|
|
192
|
-
}
|
|
189
|
+
const k3sToken = shellExec(
|
|
190
|
+
`lxc exec ${controlNode} -- bash -c 'sudo cat /var/lib/rancher/k3s/server/node-token'`,
|
|
191
|
+
{ stdout: true },
|
|
192
|
+
).trim();
|
|
193
|
+
const controlPlaneIp = shellExec(
|
|
194
|
+
`lxc list ${controlNode} --format json | jq -r '.[0].state.network.enp5s0.addresses[] | select(.family=="inet") | .address'`,
|
|
195
|
+
{ stdout: true },
|
|
196
|
+
).trim();
|
|
197
|
+
logger.info(`Joining K3s worker ${workerNode} to control plane ${controlNode} (${controlPlaneIp})`);
|
|
198
|
+
shellExec(
|
|
199
|
+
`lxc exec ${workerNode} -- bash -c 'K3S_URL=https://${controlPlaneIp}:6443 K3S_TOKEN=${k3sToken} curl -sfL https://get.k3s.io | sh -s - agent'`,
|
|
200
|
+
);
|
|
201
|
+
logger.info(`Worker ${workerNode} joined successfully.`);
|
|
193
202
|
}
|
|
194
|
-
|
|
203
|
+
|
|
204
|
+
if (options.infoVm) {
|
|
195
205
|
shellExec(`lxc config show ${options.infoVm}`);
|
|
196
206
|
shellExec(`lxc info --show-log ${options.infoVm}`);
|
|
197
207
|
shellExec(`lxc info ${options.infoVm}`);
|
|
198
208
|
shellExec(`lxc list ${options.infoVm}`);
|
|
199
209
|
}
|
|
200
|
-
|
|
210
|
+
|
|
211
|
+
if (options.expose) {
|
|
201
212
|
const [vmName, ports] = options.expose.split(':');
|
|
202
|
-
|
|
203
|
-
const protocols = ['tcp']; // udp
|
|
213
|
+
const protocols = ['tcp'];
|
|
204
214
|
const hostIp = Underpost.dns.getLocalIPv4Address();
|
|
205
215
|
const vmIp = shellExec(
|
|
206
216
|
`lxc list ${vmName} --format json | jq -r '.[0].state.network.enp5s0.addresses[] | select(.family=="inet") | .address'`,
|
|
207
217
|
{ stdout: true },
|
|
208
218
|
).trim();
|
|
209
219
|
if (!vmIp) {
|
|
210
|
-
|
|
220
|
+
logger.error(`Could not get VM IP for ${vmName}. Cannot expose ports.`);
|
|
211
221
|
return;
|
|
212
222
|
}
|
|
213
223
|
for (const port of ports.split(',')) {
|
|
214
224
|
for (const protocol of protocols) {
|
|
215
225
|
const deviceName = `${vmName}-${protocol}-port-${port}`;
|
|
216
|
-
shellExec(`lxc config device remove ${vmName} ${deviceName}`);
|
|
226
|
+
shellExec(`lxc config device remove ${vmName} ${deviceName}`);
|
|
217
227
|
shellExec(
|
|
218
228
|
`lxc config device add ${vmName} ${deviceName} proxy listen=${protocol}:${hostIp}:${port} connect=${protocol}:${vmIp}:${port} nat=true`,
|
|
219
229
|
);
|
|
220
|
-
|
|
230
|
+
logger.info(`Exposed ${protocol}:${hostIp}:${port} -> ${vmIp}:${port} on ${vmName}`);
|
|
221
231
|
}
|
|
222
232
|
}
|
|
223
233
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const protocols = ['tcp'];
|
|
234
|
+
|
|
235
|
+
if (options.deleteExpose) {
|
|
236
|
+
const [vmName, ports] = options.deleteExpose.split(':');
|
|
237
|
+
const protocols = ['tcp'];
|
|
228
238
|
for (const port of ports.split(',')) {
|
|
229
239
|
for (const protocol of protocols) {
|
|
230
|
-
shellExec(`lxc config device remove ${
|
|
240
|
+
shellExec(`lxc config device remove ${vmName} ${vmName}-${protocol}-port-${port}`);
|
|
231
241
|
}
|
|
232
242
|
}
|
|
233
243
|
}
|
|
234
244
|
|
|
235
|
-
if (options.test
|
|
245
|
+
if (options.test) {
|
|
236
246
|
const vmName = options.test;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
} catch (error) {
|
|
259
|
-
console.error(`Error getting IPv4 address: ${error.message}`);
|
|
260
|
-
console.log(`Retrying in ${delayMs / 1000} seconds...`);
|
|
261
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
262
|
-
}
|
|
263
|
-
retries++;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (!vmIp) {
|
|
267
|
-
console.error(`Failed to get IPv4 address for ${vmName} after ${maxRetries} attempts. Aborting tests.`);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// 2. Iteratively check connection to google.com
|
|
272
|
-
let connectedToGoogle = false;
|
|
273
|
-
retries = 0;
|
|
274
|
-
while (!connectedToGoogle && retries < maxRetries) {
|
|
275
|
-
try {
|
|
276
|
-
console.log(`Checking connectivity to google.com from ${vmName} (Attempt ${retries + 1}/${maxRetries})...`);
|
|
277
|
-
const curlOutput = shellExec(
|
|
278
|
-
`lxc exec ${vmName} -- bash -c 'curl -s -o /dev/null -w "%{http_code}" http://google.com'`,
|
|
279
|
-
{ stdout: true },
|
|
280
|
-
);
|
|
281
|
-
if (curlOutput.startsWith('2') || curlOutput.startsWith('3')) {
|
|
282
|
-
console.log(`Successfully connected to google.com from ${vmName}.`);
|
|
283
|
-
connectedToGoogle = true;
|
|
284
|
-
} else {
|
|
285
|
-
console.log(`Connectivity to google.com not yet verified. Retrying in ${delayMs / 1000} seconds...`);
|
|
286
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
287
|
-
}
|
|
288
|
-
} catch (error) {
|
|
289
|
-
console.error(`Error checking connectivity to google.com: ${error.message}`);
|
|
290
|
-
console.log(`Retrying in ${delayMs / 1000} seconds...`);
|
|
291
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
292
|
-
}
|
|
293
|
-
retries++;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (!connectedToGoogle) {
|
|
297
|
-
console.error(
|
|
298
|
-
`Failed to connect to google.com from ${vmName} after ${maxRetries} attempts. Aborting further tests.`,
|
|
299
|
-
);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// 3. Check other connectivity, network, and VM health parameters
|
|
304
|
-
console.log(`\n--- Comprehensive Health Report for ${vmName} ---`);
|
|
305
|
-
|
|
306
|
-
// VM Status
|
|
307
|
-
console.log('\n--- VM Status ---');
|
|
308
|
-
try {
|
|
309
|
-
const vmStatus = shellExec(`lxc list ${vmName} --format json`, { stdout: true, silent: true });
|
|
310
|
-
console.log(JSON.stringify(JSON.parse(vmStatus), null, 2));
|
|
311
|
-
} catch (error) {
|
|
312
|
-
console.error(`Error getting VM status: ${error.message}`);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// CPU Usage
|
|
316
|
-
console.log('\n--- CPU Usage ---');
|
|
317
|
-
try {
|
|
318
|
-
const cpuUsage = shellExec(`lxc exec ${vmName} -- bash -c 'top -bn1 | grep "Cpu(s)"'`, { stdout: true });
|
|
319
|
-
console.log(cpuUsage.trim());
|
|
320
|
-
} catch (error) {
|
|
321
|
-
console.error(`Error getting CPU usage: ${error.message}`);
|
|
322
|
-
}
|
|
247
|
+
const vmIp = shellExec(
|
|
248
|
+
`lxc list ${vmName} --format json | jq -r '.[0].state.network.enp5s0.addresses[] | select(.family=="inet") | .address'`,
|
|
249
|
+
{ stdout: true },
|
|
250
|
+
).trim();
|
|
251
|
+
logger.info(`VM ${vmName} IPv4: ${vmIp || 'none'}`);
|
|
252
|
+
const httpStatus = shellExec(
|
|
253
|
+
`lxc exec ${vmName} -- curl -s -o /dev/null -w "%{http_code}" --max-time 5 http://google.com`,
|
|
254
|
+
{ stdout: true },
|
|
255
|
+
).trim();
|
|
256
|
+
logger.info(`VM ${vmName} HTTP connectivity: ${httpStatus}`);
|
|
257
|
+
logger.info(`Health report for VM: ${vmName}`);
|
|
258
|
+
shellExec(`lxc list ${vmName} --format json`);
|
|
259
|
+
shellExec(`lxc exec ${vmName} -- bash -c 'top -bn1 | grep "Cpu(s)"'`);
|
|
260
|
+
shellExec(`lxc exec ${vmName} -- bash -c 'free -m'`);
|
|
261
|
+
shellExec(`lxc exec ${vmName} -- bash -c 'df -h /'`);
|
|
262
|
+
shellExec(`lxc exec ${vmName} -- bash -c 'ip a'`);
|
|
263
|
+
shellExec(`lxc exec ${vmName} -- bash -c 'cat /etc/resolv.conf'`);
|
|
264
|
+
shellExec(`lxc exec ${vmName} -- bash -c 'sudo k3s kubectl get nodes'`);
|
|
265
|
+
}
|
|
266
|
+
},
|
|
323
267
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
268
|
+
/**
|
|
269
|
+
* @method pushDirectory
|
|
270
|
+
* @description Pushes a host directory into a VM using ignore-walk (respecting .gitignore)
|
|
271
|
+
* and a tar pipe. Skips gitignored paths (e.g. node_modules, .git, build artifacts).
|
|
272
|
+
* @param {object} params
|
|
273
|
+
* @param {string} params.srcPath - Absolute path of the source directory on the host.
|
|
274
|
+
* @param {string} params.vmName - Target LXD VM name.
|
|
275
|
+
* @param {string} params.destPath - Absolute path of the destination directory inside the VM.
|
|
276
|
+
* @param {string[]} [params.ignoreFiles=['.gitignore']] - Ignore-file names to respect during walk.
|
|
277
|
+
* @returns {Promise<void>}
|
|
278
|
+
* @memberof UnderpostLxd
|
|
279
|
+
*/
|
|
280
|
+
async pushDirectory({ srcPath, vmName, destPath, ignoreFiles }) {
|
|
281
|
+
const includesFile = `/tmp/lxd-push-${vmName}-${Date.now()}.txt`;
|
|
282
|
+
if (!ignoreFiles) ignoreFiles = ['.gitignore'];
|
|
283
|
+
// Collect non-ignored files via ignore-walk
|
|
284
|
+
const files = await new Promise((resolve) =>
|
|
285
|
+
walk(
|
|
286
|
+
{
|
|
287
|
+
path: srcPath,
|
|
288
|
+
ignoreFiles,
|
|
289
|
+
includeEmpty: false,
|
|
290
|
+
follow: false,
|
|
291
|
+
},
|
|
292
|
+
(_, result) => resolve(result),
|
|
293
|
+
),
|
|
294
|
+
);
|
|
332
295
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const diskUsage = shellExec(`lxc exec ${vmName} -- bash -c 'df -h /'`, { stdout: true });
|
|
337
|
-
console.log(diskUsage.trim());
|
|
338
|
-
} catch (error) {
|
|
339
|
-
console.error(`Error getting disk usage: ${error.message}`);
|
|
340
|
-
}
|
|
296
|
+
// Write relative paths (one per line) to a temp includes file
|
|
297
|
+
fs.writeFileSync(includesFile, files.join('\n'));
|
|
298
|
+
logger.info(`lxd pushDirectory: ${files.length} files collected`, { srcPath, vmName, destPath, includesFile });
|
|
341
299
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
try {
|
|
345
|
-
const ipA = shellExec(`lxc exec ${vmName} -- bash -c 'ip a'`, { stdout: true });
|
|
346
|
-
console.log(ipA.trim());
|
|
347
|
-
} catch (error) {
|
|
348
|
-
console.error(`Error getting network interface status: ${error.message}`);
|
|
349
|
-
}
|
|
300
|
+
// Reset destination directory inside the VM
|
|
301
|
+
shellExec(`lxc exec ${vmName} -- bash -c 'rm -rf ${destPath} && mkdir -p ${destPath}'`);
|
|
350
302
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
console.log(resolvConf.trim());
|
|
356
|
-
} catch (error) {
|
|
357
|
-
console.error(`Error getting DNS configuration: ${error.message}`);
|
|
358
|
-
}
|
|
303
|
+
// Stream tar archive from host into VM
|
|
304
|
+
shellExec(
|
|
305
|
+
`tar -C ${srcPath} -cf - --files-from=${includesFile} | lxc exec ${vmName} -- tar -C ${destPath} -xf -`,
|
|
306
|
+
);
|
|
359
307
|
|
|
360
|
-
|
|
361
|
-
|
|
308
|
+
// Clean up temp includes file
|
|
309
|
+
fs.removeSync(includesFile);
|
|
362
310
|
},
|
|
311
|
+
|
|
363
312
|
/**
|
|
364
313
|
* @method runWorkflow
|
|
365
314
|
* @description Executes predefined workflows on LXD VMs.
|
|
366
|
-
* @param {object} params
|
|
367
|
-
* @param {string} params.workflowId -
|
|
368
|
-
* @param {string} params.vmName -
|
|
369
|
-
* @param {string} [params.deployId] -
|
|
370
|
-
* @param {boolean} [params.dev=false] -
|
|
315
|
+
* @param {object} params
|
|
316
|
+
* @param {string} params.workflowId - Workflow ID to execute.
|
|
317
|
+
* @param {string} params.vmName - Target VM name.
|
|
318
|
+
* @param {string} [params.deployId] - Deployment identifier.
|
|
319
|
+
* @param {boolean} [params.dev=false] - Use local paths.
|
|
371
320
|
* @memberof UnderpostLxd
|
|
372
321
|
*/
|
|
373
322
|
async runWorkflow({ workflowId, vmName, deployId, dev }) {
|
|
374
323
|
switch (workflowId) {
|
|
375
324
|
case 'engine': {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
325
|
+
await Underpost.lxd.pushDirectory({
|
|
326
|
+
srcPath: `/home/dd/engine`,
|
|
327
|
+
vmName,
|
|
328
|
+
destPath: `/home/dd/engine`,
|
|
329
|
+
});
|
|
330
|
+
await Underpost.lxd.pushDirectory({
|
|
331
|
+
srcPath: `/home/dd/engine/engine-private`,
|
|
332
|
+
vmName,
|
|
333
|
+
destPath: `/home/dd/engine/engine-private`,
|
|
334
|
+
ignoreFiles: ['/home/dd/engine/.gitignore', '.gitignore'],
|
|
335
|
+
});
|
|
382
336
|
break;
|
|
383
337
|
}
|
|
384
|
-
case '
|
|
385
|
-
const
|
|
386
|
-
shellExec(`lxc exec ${vmName} -- bash -
|
|
387
|
-
shellExec(`lxc exec ${vmName} -- bash -
|
|
338
|
+
case 'engine-recursive-push': {
|
|
339
|
+
const enginePath = '/home/dd/engine';
|
|
340
|
+
shellExec(`lxc exec ${vmName} -- bash -c 'rm -rf ${enginePath}'`);
|
|
341
|
+
shellExec(`lxc exec ${vmName} -- bash -c 'mkdir -p /home/dd'`);
|
|
342
|
+
shellExec(`lxc file push ${enginePath} ${vmName}/home/dd --recursive`);
|
|
388
343
|
break;
|
|
389
344
|
}
|
|
390
|
-
case '
|
|
345
|
+
case 'dev-reset': {
|
|
391
346
|
shellExec(
|
|
392
|
-
`lxc exec ${vmName} -- bash -lc 'cd /home/dd/engine && node bin cluster --dev --reset && node bin cluster --dev --k3s'`,
|
|
347
|
+
`lxc exec ${vmName} -- bash -lc 'cd /home/dd/engine && node bin cluster --dev --reset --k3s && node bin cluster --dev --k3s'`,
|
|
393
348
|
);
|
|
394
349
|
break;
|
|
395
350
|
}
|
package/src/cli/repository.js
CHANGED
|
@@ -124,6 +124,7 @@ class UnderpostRepository {
|
|
|
124
124
|
|
|
125
125
|
if (options.changelog !== undefined || options.changelogBuild) {
|
|
126
126
|
const ciIntegrationPrefix = 'ci(package-pwa-microservices-';
|
|
127
|
+
const ciIntegrationVersionPrefix = 'New release v:';
|
|
127
128
|
const releaseMatch = 'New release v:';
|
|
128
129
|
|
|
129
130
|
// Helper: parse [<tag>] commits into grouped sections
|
|
@@ -261,7 +262,9 @@ class UnderpostRepository {
|
|
|
261
262
|
let commits;
|
|
262
263
|
if (!hasExplicitCount) {
|
|
263
264
|
// No explicit count: find commits up to the last CI integration boundary
|
|
264
|
-
const ciIndex = allCommits.findIndex(
|
|
265
|
+
const ciIndex = allCommits.findIndex(
|
|
266
|
+
(c) => c.message.startsWith(ciIntegrationPrefix) && !c.message.match(ciIntegrationVersionPrefix),
|
|
267
|
+
);
|
|
265
268
|
commits = ciIndex >= 0 ? allCommits.slice(0, ciIndex) : allCommits;
|
|
266
269
|
} else {
|
|
267
270
|
commits = allCommits;
|
package/src/cli/run.js
CHANGED
|
@@ -1148,7 +1148,7 @@ EOF
|
|
|
1148
1148
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
1149
1149
|
const clusterType = options.k3s ? 'k3s' : 'kubeadm';
|
|
1150
1150
|
shellCd(`/home/dd/engine`);
|
|
1151
|
-
shellExec(`${baseCommand} cluster${baseClusterCommand} --reset`);
|
|
1151
|
+
shellExec(`${baseCommand} cluster${baseClusterCommand} --reset --${clusterType}`);
|
|
1152
1152
|
await timer(5000);
|
|
1153
1153
|
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType}`);
|
|
1154
1154
|
await timer(5000);
|
|
@@ -1779,8 +1779,9 @@ EOF
|
|
|
1779
1779
|
* @memberof UnderpostRun
|
|
1780
1780
|
*/
|
|
1781
1781
|
'gpu-env': (path, options = DEFAULT_OPTION) => {
|
|
1782
|
+
const clusterType = 'kubeadm';
|
|
1782
1783
|
shellExec(
|
|
1783
|
-
`node bin cluster --dev --reset && node bin cluster --dev --dedicated-gpu
|
|
1784
|
+
`node bin cluster --dev --reset --${clusterType} && node bin cluster --dev --dedicated-gpu --${clusterType} && kubectl get pods --all-namespaces -o wide -w`,
|
|
1784
1785
|
);
|
|
1785
1786
|
},
|
|
1786
1787
|
/**
|