underpost 2.90.1 → 2.92.0
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/release.cd.yml +7 -7
- package/README.md +6 -6
- package/bin/deploy.js +16 -127
- package/cli.md +123 -30
- package/examples/QUICK-REFERENCE.md +499 -0
- package/examples/README.md +447 -0
- package/examples/STATIC-GENERATOR-GUIDE.md +807 -0
- package/examples/ssr-components/CustomPage.js +579 -0
- package/examples/static-config-example.json +183 -0
- package/examples/static-config-simple.json +57 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/scripts/rocky-setup.sh +1 -0
- package/src/api/document/document.model.js +7 -0
- package/src/api/document/document.service.js +4 -1
- package/src/cli/db.js +1148 -197
- package/src/cli/deploy.js +17 -12
- package/src/cli/env.js +2 -2
- package/src/cli/index.js +191 -15
- package/src/cli/repository.js +127 -3
- package/src/cli/run.js +41 -12
- package/src/cli/ssh.js +424 -13
- package/src/cli/static.js +785 -49
- package/src/client/components/core/CommonJs.js +0 -1
- package/src/client/components/core/Input.js +6 -4
- package/src/client/components/core/Modal.js +13 -18
- package/src/client/components/core/Panel.js +26 -6
- package/src/client/components/core/PanelForm.js +67 -52
- package/src/db/mongo/MongooseDB.js +5 -1
- package/src/index.js +1 -1
- package/src/server/dns.js +154 -0
- package/src/server/start.js +2 -0
|
@@ -102,8 +102,8 @@ const Input = {
|
|
|
102
102
|
</div>
|
|
103
103
|
`
|
|
104
104
|
: options?.placeholderIcon
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
? html` <div class="fl input-row-${id}">${options.placeholderIcon} ${inputElement}</div> `
|
|
106
|
+
: inputElement}
|
|
107
107
|
<div class="in input-info input-info-${id}"> </div>
|
|
108
108
|
</div>
|
|
109
109
|
</div>`;
|
|
@@ -189,7 +189,7 @@ const Input = {
|
|
|
189
189
|
|
|
190
190
|
switch (inputData.inputType) {
|
|
191
191
|
case 'file':
|
|
192
|
-
if (fileObj[inputData.model] && s(`.${inputData.id}`)) {
|
|
192
|
+
if (fileObj && fileObj[inputData.model] && s(`.${inputData.id}`)) {
|
|
193
193
|
const dataTransfer = new DataTransfer();
|
|
194
194
|
|
|
195
195
|
if (fileObj[inputData.model].fileBlob)
|
|
@@ -207,7 +207,9 @@ const Input = {
|
|
|
207
207
|
continue;
|
|
208
208
|
break;
|
|
209
209
|
case 'md':
|
|
210
|
-
|
|
210
|
+
if (fileObj && fileObj[inputData.model] && fileObj[inputData.model].mdPlain) {
|
|
211
|
+
RichText.Tokens[inputData.id].easyMDE.value(fileObj[inputData.model].mdPlain);
|
|
212
|
+
}
|
|
211
213
|
continue;
|
|
212
214
|
break;
|
|
213
215
|
|
|
@@ -54,19 +54,18 @@ const Modal = {
|
|
|
54
54
|
disableBoxShadow: false,
|
|
55
55
|
},
|
|
56
56
|
) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
const originHeightBottomBar = 50;
|
|
58
|
+
const originHeightTopBar = 50;
|
|
59
|
+
options.heightBottomBar = 0;
|
|
60
|
+
options.heightTopBar = 100;
|
|
61
|
+
if (options && options.barMode && options.barMode === 'top-bottom-bar') {
|
|
62
|
+
options.heightTopBar = 50;
|
|
63
|
+
options.heightBottomBar = 50;
|
|
64
|
+
}
|
|
61
65
|
let width = 300;
|
|
62
66
|
let height = 400;
|
|
63
67
|
let top = options.style?.top ? options.style.top : 0;
|
|
64
68
|
let left = options.style?.left ? options.style.left : 0;
|
|
65
|
-
const topBottomBarEnable = options && options.barMode && options.barMode === 'top-bottom-bar';
|
|
66
|
-
if (!topBottomBarEnable) {
|
|
67
|
-
options.heightTopBar = options.heightTopBar + options.heightBottomBar;
|
|
68
|
-
options.heightBottomBar = 0;
|
|
69
|
-
}
|
|
70
69
|
let transition = `opacity 0.3s, box-shadow 0.3s, bottom 0.3s`;
|
|
71
70
|
const originSlideMenuWidth = 320;
|
|
72
71
|
const collapseSlideMenuWidth = 50;
|
|
@@ -828,8 +827,7 @@ const Modal = {
|
|
|
828
827
|
},
|
|
829
828
|
dragDisabled: true,
|
|
830
829
|
maximize: true,
|
|
831
|
-
|
|
832
|
-
heightTopBar: options.heightTopBar,
|
|
830
|
+
barMode: options.barMode,
|
|
833
831
|
});
|
|
834
832
|
|
|
835
833
|
// Bind hover/focus and click-outside to dismiss
|
|
@@ -997,8 +995,6 @@ const Modal = {
|
|
|
997
995
|
dragDisabled: true,
|
|
998
996
|
maximize: true,
|
|
999
997
|
slideMenu: 'modal-menu',
|
|
1000
|
-
heightTopBar: originHeightTopBar,
|
|
1001
|
-
heightBottomBar: originHeightBottomBar,
|
|
1002
998
|
barMode: options.barMode,
|
|
1003
999
|
observer: true,
|
|
1004
1000
|
disableBoxShadow: true,
|
|
@@ -1150,7 +1146,7 @@ const Modal = {
|
|
|
1150
1146
|
dragDisabled: true,
|
|
1151
1147
|
disableCenter: true,
|
|
1152
1148
|
// maximize: true,
|
|
1153
|
-
|
|
1149
|
+
barMode: options.barMode,
|
|
1154
1150
|
});
|
|
1155
1151
|
Responsive.Event[`view-${id}`] = () => {
|
|
1156
1152
|
if (!this.Data[id] || !s(`.${id}`)) return delete Responsive.Event[`view-${id}`];
|
|
@@ -1259,8 +1255,6 @@ const Modal = {
|
|
|
1259
1255
|
},
|
|
1260
1256
|
dragDisabled: true,
|
|
1261
1257
|
maximize: true,
|
|
1262
|
-
heightBottomBar: 0,
|
|
1263
|
-
heightTopBar: originHeightTopBar,
|
|
1264
1258
|
barMode: options.barMode,
|
|
1265
1259
|
});
|
|
1266
1260
|
|
|
@@ -1319,8 +1313,6 @@ const Modal = {
|
|
|
1319
1313
|
},
|
|
1320
1314
|
dragDisabled: true,
|
|
1321
1315
|
maximize: true,
|
|
1322
|
-
heightTopBar: originHeightTopBar,
|
|
1323
|
-
heightBottomBar: originHeightBottomBar,
|
|
1324
1316
|
barMode: options.barMode,
|
|
1325
1317
|
});
|
|
1326
1318
|
|
|
@@ -2489,6 +2481,9 @@ const subMenuHandler = (routes, route) => {
|
|
|
2489
2481
|
}
|
|
2490
2482
|
setTimeout(() => {
|
|
2491
2483
|
let cid = getQueryParams().cid;
|
|
2484
|
+
if (cid && cid.includes(',')) {
|
|
2485
|
+
cid = cid.split(',')[0];
|
|
2486
|
+
}
|
|
2492
2487
|
if (s(`.main-sub-btn-active`)) s(`.main-sub-btn-active`).classList.remove('main-sub-btn-active');
|
|
2493
2488
|
if (cid && s(`.btn-${route}-${cid}`)) {
|
|
2494
2489
|
s(`.btn-${route}-${cid}`).classList.add('main-sub-btn-active');
|
|
@@ -108,12 +108,32 @@ const Panel = {
|
|
|
108
108
|
openPanelForm();
|
|
109
109
|
// s(`.btn-${idPanel}-add`).click();
|
|
110
110
|
s(`.${scrollClassContainer}`).scrollTop = 0;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
);
|
|
111
|
+
|
|
112
|
+
const originData = options.originData();
|
|
113
|
+
const filesData = options.filesData();
|
|
114
|
+
|
|
115
|
+
// Convert IDs to strings for comparison to handle ObjectId vs string issues
|
|
116
|
+
const searchId = String(obj._id || obj.id);
|
|
117
|
+
const foundOrigin = originData.find((d) => String(d._id || d.id) === searchId);
|
|
118
|
+
const foundFiles = filesData.find((d) => String(d._id || d.id) === searchId);
|
|
119
|
+
|
|
120
|
+
if (!foundOrigin) {
|
|
121
|
+
logger.error('Could not find origin data for ID:', searchId);
|
|
122
|
+
logger.error(
|
|
123
|
+
'Available originData IDs:',
|
|
124
|
+
originData.map((d) => String(d._id || d.id)),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!foundFiles) {
|
|
129
|
+
logger.error('Could not find files data for ID:', searchId);
|
|
130
|
+
logger.error(
|
|
131
|
+
'Available filesData IDs:',
|
|
132
|
+
filesData.map((d) => String(d._id || d.id)),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
Input.setValues(formData, obj, foundOrigin, foundFiles);
|
|
117
137
|
if (options.on.initEdit) await options.on.initEdit({ data: obj });
|
|
118
138
|
});
|
|
119
139
|
s(`.a-${payload._id}`).onclick = async (e) => {
|
|
@@ -230,9 +230,10 @@ const PanelForm = {
|
|
|
230
230
|
let message = '';
|
|
231
231
|
let status = 'success';
|
|
232
232
|
let indexFormDoc = -1;
|
|
233
|
-
const filesData = data.fileId ? data.fileId : [null];
|
|
234
233
|
|
|
235
|
-
|
|
234
|
+
const inputFiles = data.fileId ? data.fileId : [null];
|
|
235
|
+
|
|
236
|
+
for (const file of inputFiles) {
|
|
236
237
|
indexFormDoc++;
|
|
237
238
|
let fileId;
|
|
238
239
|
|
|
@@ -247,8 +248,17 @@ const PanelForm = {
|
|
|
247
248
|
status,
|
|
248
249
|
});
|
|
249
250
|
if (status === 'success') {
|
|
250
|
-
|
|
251
|
-
|
|
251
|
+
// Identify files by comparing filename instead of just mimetype
|
|
252
|
+
// This handles the case where an .md file is uploaded as the optional file
|
|
253
|
+
// - mdFileId: matches the generated mdFileName from the title
|
|
254
|
+
// - fileId: any other file (including other .md files)
|
|
255
|
+
for (const uploadedFile of data) {
|
|
256
|
+
if (uploadedFile.name === mdFileName) {
|
|
257
|
+
mdFileId = uploadedFile._id;
|
|
258
|
+
} else {
|
|
259
|
+
fileId = uploadedFile._id;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
252
262
|
}
|
|
253
263
|
})();
|
|
254
264
|
const body = {
|
|
@@ -329,8 +339,12 @@ const PanelForm = {
|
|
|
329
339
|
|
|
330
340
|
const getPanelData = async (isLoadMore = false) => {
|
|
331
341
|
const panelData = PanelForm.Data[idPanel];
|
|
342
|
+
logger.warn('getPanelData called, isLoadMore:', isLoadMore);
|
|
332
343
|
try {
|
|
333
|
-
if (panelData.loading || !panelData.hasMore)
|
|
344
|
+
if (panelData.loading || !panelData.hasMore) {
|
|
345
|
+
logger.warn('getPanelData early return - loading:', panelData.loading, 'hasMore:', panelData.hasMore);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
334
348
|
panelData.loading = true;
|
|
335
349
|
|
|
336
350
|
if (!isLoadMore) {
|
|
@@ -364,45 +378,51 @@ const PanelForm = {
|
|
|
364
378
|
let mdBlob, fileBlob;
|
|
365
379
|
let mdPlain, filePlain;
|
|
366
380
|
|
|
367
|
-
{
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
// const ext = file.name.split('.')[file.name.split('.').length - 1];
|
|
373
|
-
mdBlob = file;
|
|
374
|
-
mdPlain = await getRawContentFile(getBlobFromUint8ArrayFile(file.data.data, file.mimetype));
|
|
375
|
-
mdFileId = newInstance(mdPlain);
|
|
376
|
-
}
|
|
377
|
-
if (documentObject.fileId) {
|
|
378
|
-
const {
|
|
379
|
-
data: [file],
|
|
380
|
-
} = await FileService.get({ id: documentObject.fileId._id });
|
|
381
|
+
try {
|
|
382
|
+
{
|
|
383
|
+
const {
|
|
384
|
+
data: [file],
|
|
385
|
+
} = await FileService.get({ id: documentObject.mdFileId._id });
|
|
381
386
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
+
// const ext = file.name.split('.')[file.name.split('.').length - 1];
|
|
388
|
+
mdBlob = file;
|
|
389
|
+
mdPlain = await getRawContentFile(getBlobFromUint8ArrayFile(file.data.data, file.mimetype));
|
|
390
|
+
mdFileId = newInstance(mdPlain);
|
|
391
|
+
}
|
|
392
|
+
if (documentObject.fileId) {
|
|
393
|
+
const {
|
|
394
|
+
data: [file],
|
|
395
|
+
} = await FileService.get({ id: documentObject.fileId._id });
|
|
396
|
+
|
|
397
|
+
// const ext = file.name.split('.')[file.name.split('.').length - 1];
|
|
398
|
+
fileBlob = file;
|
|
399
|
+
filePlain = undefined;
|
|
400
|
+
fileId = getSrcFromFileData(file);
|
|
401
|
+
}
|
|
387
402
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
403
|
+
panelData.filesData.push({
|
|
404
|
+
id: documentObject._id,
|
|
405
|
+
_id: documentObject._id,
|
|
406
|
+
mdFileId: { mdBlob, mdPlain },
|
|
407
|
+
fileId: { fileBlob, filePlain },
|
|
408
|
+
});
|
|
394
409
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
410
|
+
panelData.data.push({
|
|
411
|
+
id: documentObject._id,
|
|
412
|
+
title: documentObject.title,
|
|
413
|
+
createdAt: documentObject.createdAt,
|
|
414
|
+
tags: documentObject.tags.filter((t) => !prefixTags.includes(t)),
|
|
415
|
+
mdFileId: marked.parse(mdFileId),
|
|
416
|
+
userId: documentObject.userId._id,
|
|
417
|
+
fileId,
|
|
418
|
+
tools: Elements.Data.user.main.model.user._id === documentObject.userId._id,
|
|
419
|
+
_id: documentObject._id,
|
|
420
|
+
});
|
|
421
|
+
} catch (fileError) {
|
|
422
|
+
logger.error('Error fetching files for document:', documentObject._id, fileError);
|
|
423
|
+
// Still add the document to originData even if file fetching fails
|
|
424
|
+
// but skip adding to data and filesData arrays
|
|
425
|
+
}
|
|
406
426
|
}
|
|
407
427
|
|
|
408
428
|
panelData.skip += result.data.data.length;
|
|
@@ -497,7 +517,11 @@ const PanelForm = {
|
|
|
497
517
|
JSON.stringify(Elements.Data.user.main.model.user, null, 4),
|
|
498
518
|
);
|
|
499
519
|
|
|
500
|
-
|
|
520
|
+
// Normalize empty values for comparison (undefined, null, '' should all be treated as empty)
|
|
521
|
+
const normalizedCid = cid || '';
|
|
522
|
+
const normalizedLastCid = lastCid || '';
|
|
523
|
+
|
|
524
|
+
if (loadingGetData || (normalizedLastCid === normalizedCid && !forceUpdate)) return;
|
|
501
525
|
loadingGetData = true;
|
|
502
526
|
lastUserId = newInstance(Elements.Data.user.main.model.user._id);
|
|
503
527
|
lastCid = cid;
|
|
@@ -579,16 +603,7 @@ const PanelForm = {
|
|
|
579
603
|
id: options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body',
|
|
580
604
|
routeId: options.route,
|
|
581
605
|
event: async (path) => {
|
|
582
|
-
|
|
583
|
-
...PanelForm.Data[idPanel],
|
|
584
|
-
originData: [],
|
|
585
|
-
data: [],
|
|
586
|
-
filesData: [],
|
|
587
|
-
// skip: 0,
|
|
588
|
-
limit: 3, // Load 5 items per page
|
|
589
|
-
hasMore: true,
|
|
590
|
-
loading: false,
|
|
591
|
-
};
|
|
606
|
+
// Don't manually clear arrays - updatePanel() will handle it if needed
|
|
592
607
|
await PanelForm.Data[idPanel].updatePanel();
|
|
593
608
|
},
|
|
594
609
|
});
|
|
@@ -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/server/dns.js
CHANGED
|
@@ -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.
|
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
|
/**
|