underpost 2.8.885 → 2.8.886

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.
Files changed (66) hide show
  1. package/.env.production +3 -0
  2. package/.github/workflows/ghpkg.ci.yml +1 -1
  3. package/.github/workflows/npmpkg.ci.yml +1 -1
  4. package/.github/workflows/publish.ci.yml +5 -5
  5. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -1
  6. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  7. package/CHANGELOG.md +145 -1
  8. package/Dockerfile +1 -1
  9. package/README.md +3 -3
  10. package/bin/build.js +18 -9
  11. package/bin/deploy.js +93 -187
  12. package/cli.md +2 -2
  13. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  14. package/manifests/deployment/dd-test-development/deployment.yaml +54 -54
  15. package/manifests/deployment/dd-test-development/proxy.yaml +4 -4
  16. package/manifests/lxd/underpost-setup.sh +5 -5
  17. package/package.json +3 -3
  18. package/scripts/ssl.sh +164 -0
  19. package/src/cli/baremetal.js +7 -7
  20. package/src/cli/cloud-init.js +1 -1
  21. package/src/cli/cluster.js +10 -3
  22. package/src/cli/cron.js +1 -1
  23. package/src/cli/db.js +1 -1
  24. package/src/cli/deploy.js +33 -1
  25. package/src/cli/fs.js +2 -2
  26. package/src/cli/image.js +7 -0
  27. package/src/cli/monitor.js +33 -1
  28. package/src/cli/run.js +315 -51
  29. package/src/cli/script.js +32 -0
  30. package/src/cli/secrets.js +34 -0
  31. package/src/cli/test.js +42 -1
  32. package/src/client/components/core/Css.js +0 -8
  33. package/src/client/components/core/windowGetDimensions.js +229 -162
  34. package/src/index.js +2 -2
  35. package/src/mailer/MailerProvider.js +1 -0
  36. package/src/runtime/express/Express.js +12 -4
  37. package/src/runtime/lampp/Dockerfile +1 -1
  38. package/src/server/backup.js +20 -0
  39. package/src/server/client-build-live.js +12 -10
  40. package/src/server/client-build.js +136 -91
  41. package/src/server/client-dev-server.js +16 -2
  42. package/src/server/client-icons.js +19 -0
  43. package/src/server/conf.js +470 -60
  44. package/src/server/dns.js +184 -42
  45. package/src/server/downloader.js +65 -24
  46. package/src/server/object-layer.js +260 -162
  47. package/src/server/peer.js +2 -8
  48. package/src/server/proxy.js +93 -76
  49. package/src/server/runtime.js +15 -16
  50. package/src/server/ssr.js +4 -4
  51. package/src/server/tls.js +251 -0
  52. package/src/server/valkey.js +11 -10
  53. package/src/ws/IoInterface.js +2 -1
  54. package/src/ws/IoServer.js +2 -1
  55. package/src/ws/core/core.ws.connection.js +1 -1
  56. package/src/ws/core/core.ws.emit.js +1 -1
  57. package/src/ws/core/core.ws.server.js +1 -1
  58. package/manifests/maas/lxd-preseed.yaml +0 -32
  59. package/src/server/ssl.js +0 -108
  60. /package/{manifests/maas → scripts}/device-scan.sh +0 -0
  61. /package/{manifests/maas → scripts}/gpu-diag.sh +0 -0
  62. /package/{manifests/maas → scripts}/maas-setup.sh +0 -0
  63. /package/{manifests/maas → scripts}/nat-iptables.sh +0 -0
  64. /package/{manifests/maas → scripts}/nvim.sh +0 -0
  65. /package/{manifests/maas → scripts}/snap-clean.sh +0 -0
  66. /package/{manifests/maas → scripts}/ssh-cluster-info.sh +0 -0
package/src/server/dns.js CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Provides a comprehensive set of DNS and IP management utilities,
3
+ * primarily focused on dynamic DNS (DDNS) updates and network checks.
4
+ * @module src/server/dns.js
5
+ * @namespace DnsManager
6
+ */
1
7
  import axios from 'axios';
2
8
  import dotenv from 'dotenv';
3
9
  import fs from 'fs';
@@ -13,52 +19,109 @@ dotenv.config();
13
19
 
14
20
  const logger = loggerFactory(import.meta);
15
21
 
16
- const ip = {
17
- public: {
18
- get: async () => await publicIp(), // => 'fe80::200:f8ff:fe21:67cf'
19
- ipv4: async () => await publicIpv4(), // => '46.5.21.123'
20
- ipv6: async () => await publicIpv6(), // => 'fe80::200:f8ff:fe21:67cf'
21
- },
22
- };
22
+ /**
23
+ * Main class for handling DNS and IP related operations.
24
+ * All utility methods are implemented as static to serve as a namespace container.
25
+ * @class Dns
26
+ * @augments Dns
27
+ * @memberof DnsManager
28
+ */
29
+ class Dns {
30
+ /**
31
+ * Retrieves the current public IP address (IPv4 or IPv6).
32
+ * @async
33
+ * @static
34
+ * @memberof DnsManager
35
+ * @returns {Promise<string>} The public IP address.
36
+ */
37
+ static async getPublicIp() {
38
+ return await publicIp();
39
+ }
23
40
 
24
- const isInternetConnection = (domain = 'google.com') =>
25
- new Promise((resolve) => dns.lookup(domain, {}, (err) => resolve(err ? false : true)));
41
+ /**
42
+ * Retrieves the current public IPv4 address.
43
+ * @async
44
+ * @static
45
+ * @memberof DnsManager
46
+ * @returns {Promise<string>} The public IPv4 address.
47
+ */
48
+ static async getPublicIpv4() {
49
+ return await publicIpv4();
50
+ }
26
51
 
27
- // export INTERFACE=$(ip route | grep default | cut -d ' ' -f 5)
28
- // export IP_ADDRESS=$(ip -4 addr show dev $INTERFACE | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
29
- const getLocalIPv4Address = () =>
30
- os.networkInterfaces()[
31
- shellExec(`ip route | grep default | cut -d ' ' -f 5`, {
52
+ /**
53
+ * Retrieves the current public IPv6 address.
54
+ * @async
55
+ * @static
56
+ * @memberof DnsManager
57
+ * @returns {Promise<string>} The public IPv6 address.
58
+ */
59
+ static async getPublicIpv6() {
60
+ return await publicIpv6();
61
+ }
62
+
63
+ /**
64
+ * Checks for active internet connection by performing a DNS lookup on a specified domain.
65
+ * @static
66
+ * @memberof DnsManager
67
+ * @param {string} [domain='google.com'] The domain to check the connection against.
68
+ * @returns {Promise<boolean>} True if connected, false otherwise.
69
+ */
70
+ static isInternetConnection(domain = 'google.com') {
71
+ return new Promise((resolve) => dns.lookup(domain, {}, (err) => resolve(err ? false : true)));
72
+ }
73
+
74
+ /**
75
+ * Gets the local device's IPv4 address by determining the active network interface.
76
+ * This relies on shell execution (`ip route`) and is primarily intended for Linux environments.
77
+ * @static
78
+ * @memberof DnsManager
79
+ * @returns {string} The local IPv4 address.
80
+ */
81
+ static getLocalIPv4Address() {
82
+ // Determine the default network interface name using shell command
83
+ const interfaceName = shellExec(`ip route | grep default | cut -d ' ' -f 5`, {
32
84
  stdout: true,
33
85
  silent: true,
34
86
  disableLog: true,
35
- }).trim()
36
- ].find((i) => i.family === 'IPv4').address;
87
+ }).trim();
37
88
 
38
- class Dns {
39
- static callback = async function (deployList) {
40
- // Network topology configuration:
41
- // LAN -> [NAT-VPS](modem/router device) -> WAN
42
- // enabled DMZ Host to proxy IP 80-443 (79-444) sometimes router block first port
43
-
44
- // Enabling DHCP
45
- // Navigate to Subnets > VLAN > Configure DHCP.
46
- // Select the appropriate DHCP options (Managed or Relay).
47
- // Save and apply changes.
48
-
49
- // verify inet ip proxy server address
50
- // DHCP (Dynamic Host Configuration Protocol) LAN reserver IP -> MAC ID
51
- // LAN server or device's local servers port -> 3000-3100 (2999-3101)
52
- // DNS Records: [ANAME](Address Dynamic) -> [A](ipv4) host | [AAAA](ipv6) host -> [public-ip]
53
- // Forward the router's TCP/UDP ports to the LAN device's IP address
54
- const isOnline = await isInternetConnection();
89
+ // Find the IPv4 address associated with the determined interface
90
+ const networkInfo = os.networkInterfaces()[interfaceName];
91
+
92
+ if (!networkInfo) {
93
+ logger.error(`Could not find network interface: ${interfaceName}`);
94
+ return null;
95
+ }
96
+
97
+ const ipv4 = networkInfo.find((i) => i.family === 'IPv4');
98
+
99
+ if (!ipv4) {
100
+ logger.error(`Could not find IPv4 address for interface: ${interfaceName}`);
101
+ return null;
102
+ }
103
+
104
+ return ipv4.address;
105
+ }
106
+
107
+ /**
108
+ * Performs the dynamic DNS update logic.
109
+ * It checks if the public IP has changed and, if so, updates the configured DNS records.
110
+ * @async
111
+ * @static
112
+ * @memberof DnsManager
113
+ * @param {string} deployList Comma-separated string of deployment IDs to process.
114
+ * @returns {Promise<void>}
115
+ */
116
+ static async callback(deployList) {
117
+ const isOnline = await Dns.isInternetConnection();
55
118
 
56
119
  if (!isOnline) return;
57
120
 
58
121
  let testIp;
59
122
 
60
123
  try {
61
- testIp = await ip.public.ipv4();
124
+ testIp = await Dns.getPublicIpv4();
62
125
  } catch (error) {
63
126
  logger.error(error, { testIp, stack: error.stack });
64
127
  }
@@ -66,52 +129,97 @@ class Dns {
66
129
  const currentIp = UnderpostRootEnv.API.get('ip');
67
130
 
68
131
  if (validator.isIP(testIp) && currentIp !== testIp) {
69
- logger.info(`new ip`, testIp);
132
+ logger.info(`New IP detected`, testIp);
70
133
  UnderpostRootEnv.API.set('monitor-input', 'pause');
71
134
 
72
135
  for (const _deployId of deployList.split(',')) {
73
136
  const deployId = _deployId.trim();
74
137
  const privateCronConfPath = `./engine-private/conf/${deployId}/conf.cron.json`;
75
138
  const confCronPath = fs.existsSync(privateCronConfPath) ? privateCronConfPath : './conf/conf.cron.json';
139
+
140
+ if (!fs.existsSync(confCronPath)) {
141
+ logger.warn(`Cron config file not found for deployId: ${deployId} at ${confCronPath}`);
142
+ continue;
143
+ }
144
+
76
145
  const confCronData = JSON.parse(fs.readFileSync(confCronPath, 'utf8'));
146
+
147
+ if (!confCronData.records) {
148
+ logger.warn(`'records' field missing in cron config for deployId: ${deployId}`);
149
+ continue;
150
+ }
151
+
152
+ // Iterate through DNS record types (A, AAAA, etc.)
77
153
  for (const recordType of Object.keys(confCronData.records)) {
78
154
  switch (recordType) {
79
155
  case 'A':
156
+ // Process A records for IPv4 update
80
157
  for (const dnsProvider of confCronData.records[recordType]) {
81
158
  if (typeof Dns.services.updateIp[dnsProvider.dns] === 'function')
82
159
  await Dns.services.updateIp[dnsProvider.dns]({ ...dnsProvider, ip: testIp });
83
160
  }
84
161
  break;
85
162
 
163
+ // Add other record types (e.g., AAAA) here if needed
86
164
  default:
87
165
  break;
88
166
  }
89
167
  }
168
+
169
+ // Verify the IP update externally
90
170
  try {
91
171
  const ipUrlTest = `https://${process.env.DEFAULT_DEPLOY_HOST}`;
92
172
  const response = await axios.get(ipUrlTest);
93
173
  const verifyIp = response.request.socket.remoteAddress;
94
174
  logger.info(ipUrlTest + ' verify ip', verifyIp);
95
175
  if (verifyIp === testIp) {
96
- logger.info('ip updated successfully', testIp);
176
+ logger.info('IP updated successfully and verified', testIp);
97
177
  UnderpostRootEnv.API.set('ip', testIp);
98
178
  UnderpostRootEnv.API.delete('monitor-input');
99
- } else logger.error('ip not updated', testIp);
179
+ } else {
180
+ logger.error('IP not updated or verification failed', { expected: testIp, received: verifyIp });
181
+ }
100
182
  } catch (error) {
101
- logger.error(error, error.stack);
102
- logger.error('ip not updated', testIp);
183
+ logger.error('Error during IP update verification step', {
184
+ error: error.message,
185
+ stack: error.stack,
186
+ testIp,
187
+ });
103
188
  }
104
189
  }
105
190
  }
106
- };
191
+ }
107
192
 
193
+ /**
194
+ * Internal collection of external DNS service update functions.
195
+ * @static
196
+ * @memberof DnsManager
197
+ * @property {object} updateIp - Functions keyed by DNS provider name to update A/AAAA records.
198
+ */
108
199
  static services = {
109
200
  updateIp: {
201
+ /**
202
+ * Updates the IP address for a dondominio.com DNS record.
203
+ * @memberof DnsManager
204
+ * @param {object} options
205
+ * @param {string} options.user - The dondominio DDNS username.
206
+ * @param {string} options.api_key - The dondominio DDNS password/API key.
207
+ * @param {string} options.host - The hostname to update.
208
+ * @param {string} options.dns - The name of the DNS provider ('dondominio').
209
+ * @param {string} options.ip - The new IPv4 address to set.
210
+ * @returns {Promise<boolean>} True on success, false on failure.
211
+ */
110
212
  dondominio: (options) => {
111
213
  const { user, api_key, host, dns, ip } = options;
112
214
  const url = `https://dondns.dondominio.com/json/?user=${user}&password=${api_key}&host=${host}&ip=${ip}`;
113
215
  logger.info(`${dns} update ip url`, url);
114
- if (process.env.NODE_ENV !== 'production') return false;
216
+
217
+ // Prevent live IP update in non-production environments
218
+ if (process.env.NODE_ENV !== 'production') {
219
+ logger.warn('Skipping dondominio update in non-production environment.');
220
+ return Promise.resolve(false);
221
+ }
222
+
115
223
  return new Promise((resolve) => {
116
224
  axios
117
225
  .get(url)
@@ -120,15 +228,49 @@ class Dns {
120
228
  return resolve(true);
121
229
  })
122
230
  .catch((error) => {
123
- logger.error(error, `${dns} update ip error`);
231
+ logger.error(error, `${dns} update ip error: ${error.message}`);
124
232
  return resolve(false);
125
233
  });
126
234
  });
127
235
  },
236
+ // Add other DNS provider update functions here
128
237
  },
129
238
  };
130
239
  }
131
240
 
241
+ /**
242
+ * @namespace Dns
243
+ * @description Exported IP object for backward compatibility, mapping to Dns static methods.
244
+ */
245
+ const ip = {
246
+ public: {
247
+ /** @type {function(): Promise<string>} */
248
+ get: Dns.getPublicIp,
249
+ /** @type {function(): Promise<string>} */
250
+ ipv4: Dns.getPublicIpv4,
251
+ /** @type {function(): Promise<string>} */
252
+ ipv6: Dns.getPublicIpv6,
253
+ },
254
+ };
255
+
256
+ /**
257
+ * @function isInternetConnection
258
+ * @memberof DnsManager
259
+ * @description Exported function for backward compatibility.
260
+ * @param {string} [domain='google.com']
261
+ * @returns {Promise<boolean>}
262
+ */
263
+ const isInternetConnection = Dns.isInternetConnection;
264
+
265
+ /**
266
+ * @function getLocalIPv4Address
267
+ * @memberof DnsManager
268
+ * @description Exported function for backward compatibility.
269
+ * @returns {string}
270
+ */
271
+ const getLocalIPv4Address = Dns.getLocalIPv4Address;
272
+
273
+ // Export the class as default and all original identifiers for backward compatibility.
132
274
  export default Dns;
133
275
 
134
276
  export { Dns, ip, isInternetConnection, getLocalIPv4Address };
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Provides a utility class for downloading files from a URL and saving them to the local filesystem.
3
+ * @module src/server/downloader.js
4
+ * @namespace Downloader
5
+ */
6
+
1
7
  import axios from 'axios';
2
8
  import fs from 'fs';
3
9
  import { loggerFactory } from './logger.js';
@@ -6,29 +12,64 @@ dotenv.config();
6
12
 
7
13
  const logger = loggerFactory(import.meta);
8
14
 
9
- const Downloader = (url, fullPath, options = { method: 'get', responseType: 'stream' }) =>
10
- new Promise((resolve, reject) =>
11
- axios({
12
- url,
13
- ...options,
14
- })
15
- .then((response) => {
16
- // Create a write stream to save the file to the specified path
17
- const writer = fs.createWriteStream(fullPath);
18
- response.data.pipe(writer);
19
- writer.on('finish', () => {
20
- logger.info('Download complete. File saved at', fullPath);
21
- return resolve(fullPath);
22
- });
23
- writer.on('error', (error) => {
24
- logger.error(error, 'Error downloading the file');
25
- return reject(error);
26
- });
15
+ /**
16
+ * Main class for handling file downloading operations.
17
+ * All utility methods are implemented as static to serve as a namespace container.
18
+ * @class Downloader
19
+ * @augments Downloader
20
+ * @memberof Downloader
21
+ */
22
+ class Downloader {
23
+ /**
24
+ * Downloads a file from a given URL and pipes the stream to a local file path.
25
+ * @static
26
+ * @memberof Downloader
27
+ * @param {string} url The URL of the file to download.
28
+ * @param {string} fullPath The full local path where the file should be saved.
29
+ * @param {object} [options] Axios request configuration options.
30
+ * @param {string} [options.method='get'] HTTP method.
31
+ * @param {string} [options.responseType='stream'] Expected response type.
32
+ * @returns {Promise<string>} Resolves with the full path of the saved file on success.
33
+ * @memberof Downloader
34
+ */
35
+ static downloadFile(url, fullPath, options = { method: 'get', responseType: 'stream' }) {
36
+ return new Promise((resolve, reject) =>
37
+ axios({
38
+ url,
39
+ ...options,
27
40
  })
28
- .catch((error) => {
29
- logger.error(error, 'Error in the request');
30
- return reject(error);
31
- }),
32
- );
41
+ .then((response) => {
42
+ // Create a write stream to save the file to the specified path
43
+ const writer = fs.createWriteStream(fullPath);
44
+ response.data.pipe(writer);
45
+ writer.on('finish', () => {
46
+ logger.info('Download complete. File saved at', fullPath);
47
+ return resolve(fullPath);
48
+ });
49
+ writer.on('error', (error) => {
50
+ logger.error(error, 'Error downloading the file');
51
+ // Cleanup incomplete file if possible
52
+ if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
53
+ return reject(error);
54
+ });
55
+ })
56
+ .catch((error) => {
57
+ logger.error(error, 'Error in the request');
58
+ return reject(error);
59
+ }),
60
+ );
61
+ }
62
+ }
63
+
64
+ export default Downloader;
33
65
 
34
- export { Downloader };
66
+ /**
67
+ * @function downloadFile
68
+ * @description Backward compatibility export for `Downloader.downloadFile`.
69
+ * @param {string} url The URL of the file to download.
70
+ * @param {string} fullPath The full local path where the file should be saved.
71
+ * @param {object} [options] Axios request configuration options.
72
+ * @returns {Promise<string>} Resolves with the full path of the saved file on success.
73
+ * @memberof Downloader
74
+ */
75
+ export const downloadFile = Downloader.downloadFile;