underpost 2.90.4 → 2.95.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/.github/workflows/pwa-microservices-template-page.cd.yml +5 -4
- package/.github/workflows/release.cd.yml +7 -7
- package/README.md +7 -8
- package/bin/build.js +6 -1
- package/bin/deploy.js +2 -196
- package/cli.md +154 -80
- package/manifests/deployment/dd-default-development/deployment.yaml +4 -4
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/scripts/disk-clean.sh +216 -0
- package/scripts/rocky-setup.sh +1 -0
- package/scripts/ssh-cluster-info.sh +4 -3
- package/src/cli/cluster.js +1 -1
- package/src/cli/db.js +1143 -201
- package/src/cli/deploy.js +93 -24
- package/src/cli/env.js +2 -2
- package/src/cli/image.js +198 -133
- package/src/cli/index.js +111 -44
- package/src/cli/lxd.js +73 -74
- package/src/cli/monitor.js +20 -9
- package/src/cli/repository.js +212 -5
- package/src/cli/run.js +207 -74
- package/src/cli/ssh.js +642 -14
- package/src/client/components/core/CommonJs.js +0 -1
- package/src/db/mongo/MongooseDB.js +5 -1
- package/src/index.js +1 -1
- package/src/monitor.js +11 -1
- package/src/server/backup.js +1 -1
- package/src/server/conf.js +1 -1
- package/src/server/dns.js +242 -1
- package/src/server/process.js +6 -1
- package/src/server/start.js +2 -0
- package/scripts/snap-clean.sh +0 -26
- package/src/client/public/default/plantuml/client-conf.svg +0 -1
- package/src/client/public/default/plantuml/client-schema.svg +0 -1
- package/src/client/public/default/plantuml/cron-conf.svg +0 -1
- package/src/client/public/default/plantuml/cron-schema.svg +0 -1
- package/src/client/public/default/plantuml/server-conf.svg +0 -1
- package/src/client/public/default/plantuml/server-schema.svg +0 -1
- package/src/client/public/default/plantuml/ssr-conf.svg +0 -1
- package/src/client/public/default/plantuml/ssr-schema.svg +0 -1
|
@@ -31,7 +31,11 @@ class MongooseDBService {
|
|
|
31
31
|
logger.info('MongooseDB connect', { host, name, uri });
|
|
32
32
|
return await mongoose
|
|
33
33
|
.createConnection(uri, {
|
|
34
|
-
|
|
34
|
+
serverSelectionTimeoutMS: 5000,
|
|
35
|
+
// readPreference: 'primary',
|
|
36
|
+
// directConnection: true,
|
|
37
|
+
// useNewUrlParser: true,
|
|
38
|
+
// useUnifiedTopology: true,
|
|
35
39
|
})
|
|
36
40
|
.asPromise();
|
|
37
41
|
}
|
package/src/index.js
CHANGED
package/src/monitor.js
CHANGED
|
@@ -19,6 +19,16 @@ const logger = loggerFactory(import.meta);
|
|
|
19
19
|
|
|
20
20
|
await logger.setUpInfo();
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const deployId = process.argv[2];
|
|
23
|
+
const env = process.argv[3] || 'production';
|
|
24
|
+
const replicas = process.argv[4] || '1';
|
|
25
|
+
const namespace = process.argv[5] || 'default';
|
|
26
|
+
|
|
27
|
+
UnderpostMonitor.API.callback(deployId, env, {
|
|
28
|
+
type: 'blue-green',
|
|
29
|
+
sync: true,
|
|
30
|
+
replicas,
|
|
31
|
+
namespace,
|
|
32
|
+
});
|
|
23
33
|
|
|
24
34
|
ProcessController.init(logger);
|
package/src/server/backup.js
CHANGED
package/src/server/conf.js
CHANGED
|
@@ -124,7 +124,7 @@ const Config = {
|
|
|
124
124
|
fs.readFileSync(`./.github/workflows/engine-test.ci.yml`, 'utf8').replaceAll('test', deployId.split('dd-')[1]),
|
|
125
125
|
'utf8',
|
|
126
126
|
);
|
|
127
|
-
shellExec(`node bin
|
|
127
|
+
shellExec(`node bin new --default-conf --deploy-id ${deployId}`);
|
|
128
128
|
|
|
129
129
|
if (!fs.existsSync(`./engine-private/deploy/dd.router`))
|
|
130
130
|
fs.writeFileSync(`./engine-private/deploy/dd.router`, '', 'utf8');
|
package/src/server/dns.js
CHANGED
|
@@ -12,7 +12,7 @@ import { loggerFactory } from './logger.js';
|
|
|
12
12
|
import UnderpostRootEnv from '../cli/env.js';
|
|
13
13
|
import dns from 'node:dns';
|
|
14
14
|
import os from 'node:os';
|
|
15
|
-
import { shellExec } from './process.js';
|
|
15
|
+
import { shellExec, pbcopy } from './process.js';
|
|
16
16
|
|
|
17
17
|
dotenv.config();
|
|
18
18
|
|
|
@@ -100,6 +100,160 @@ class Dns {
|
|
|
100
100
|
return ipv4.address;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Setup nftables tables and chains if they don't exist.
|
|
105
|
+
* @static
|
|
106
|
+
* @memberof DnsManager
|
|
107
|
+
*/
|
|
108
|
+
static setupNftables() {
|
|
109
|
+
shellExec(`sudo nft add table inet filter 2>/dev/null || true`, { silent: true });
|
|
110
|
+
shellExec(
|
|
111
|
+
`sudo nft add chain inet filter input '{ type filter hook input priority 0; policy accept; }' 2>/dev/null || true`,
|
|
112
|
+
{ silent: true },
|
|
113
|
+
);
|
|
114
|
+
shellExec(
|
|
115
|
+
`sudo nft add chain inet filter output '{ type filter hook output priority 0; policy accept; }' 2>/dev/null || true`,
|
|
116
|
+
{ silent: true },
|
|
117
|
+
);
|
|
118
|
+
shellExec(
|
|
119
|
+
`sudo nft add chain inet filter forward '{ type filter hook forward priority 0; policy accept; }' 2>/dev/null || true`,
|
|
120
|
+
{ silent: true },
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Bans an IP address from ingress traffic.
|
|
126
|
+
* @static
|
|
127
|
+
* @memberof DnsManager
|
|
128
|
+
* @param {string} ip - The IP address to ban.
|
|
129
|
+
*/
|
|
130
|
+
static banIngress(ip) {
|
|
131
|
+
Dns.setupNftables();
|
|
132
|
+
if (!validator.isIP(ip)) {
|
|
133
|
+
logger.error(`Invalid IP address: ${ip}`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
shellExec(`sudo nft add rule inet filter input ip saddr ${ip} counter drop`, { silent: true });
|
|
137
|
+
logger.info(`Banned ingress for IP: ${ip}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Bans an IP address from egress traffic.
|
|
142
|
+
* @static
|
|
143
|
+
* @memberof DnsManager
|
|
144
|
+
* @param {string} ip - The IP address to ban.
|
|
145
|
+
*/
|
|
146
|
+
static banEgress(ip) {
|
|
147
|
+
Dns.setupNftables();
|
|
148
|
+
if (!validator.isIP(ip)) {
|
|
149
|
+
logger.error(`Invalid IP address: ${ip}`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
shellExec(`sudo nft add rule inet filter output ip daddr ${ip} counter drop`, { silent: true });
|
|
153
|
+
shellExec(`sudo nft add rule inet filter forward ip daddr ${ip} counter drop`, { silent: true });
|
|
154
|
+
logger.info(`Banned egress for IP: ${ip}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Helper to get nftables rule handles for a specific IP and chain.
|
|
159
|
+
* @static
|
|
160
|
+
* @memberof DnsManager
|
|
161
|
+
* @param {string} chain - The chain name (input, output, forward).
|
|
162
|
+
* @param {string} ip - The IP address.
|
|
163
|
+
* @param {string} type - The type (saddr or daddr).
|
|
164
|
+
* @returns {string[]} Array of handles.
|
|
165
|
+
*/
|
|
166
|
+
static getNftHandles(chain, ip, type) {
|
|
167
|
+
const output = shellExec(`sudo nft -a list chain inet filter ${chain}`, { stdout: true, silent: true });
|
|
168
|
+
const lines = output.split('\n');
|
|
169
|
+
const handles = [];
|
|
170
|
+
// Regex to match IP and handle. Note: output format depends on nft version but usually contains "handle <id>" at end.
|
|
171
|
+
// Example: ip saddr 1.2.3.4 counter packets 0 bytes 0 drop # handle 5
|
|
172
|
+
const regex = new RegExp(`ip ${type} ${ip} .* handle (\\d+)`);
|
|
173
|
+
for (const line of lines) {
|
|
174
|
+
const match = line.match(regex);
|
|
175
|
+
if (match) {
|
|
176
|
+
handles.push(match[1]);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return handles;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Unbans an IP address from ingress traffic.
|
|
184
|
+
* @static
|
|
185
|
+
* @memberof DnsManager
|
|
186
|
+
* @param {string} ip - The IP address to unban.
|
|
187
|
+
*/
|
|
188
|
+
static unbanIngress(ip) {
|
|
189
|
+
const handles = Dns.getNftHandles('input', ip, 'saddr');
|
|
190
|
+
for (const handle of handles) {
|
|
191
|
+
shellExec(`sudo nft delete rule inet filter input handle ${handle}`, { silent: true });
|
|
192
|
+
}
|
|
193
|
+
logger.info(`Unbanned ingress for IP: ${ip}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Unbans an IP address from egress traffic.
|
|
198
|
+
* @static
|
|
199
|
+
* @memberof DnsManager
|
|
200
|
+
* @param {string} ip - The IP address to unban.
|
|
201
|
+
*/
|
|
202
|
+
static unbanEgress(ip) {
|
|
203
|
+
const outputHandles = Dns.getNftHandles('output', ip, 'daddr');
|
|
204
|
+
for (const handle of outputHandles) {
|
|
205
|
+
shellExec(`sudo nft delete rule inet filter output handle ${handle}`, { silent: true });
|
|
206
|
+
}
|
|
207
|
+
const forwardHandles = Dns.getNftHandles('forward', ip, 'daddr');
|
|
208
|
+
for (const handle of forwardHandles) {
|
|
209
|
+
shellExec(`sudo nft delete rule inet filter forward handle ${handle}`, { silent: true });
|
|
210
|
+
}
|
|
211
|
+
logger.info(`Unbanned egress for IP: ${ip}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Lists all banned ingress IPs.
|
|
216
|
+
* @static
|
|
217
|
+
* @memberof DnsManager
|
|
218
|
+
*/
|
|
219
|
+
static listBannedIngress() {
|
|
220
|
+
const output = shellExec(`sudo nft list chain inet filter input`, { stdout: true, silent: true });
|
|
221
|
+
console.log(output);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Lists all banned egress IPs.
|
|
226
|
+
* @static
|
|
227
|
+
* @memberof DnsManager
|
|
228
|
+
*/
|
|
229
|
+
static listBannedEgress() {
|
|
230
|
+
console.log('--- Output Chain ---');
|
|
231
|
+
console.log(shellExec(`sudo nft list chain inet filter output`, { stdout: true, silent: true }));
|
|
232
|
+
console.log('--- Forward Chain ---');
|
|
233
|
+
console.log(shellExec(`sudo nft list chain inet filter forward`, { stdout: true, silent: true }));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Clears all banned ingress IPs.
|
|
238
|
+
* @static
|
|
239
|
+
* @memberof DnsManager
|
|
240
|
+
*/
|
|
241
|
+
static clearBannedIngress() {
|
|
242
|
+
shellExec(`sudo nft flush chain inet filter input`, { silent: true });
|
|
243
|
+
logger.info('Cleared all ingress bans.');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Clears all banned egress IPs.
|
|
248
|
+
* @static
|
|
249
|
+
* @memberof DnsManager
|
|
250
|
+
*/
|
|
251
|
+
static clearBannedEgress() {
|
|
252
|
+
shellExec(`sudo nft flush chain inet filter output`, { silent: true });
|
|
253
|
+
shellExec(`sudo nft flush chain inet filter forward`, { silent: true });
|
|
254
|
+
logger.info('Cleared all egress bans.');
|
|
255
|
+
}
|
|
256
|
+
|
|
103
257
|
/**
|
|
104
258
|
* Performs the dynamic DNS update logic.
|
|
105
259
|
* It checks if the public IP has changed and, if so, updates the configured DNS records.
|
|
@@ -232,6 +386,93 @@ class Dns {
|
|
|
232
386
|
// Add other DNS provider update functions here
|
|
233
387
|
},
|
|
234
388
|
};
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Dispatcher for IP ban/unban/list/clear operations based on CLI options.
|
|
392
|
+
* @static
|
|
393
|
+
* @memberof DnsManager
|
|
394
|
+
* @param {string} [ips=''] Comma-separated string of IPs to process.
|
|
395
|
+
* @param {object} options - Options indicating which action to perform.
|
|
396
|
+
* @property {boolean} [options.banIngressAdd=false] - Ban IPs from ingress.
|
|
397
|
+
* @property {boolean} [options.banIngressRemove=false] - Unban IPs from ingress.
|
|
398
|
+
* @property {boolean} [options.banIngressList=false] - List banned ingress IPs.
|
|
399
|
+
* @property {boolean} [options.banIngressClear=false] - Clear all banned ingress IPs.
|
|
400
|
+
* @property {boolean} [options.banEgressAdd=false] - Ban IPs from egress.
|
|
401
|
+
* @property {boolean} [options.banEgressRemove=false] - Unban IPs from egress.
|
|
402
|
+
* @property {boolean} [options.banEgressList=false] - List banned egress IPs.
|
|
403
|
+
* @property {boolean} [options.banEgressClear=false] - Clear all banned egress IPs.
|
|
404
|
+
* @property {boolean} [options.banBothAdd=false] - Ban IPs from both ingress and egress.
|
|
405
|
+
* @property {boolean} [options.banBothRemove=false] - Unban IPs from both ingress and egress.
|
|
406
|
+
* @property {boolean} [options.copy=false] - Copy the public IP to clipboard.
|
|
407
|
+
* @return {Promise<string|void>} The public IP if no ban/unban action is taken.
|
|
408
|
+
*/
|
|
409
|
+
static async ipDispatcher(
|
|
410
|
+
ips = '',
|
|
411
|
+
options = {
|
|
412
|
+
banIngressAdd: false,
|
|
413
|
+
banIngressRemove: false,
|
|
414
|
+
banIngressList: false,
|
|
415
|
+
banIngressClear: false,
|
|
416
|
+
banEgressAdd: false,
|
|
417
|
+
banEgressRemove: false,
|
|
418
|
+
banEgressList: false,
|
|
419
|
+
banEgressClear: false,
|
|
420
|
+
banBothAdd: false,
|
|
421
|
+
banBothRemove: false,
|
|
422
|
+
copy: false,
|
|
423
|
+
},
|
|
424
|
+
) {
|
|
425
|
+
const ipList = ips
|
|
426
|
+
? ips
|
|
427
|
+
.split(',')
|
|
428
|
+
.map((i) => i.trim())
|
|
429
|
+
.filter(Boolean)
|
|
430
|
+
: [];
|
|
431
|
+
|
|
432
|
+
if (options.banIngressAdd) {
|
|
433
|
+
return ipList.forEach((ip) => Dns.banIngress(ip));
|
|
434
|
+
}
|
|
435
|
+
if (options.banIngressRemove) {
|
|
436
|
+
return ipList.forEach((ip) => Dns.unbanIngress(ip));
|
|
437
|
+
}
|
|
438
|
+
if (options.banIngressList) {
|
|
439
|
+
return Dns.listBannedIngress();
|
|
440
|
+
}
|
|
441
|
+
if (options.banIngressClear) {
|
|
442
|
+
return Dns.clearBannedIngress();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (options.banEgressAdd) {
|
|
446
|
+
return ipList.forEach((ip) => Dns.banEgress(ip));
|
|
447
|
+
}
|
|
448
|
+
if (options.banEgressRemove) {
|
|
449
|
+
return ipList.forEach((ip) => Dns.unbanEgress(ip));
|
|
450
|
+
}
|
|
451
|
+
if (options.banEgressList) {
|
|
452
|
+
return Dns.listBannedEgress();
|
|
453
|
+
}
|
|
454
|
+
if (options.banEgressClear) {
|
|
455
|
+
return Dns.clearBannedEgress();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (options.banBothAdd) {
|
|
459
|
+
return ipList.forEach((ip) => {
|
|
460
|
+
Dns.banIngress(ip);
|
|
461
|
+
Dns.banEgress(ip);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
if (options.banBothRemove) {
|
|
465
|
+
return ipList.forEach((ip) => {
|
|
466
|
+
Dns.unbanIngress(ip);
|
|
467
|
+
Dns.unbanEgress(ip);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const ip = await Dns.getPublicIp();
|
|
472
|
+
if (options.copy) return pbcopy(ip);
|
|
473
|
+
console.log(ip);
|
|
474
|
+
return ip;
|
|
475
|
+
}
|
|
235
476
|
}
|
|
236
477
|
|
|
237
478
|
/**
|
package/src/server/process.js
CHANGED
|
@@ -96,10 +96,15 @@ const ProcessController = {
|
|
|
96
96
|
* @param {boolean} [options.async=false] - Run command asynchronously.
|
|
97
97
|
* @param {boolean} [options.stdout=false] - Return stdout content (string) instead of shelljs result object.
|
|
98
98
|
* @param {boolean} [options.disableLog=false] - Prevent logging of the command.
|
|
99
|
+
* @param {Function} [options.callback=null] - Callback function for asynchronous execution.
|
|
99
100
|
* @returns {string|shelljs.ShellString} The result of the shell command (string if `stdout: true`, otherwise a ShellString object).
|
|
100
101
|
*/
|
|
101
|
-
const shellExec = (
|
|
102
|
+
const shellExec = (
|
|
103
|
+
cmd,
|
|
104
|
+
options = { silent: false, async: false, stdout: false, disableLog: false, callback: null },
|
|
105
|
+
) => {
|
|
102
106
|
if (!options.disableLog) logger.info(`cmd`, cmd);
|
|
107
|
+
if (options.callback) return shell.exec(cmd, options, options.callback);
|
|
103
108
|
return options.stdout ? shell.exec(cmd, options).stdout : shell.exec(cmd, options);
|
|
104
109
|
};
|
|
105
110
|
|
package/src/server/start.js
CHANGED
|
@@ -119,7 +119,9 @@ class UnderpostStartUp {
|
|
|
119
119
|
* @param {boolean} options.run - Whether to run the deployment.
|
|
120
120
|
*/
|
|
121
121
|
async callback(deployId = 'dd-default', env = 'development', options = { build: false, run: false }) {
|
|
122
|
+
UnderpostRootEnv.API.set('container-status', `${deployId}-${env}-build-deployment`);
|
|
122
123
|
if (options.build === true) await UnderpostStartUp.API.build(deployId, env);
|
|
124
|
+
UnderpostRootEnv.API.set('container-status', `${deployId}-${env}-initializing-deployment`);
|
|
123
125
|
if (options.run === true) await UnderpostStartUp.API.run(deployId, env);
|
|
124
126
|
},
|
|
125
127
|
/**
|
package/scripts/snap-clean.sh
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# cleanup-snap.sh
|
|
3
|
-
# Remove all disabled snap revisions to free up disk space.
|
|
4
|
-
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
# Ensure we’re running as root
|
|
8
|
-
if [[ $EUID -ne 0 ]]; then
|
|
9
|
-
echo "Please run this script with sudo or as root."
|
|
10
|
-
exit 1
|
|
11
|
-
fi
|
|
12
|
-
|
|
13
|
-
echo "Gathering list of snaps with disabled revisions..."
|
|
14
|
-
snap list --all \
|
|
15
|
-
| awk '/disabled/ {print $1, $3}' \
|
|
16
|
-
| while read -r pkg rev; do
|
|
17
|
-
echo " -> Removing $pkg (revision $rev)..."
|
|
18
|
-
snap remove "$pkg" --revision="$rev"
|
|
19
|
-
done
|
|
20
|
-
|
|
21
|
-
echo "Cleanup complete."
|
|
22
|
-
echo
|
|
23
|
-
echo "Tip: Limit how many revisions Snap retains by setting:"
|
|
24
|
-
echo " sudo snap set system refresh.retain=2"
|
|
25
|
-
echo "Then apply with:"
|
|
26
|
-
echo " sudo snap refresh"
|