underpost 3.2.8 → 3.2.10
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/.github/workflows/npmpkg.ci.yml +1 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -0
- package/.vscode/settings.json +10 -5
- package/CHANGELOG.md +223 -2
- package/CLI-HELP.md +36 -7
- package/README.md +38 -9
- package/bin/build.js +27 -11
- package/bin/deploy.js +20 -21
- package/bin/file.js +32 -13
- package/bin/index.js +2 -1
- package/bin/vs.js +1 -1
- package/bump.config.js +26 -0
- package/conf.js +20 -4
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +4 -2
- package/manifests/kind-config-dev.yaml +8 -0
- package/manifests/mongodb/pv-pvc.yaml +44 -8
- package/manifests/mongodb/statefulset.yaml +55 -68
- package/package.json +40 -25
- package/scripts/k3s-node-setup.sh +30 -11
- package/scripts/nat-iptables.sh +103 -18
- package/src/api/core/core.router.js +19 -14
- package/src/api/core/core.service.js +5 -5
- package/src/api/default/default.router.js +22 -18
- package/src/api/default/default.service.js +5 -5
- package/src/api/document/document.router.js +28 -23
- package/src/api/document/document.service.js +100 -23
- package/src/api/file/file.router.js +19 -13
- package/src/api/file/file.service.js +9 -7
- package/src/api/test/test.router.js +17 -12
- package/src/api/types.js +24 -0
- package/src/api/user/guest.service.js +5 -4
- package/src/api/user/user.router.js +297 -288
- package/src/api/user/user.service.js +100 -35
- package/src/cli/baremetal.js +20 -11
- package/src/cli/cluster.js +243 -55
- package/src/cli/db.js +106 -62
- package/src/cli/deploy.js +297 -154
- package/src/cli/fs.js +19 -3
- package/src/cli/index.js +37 -9
- package/src/cli/ipfs.js +4 -6
- package/src/cli/kubectl.js +4 -1
- package/src/cli/lxd.js +217 -135
- package/src/cli/release.js +289 -131
- package/src/cli/repository.js +91 -34
- package/src/cli/run.js +297 -56
- package/src/cli/test.js +9 -3
- package/src/client/Default.index.js +9 -3
- package/src/client/components/core/Auth.js +19 -5
- package/src/client/components/core/Docs.js +6 -34
- package/src/client/components/core/FileExplorer.js +6 -6
- package/src/client/components/core/Modal.js +65 -2
- package/src/client/components/core/PanelForm.js +56 -52
- package/src/client/components/core/Recover.js +4 -4
- package/src/client/components/core/Worker.js +170 -350
- package/src/client/services/default/default.management.js +20 -25
- package/src/client/services/user/guest.service.js +10 -3
- package/src/client/sw/core.sw.js +174 -112
- package/src/db/DataBaseProvider.js +120 -20
- package/src/db/mongo/MongoBootstrap.js +587 -0
- package/src/db/mongo/MongooseDB.js +126 -22
- package/src/index.js +1 -1
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/wp/Wp.js +8 -5
- package/src/server/auth.js +2 -2
- package/src/server/client-build-docs.js +1 -1
- package/src/server/client-build.js +94 -129
- package/src/server/conf.js +20 -65
- package/src/server/data-query.js +32 -20
- package/src/server/dns.js +22 -0
- package/src/server/process.js +180 -19
- package/src/server/runtime.js +1 -1
- package/src/server/start.js +26 -7
- package/src/server/valkey.js +9 -2
- package/src/ws/IoInterface.js +16 -16
- package/src/ws/core/channels/core.ws.chat.js +11 -11
- package/src/ws/core/channels/core.ws.mailer.js +29 -29
- package/src/ws/core/channels/core.ws.stream.js +19 -19
- package/src/ws/core/core.ws.connection.js +8 -8
- package/src/ws/core/core.ws.server.js +6 -5
- package/src/ws/default/channels/default.ws.main.js +10 -10
- package/src/ws/default/default.ws.connection.js +4 -4
- package/src/ws/default/default.ws.server.js +4 -3
- package/typedoc.json +10 -1
- package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
- package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
- /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
- /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
- /package/src/client/ssr/{pages → views}/Test.js +0 -0
package/src/cli/lxd.js
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
* LXD module for managing LXD virtual machines as K3s nodes.
|
|
3
3
|
* @module src/cli/lxd.js
|
|
4
4
|
* @namespace UnderpostLxd
|
|
5
|
+
*
|
|
6
|
+
* ### Proxy Device Safety
|
|
7
|
+
*
|
|
8
|
+
* Proxy devices (created by `--expose`) attach LXD proxy devices to VMs. If you
|
|
9
|
+
* stop + delete a VM without removing proxy devices first, LXD may crash or
|
|
10
|
+
* leave stale NAT rules in iptables. Both `_safeDeleteVm()` and reset() now
|
|
11
|
+
* enumerate and remove proxy devices before stopping/deleting VMs.
|
|
12
|
+
*
|
|
13
|
+
* ### Idempotency
|
|
14
|
+
*
|
|
15
|
+
* Every destructive operation (deleteVm, reset) is safe to re-run. If a VM is
|
|
16
|
+
* already gone, proxy device removal is silently skipped. If the LXD snap is
|
|
17
|
+
* already removed, reset continues gracefully.
|
|
18
|
+
*
|
|
19
|
+
* ### Lifecycle
|
|
20
|
+
*
|
|
21
|
+
* - `--reset` is the only complete teardown path: cleans ALL VMs, profiles,
|
|
22
|
+
* networks, and finally the LXD snap itself.
|
|
23
|
+
* - `--delete-vm` is a single-VM teardown that removes proxy devices first.
|
|
24
|
+
* - `--init-vm` handles OS + K3s setup. Engine replication is a separate step
|
|
25
|
+
* via `--bootstrap-engine`.
|
|
26
|
+
* - `--bootstrap-engine` replicates /home/dd/engine into the VM after init.
|
|
5
27
|
*/
|
|
6
28
|
|
|
7
29
|
import { getNpmRootPath } from '../server/conf.js';
|
|
@@ -13,40 +35,33 @@ import Underpost from '../index.js';
|
|
|
13
35
|
|
|
14
36
|
const logger = loggerFactory(import.meta);
|
|
15
37
|
|
|
16
|
-
/**
|
|
17
|
-
* @class UnderpostLxd
|
|
18
|
-
* @description Provides a set of static methods to interact with LXD,
|
|
19
|
-
* encapsulating common LXD operations for VM management and network testing.
|
|
20
|
-
* @memberof UnderpostLxd
|
|
21
|
-
*/
|
|
22
38
|
class UnderpostLxd {
|
|
23
39
|
static API = {
|
|
24
40
|
/**
|
|
25
41
|
* @method callback
|
|
26
|
-
* @description Main entry point for LXD
|
|
42
|
+
* @description Main entry point for all LXD CLI operations.
|
|
27
43
|
* @param {object} options
|
|
28
44
|
* @param {boolean} [options.init=false] - Initialize LXD via preseed.
|
|
29
|
-
* @param {boolean} [options.reset=false] -
|
|
45
|
+
* @param {boolean} [options.reset=false] - Complete safe reset: cleans all VMs
|
|
46
|
+
* (proxy devices removed first), profiles, networks, then removes LXD snap.
|
|
30
47
|
* @param {boolean} [options.dev=false] - Use local paths instead of npm global.
|
|
31
48
|
* @param {boolean} [options.install=false] - Install LXD snap.
|
|
32
49
|
* @param {boolean} [options.createVirtualNetwork=false] - Create lxdbr0 bridge network.
|
|
33
|
-
* @param {string} [options.ipv4Address='10.250.250.1/24'] - IPv4 address/CIDR for
|
|
50
|
+
* @param {string} [options.ipv4Address='10.250.250.1/24'] - IPv4 address/CIDR for lxdbr0.
|
|
34
51
|
* @param {boolean} [options.createAdminProfile=false] - Create admin-profile for VMs.
|
|
35
|
-
* @param {boolean} [options.control=false] - Initialize VM as K3s control plane
|
|
36
|
-
* @param {boolean} [options.worker=false] - Initialize VM as K3s worker
|
|
37
|
-
* @param {string} [options.initVm=''] - VM name to initialize as
|
|
38
|
-
* @param {string} [options.deleteVm=''] - VM name to stop and delete
|
|
39
|
-
*
|
|
52
|
+
* @param {boolean} [options.control=false] - Initialize VM as K3s control plane.
|
|
53
|
+
* @param {boolean} [options.worker=false] - Initialize VM as K3s worker.
|
|
54
|
+
* @param {string} [options.initVm=''] - VM name to initialize as K3s node.
|
|
55
|
+
* @param {string} [options.deleteVm=''] - VM name to safely stop and delete
|
|
56
|
+
* (removes proxy devices first).
|
|
57
|
+
* @param {string} [options.createVm=''] - VM name to create (copies command to clipboard).
|
|
40
58
|
* @param {string} [options.infoVm=''] - VM name to inspect.
|
|
41
|
-
* @param {string} [options.rootSize=''] - Root disk size in GiB for new VMs
|
|
42
|
-
* @param {string} [options.joinNode=''] - Join format: 'workerName,controlName'
|
|
59
|
+
* @param {string} [options.rootSize=''] - Root disk size in GiB for new VMs.
|
|
60
|
+
* @param {string} [options.joinNode=''] - Join format: 'workerName,controlName'.
|
|
43
61
|
* @param {string} [options.expose=''] - Expose VM ports to host: 'vmName:port1,port2'.
|
|
44
62
|
* @param {string} [options.deleteExpose=''] - Remove exposed ports: 'vmName:port1,port2'.
|
|
45
|
-
* @param {string} [options.test=''] - VM name
|
|
46
|
-
* @param {string} [options.
|
|
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.
|
|
63
|
+
* @param {string} [options.test=''] - VM name for connectivity and health checks.
|
|
64
|
+
* @param {string} [options.bootstrapEngine=''] - VM name to replicate /home/dd/engine into.
|
|
50
65
|
* @memberof UnderpostLxd
|
|
51
66
|
*/
|
|
52
67
|
async callback(
|
|
@@ -69,22 +84,58 @@ class UnderpostLxd {
|
|
|
69
84
|
expose: '',
|
|
70
85
|
deleteExpose: '',
|
|
71
86
|
test: '',
|
|
72
|
-
|
|
73
|
-
vmId: '',
|
|
74
|
-
deployId: '',
|
|
75
|
-
namespace: '',
|
|
87
|
+
bootstrapEngine: '',
|
|
76
88
|
},
|
|
77
89
|
) {
|
|
78
90
|
const npmRoot = getNpmRootPath();
|
|
79
91
|
const underpostRoot = options?.dev === true ? '.' : `${npmRoot}/underpost`;
|
|
80
92
|
|
|
93
|
+
// =====================================================================
|
|
94
|
+
// RESET: Complete, safe teardown of all LXD state
|
|
95
|
+
// =====================================================================
|
|
81
96
|
if (options.reset === true) {
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
logger.info('=== SAFE LXD RESET ===');
|
|
98
|
+
logger.info('Phase 1/5: Enumerating all VMs and removing proxy devices...');
|
|
99
|
+
const vmList = UnderpostLxd._listVms();
|
|
100
|
+
for (const vmName of vmList) {
|
|
101
|
+
UnderpostLxd._removeProxyDevices(vmName);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
logger.info('Phase 2/5: Stopping all VMs gracefully...');
|
|
105
|
+
for (const vmName of vmList) {
|
|
106
|
+
logger.info(` Stopping VM: ${vmName}`);
|
|
107
|
+
shellExec(`lxc stop ${vmName} --timeout 30 2>/dev/null || true`, { silent: true, silentOnError: true });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
logger.info('Phase 3/5: Deleting all VMs...');
|
|
111
|
+
for (const vmName of vmList) {
|
|
112
|
+
logger.info(` Deleting VM: ${vmName}`);
|
|
113
|
+
shellExec(`lxc delete ${vmName} --force 2>/dev/null || true`, { silent: true, silentOnError: true });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
logger.info('Phase 4/5: Removing admin-profile and network...');
|
|
117
|
+
shellExec(`lxc profile delete admin-profile 2>/dev/null || true`, { silent: true, silentOnError: true });
|
|
118
|
+
shellExec(`lxc network delete lxdbr0 2>/dev/null || true`, { silent: true, silentOnError: true });
|
|
119
|
+
|
|
120
|
+
logger.info('Phase 5/5: Stopping LXD snap daemon and purging snap...');
|
|
121
|
+
shellExec(`sudo systemctl stop snap.lxd.daemon 2>/dev/null || true`, { silent: true, silentOnError: true });
|
|
122
|
+
shellExec(`sudo snap remove lxd --purge 2>/dev/null || true`, { silent: true, silentOnError: true });
|
|
123
|
+
|
|
124
|
+
logger.info('=== LXD RESET COMPLETE ===');
|
|
125
|
+
logger.info('All VMs, proxy devices, profiles, networks, and the LXD snap have been removed.');
|
|
126
|
+
return;
|
|
84
127
|
}
|
|
85
128
|
|
|
86
|
-
|
|
129
|
+
// =====================================================================
|
|
130
|
+
// INSTALL
|
|
131
|
+
// =====================================================================
|
|
132
|
+
if (options.install === true) {
|
|
133
|
+
shellExec(`sudo snap install lxd`);
|
|
134
|
+
}
|
|
87
135
|
|
|
136
|
+
// =====================================================================
|
|
137
|
+
// INIT (LXD preseed)
|
|
138
|
+
// =====================================================================
|
|
88
139
|
if (options.init === true) {
|
|
89
140
|
shellExec(`sudo systemctl start snap.lxd.daemon`);
|
|
90
141
|
shellExec(`sudo systemctl status snap.lxd.daemon`);
|
|
@@ -95,6 +146,9 @@ class UnderpostLxd {
|
|
|
95
146
|
shellExec(`lxc cluster list`);
|
|
96
147
|
}
|
|
97
148
|
|
|
149
|
+
// =====================================================================
|
|
150
|
+
// CREATE VIRTUAL NETWORK
|
|
151
|
+
// =====================================================================
|
|
98
152
|
if (options.createVirtualNetwork === true) {
|
|
99
153
|
const ipv4Address = options.ipv4Address ? options.ipv4Address : '10.250.250.1/24';
|
|
100
154
|
shellExec(`lxc network create lxdbr0 \
|
|
@@ -104,6 +158,9 @@ ipv4.dhcp=true \
|
|
|
104
158
|
ipv6.address=none`);
|
|
105
159
|
}
|
|
106
160
|
|
|
161
|
+
// =====================================================================
|
|
162
|
+
// CREATE ADMIN PROFILE
|
|
163
|
+
// =====================================================================
|
|
107
164
|
if (options.createAdminProfile === true) {
|
|
108
165
|
const existingProfiles = await new Promise((resolve) => {
|
|
109
166
|
shellExec(`lxc profile show admin-profile`, {
|
|
@@ -120,25 +177,28 @@ ipv6.address=none`);
|
|
|
120
177
|
}
|
|
121
178
|
}
|
|
122
179
|
|
|
180
|
+
// =====================================================================
|
|
181
|
+
// DELETE VM (safe: removes proxy devices first)
|
|
182
|
+
// =====================================================================
|
|
123
183
|
if (options.deleteVm) {
|
|
124
184
|
const vmName = options.deleteVm;
|
|
125
|
-
|
|
126
|
-
shellExec(`lxc stop ${vmName}`);
|
|
127
|
-
logger.info(`Deleting VM: ${vmName}`);
|
|
128
|
-
shellExec(`lxc delete ${vmName}`);
|
|
129
|
-
logger.info(`VM ${vmName} deleted.`);
|
|
185
|
+
UnderpostLxd._safeDeleteVm(vmName);
|
|
130
186
|
}
|
|
131
187
|
|
|
188
|
+
// =====================================================================
|
|
189
|
+
// CREATE VM (copy launch command to clipboard)
|
|
190
|
+
// =====================================================================
|
|
132
191
|
if (options.createVm) {
|
|
133
192
|
pbcopy(
|
|
134
|
-
`lxc launch images:rockylinux/9 ${
|
|
135
|
-
|
|
136
|
-
} --vm --target lxd-node1 -c limits.cpu=2 -c limits.memory=4GB --profile admin-profile -d root,size=${
|
|
137
|
-
options.rootSize ? options.rootSize + 'GiB' : '32GiB'
|
|
193
|
+
`lxc launch images:rockylinux/9 ${options.createVm
|
|
194
|
+
} --vm --target lxd-node1 -c limits.cpu=2 -c limits.memory=4GB --profile admin-profile -d root,size=${options.rootSize ? options.rootSize + 'GiB' : '32GiB'
|
|
138
195
|
}`,
|
|
139
196
|
);
|
|
140
197
|
}
|
|
141
198
|
|
|
199
|
+
// =====================================================================
|
|
200
|
+
// INIT VM (OS setup + K3s role, no engine push)
|
|
201
|
+
// =====================================================================
|
|
142
202
|
if (options.initVm) {
|
|
143
203
|
const vmName = options.initVm;
|
|
144
204
|
const lxdSetupPath = `${underpostRoot}/scripts/lxd-vm-setup.sh`;
|
|
@@ -147,10 +207,8 @@ ipv6.address=none`);
|
|
|
147
207
|
// Step 1: OS base setup (disk, packages, kernel modules)
|
|
148
208
|
shellExec(`cat ${lxdSetupPath} | lxc exec ${vmName} -- bash`);
|
|
149
209
|
|
|
150
|
-
// Step 2:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// Step 3: K3s role setup (installs Node, npm deps, then k3s via node bin --dev)
|
|
210
|
+
// Step 2: K3s role setup (installs Node, npm deps, then k3s via underpost CLI)
|
|
211
|
+
// Engine source replication is a separate step via --bootstrap-engine.
|
|
154
212
|
if (options.worker === true) {
|
|
155
213
|
if (options.joinNode) {
|
|
156
214
|
const controlNode = options.joinNode.includes(',') ? options.joinNode.split(',').pop() : options.joinNode;
|
|
@@ -174,16 +232,52 @@ ipv6.address=none`);
|
|
|
174
232
|
}
|
|
175
233
|
}
|
|
176
234
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
235
|
+
// =====================================================================
|
|
236
|
+
// BOOTSTRAP ENGINE: Replicate /home/dd/engine into a VM
|
|
237
|
+
// =====================================================================
|
|
238
|
+
if (options.bootstrapEngine) {
|
|
239
|
+
const vmName = options.bootstrapEngine;
|
|
240
|
+
logger.info(`Bootstrapping engine source into VM: ${vmName}...`);
|
|
241
|
+
|
|
242
|
+
const includesFile = `/tmp/lxd-push-${vmName}-${Date.now()}.txt`;
|
|
243
|
+
const srcPath = `/home/dd/engine`;
|
|
244
|
+
const files = await new Promise((resolve) =>
|
|
245
|
+
walk({ path: srcPath, ignoreFiles: ['.gitignore'], includeEmpty: false, follow: false }, (_, result) =>
|
|
246
|
+
resolve(result),
|
|
247
|
+
),
|
|
248
|
+
);
|
|
249
|
+
fs.writeFileSync(includesFile, files.join('\n'));
|
|
250
|
+
shellExec(`lxc exec ${vmName} -- bash -c 'rm -rf /home/dd/engine && mkdir -p /home/dd/engine'`);
|
|
251
|
+
shellExec(`tar -C ${srcPath} -cf - --files-from=${includesFile} | lxc exec ${vmName} -- tar -C /home/dd/engine -xf -`);
|
|
252
|
+
fs.removeSync(includesFile);
|
|
253
|
+
|
|
254
|
+
// Also push engine-private if it exists
|
|
255
|
+
const privateSrcPath = `/home/dd/engine/engine-private`;
|
|
256
|
+
if (fs.existsSync(privateSrcPath)) {
|
|
257
|
+
const privateFiles = await new Promise((resolve) =>
|
|
258
|
+
walk(
|
|
259
|
+
{
|
|
260
|
+
path: privateSrcPath,
|
|
261
|
+
ignoreFiles: ['/home/dd/engine/.gitignore', '.gitignore'],
|
|
262
|
+
includeEmpty: false,
|
|
263
|
+
follow: false,
|
|
264
|
+
},
|
|
265
|
+
(_, result) => resolve(result),
|
|
266
|
+
),
|
|
267
|
+
);
|
|
268
|
+
const privateIncludes = `/tmp/lxd-push-${vmName}-private-${Date.now()}.txt`;
|
|
269
|
+
fs.writeFileSync(privateIncludes, privateFiles.join('\n'));
|
|
270
|
+
shellExec(`lxc exec ${vmName} -- bash -c 'rm -rf /home/dd/engine/engine-private && mkdir -p /home/dd/engine/engine-private'`);
|
|
271
|
+
shellExec(`tar -C ${privateSrcPath} -cf - --files-from=${privateIncludes} | lxc exec ${vmName} -- tar -C /home/dd/engine/engine-private -xf -`);
|
|
272
|
+
fs.removeSync(privateIncludes);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
logger.info(`Engine source bootstrapped into ${vmName}:/home/dd/engine`);
|
|
184
276
|
}
|
|
185
277
|
|
|
186
|
-
//
|
|
278
|
+
// =====================================================================
|
|
279
|
+
// STANDALONE JOIN
|
|
280
|
+
// =====================================================================
|
|
187
281
|
if (options.joinNode && !options.initVm) {
|
|
188
282
|
const [workerNode, controlNode] = options.joinNode.split(',');
|
|
189
283
|
const k3sToken = shellExec(
|
|
@@ -201,6 +295,9 @@ ipv6.address=none`);
|
|
|
201
295
|
logger.info(`Worker ${workerNode} joined successfully.`);
|
|
202
296
|
}
|
|
203
297
|
|
|
298
|
+
// =====================================================================
|
|
299
|
+
// INFO VM
|
|
300
|
+
// =====================================================================
|
|
204
301
|
if (options.infoVm) {
|
|
205
302
|
shellExec(`lxc config show ${options.infoVm}`);
|
|
206
303
|
shellExec(`lxc info --show-log ${options.infoVm}`);
|
|
@@ -208,6 +305,9 @@ ipv6.address=none`);
|
|
|
208
305
|
shellExec(`lxc list ${options.infoVm}`);
|
|
209
306
|
}
|
|
210
307
|
|
|
308
|
+
// =====================================================================
|
|
309
|
+
// EXPOSE (proxy host ports to VM)
|
|
310
|
+
// =====================================================================
|
|
211
311
|
if (options.expose) {
|
|
212
312
|
const [vmName, ports] = options.expose.split(':');
|
|
213
313
|
const protocols = ['tcp'];
|
|
@@ -232,6 +332,9 @@ ipv6.address=none`);
|
|
|
232
332
|
}
|
|
233
333
|
}
|
|
234
334
|
|
|
335
|
+
// =====================================================================
|
|
336
|
+
// DELETE EXPOSE
|
|
337
|
+
// =====================================================================
|
|
235
338
|
if (options.deleteExpose) {
|
|
236
339
|
const [vmName, ports] = options.deleteExpose.split(':');
|
|
237
340
|
const protocols = ['tcp'];
|
|
@@ -242,6 +345,9 @@ ipv6.address=none`);
|
|
|
242
345
|
}
|
|
243
346
|
}
|
|
244
347
|
|
|
348
|
+
// =====================================================================
|
|
349
|
+
// TEST (connectivity and health checks)
|
|
350
|
+
// =====================================================================
|
|
245
351
|
if (options.test) {
|
|
246
352
|
const vmName = options.test;
|
|
247
353
|
const vmIp = shellExec(
|
|
@@ -264,93 +370,69 @@ ipv6.address=none`);
|
|
|
264
370
|
shellExec(`lxc exec ${vmName} -- bash -c 'sudo k3s kubectl get nodes'`);
|
|
265
371
|
}
|
|
266
372
|
},
|
|
373
|
+
};
|
|
267
374
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
);
|
|
295
|
-
|
|
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 });
|
|
299
|
-
|
|
300
|
-
// Reset destination directory inside the VM
|
|
301
|
-
shellExec(`lxc exec ${vmName} -- bash -c 'rm -rf ${destPath} && mkdir -p ${destPath}'`);
|
|
302
|
-
|
|
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
|
-
);
|
|
307
|
-
|
|
308
|
-
// Clean up temp includes file
|
|
309
|
-
fs.removeSync(includesFile);
|
|
310
|
-
},
|
|
375
|
+
// =====================================================================
|
|
376
|
+
// PRIVATE HELPERS
|
|
377
|
+
// =====================================================================
|
|
311
378
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Lists all LXD VM (virtual-machine) instance names.
|
|
381
|
+
* @returns {string[]} Array of VM names.
|
|
382
|
+
* @private
|
|
383
|
+
*/
|
|
384
|
+
static _listVms() {
|
|
385
|
+
const raw = shellExec(
|
|
386
|
+
`lxc list --format json | jq -r '.[] | select(.type=="virtual-machine") | .name // empty' 2>/dev/null || true`,
|
|
387
|
+
{ stdout: true, silent: true, silentOnError: true },
|
|
388
|
+
).trim();
|
|
389
|
+
if (!raw) return [];
|
|
390
|
+
return raw.split('\n').filter((n) => n.length > 0);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Enumerates and removes all proxy devices attached to a VM.
|
|
395
|
+
* Proxy devices are named with the pattern <vmName>-<protocol>-port-<port>.
|
|
396
|
+
* Fails silently if the VM or device is already gone (idempotent).
|
|
397
|
+
* @param {string} vmName - The VM name to clean proxy devices from.
|
|
398
|
+
* @private
|
|
399
|
+
*/
|
|
400
|
+
static _removeProxyDevices(vmName) {
|
|
401
|
+
logger.info(` Removing proxy devices from ${vmName}...`);
|
|
402
|
+
const devicesRaw = shellExec(
|
|
403
|
+
`lxc config device list ${vmName} 2>/dev/null | grep -E "^${vmName}-tcp-port-" || true`,
|
|
404
|
+
{ stdout: true, silent: true, silentOnError: true },
|
|
405
|
+
).trim();
|
|
406
|
+
if (!devicesRaw) {
|
|
407
|
+
logger.info(` No proxy devices found on ${vmName}.`);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
for (const deviceName of devicesRaw.split('\n')) {
|
|
411
|
+
const name = deviceName.trim();
|
|
412
|
+
if (!name) continue;
|
|
413
|
+
logger.info(` Removing device: ${name}`);
|
|
414
|
+
shellExec(`lxc config device remove ${vmName} ${name} 2>/dev/null || true`, {
|
|
415
|
+
silent: true,
|
|
416
|
+
silentOnError: true,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Safely deletes a single VM: removes proxy devices first, then stops and deletes.
|
|
423
|
+
* Idempotent: safe to re-run if VM is already gone.
|
|
424
|
+
* @param {string} vmName - The VM name to delete.
|
|
425
|
+
* @private
|
|
426
|
+
*/
|
|
427
|
+
static _safeDeleteVm(vmName) {
|
|
428
|
+
logger.info(`Safely deleting VM: ${vmName}`);
|
|
429
|
+
UnderpostLxd._removeProxyDevices(vmName);
|
|
430
|
+
logger.info(` Stopping VM: ${vmName}`);
|
|
431
|
+
shellExec(`lxc stop ${vmName} --timeout 30 2>/dev/null || true`, { silent: true, silentOnError: true });
|
|
432
|
+
logger.info(` Deleting VM: ${vmName}`);
|
|
433
|
+
shellExec(`lxc delete ${vmName} --force 2>/dev/null || true`, { silent: true, silentOnError: true });
|
|
434
|
+
logger.info(`VM ${vmName} safely deleted.`);
|
|
435
|
+
}
|
|
354
436
|
}
|
|
355
437
|
|
|
356
|
-
export default UnderpostLxd;
|
|
438
|
+
export default UnderpostLxd;
|