underpost 2.8.885 → 2.81.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.
Files changed (72) 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/.vscode/zed.keymap.json +17 -0
  8. package/.vscode/zed.settings.json +20 -0
  9. package/CHANGELOG.md +145 -1
  10. package/Dockerfile +20 -3
  11. package/README.md +6 -6
  12. package/bin/build.js +18 -9
  13. package/bin/deploy.js +130 -195
  14. package/bin/zed.js +20 -0
  15. package/cli.md +13 -7
  16. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  17. package/manifests/deployment/dd-test-development/deployment.yaml +50 -50
  18. package/manifests/deployment/dd-test-development/proxy.yaml +4 -4
  19. package/manifests/lxd/underpost-setup.sh +5 -5
  20. package/package.json +3 -4
  21. package/{manifests/maas → scripts}/ssh-cluster-info.sh +1 -1
  22. package/scripts/ssl.sh +164 -0
  23. package/src/cli/baremetal.js +8 -8
  24. package/src/cli/cloud-init.js +1 -1
  25. package/src/cli/cluster.js +15 -4
  26. package/src/cli/cron.js +1 -1
  27. package/src/cli/db.js +2 -1
  28. package/src/cli/deploy.js +65 -14
  29. package/src/cli/fs.js +2 -2
  30. package/src/cli/image.js +19 -2
  31. package/src/cli/index.js +11 -4
  32. package/src/cli/monitor.js +34 -1
  33. package/src/cli/repository.js +42 -1
  34. package/src/cli/run.js +396 -86
  35. package/src/cli/script.js +32 -0
  36. package/src/cli/secrets.js +34 -0
  37. package/src/cli/test.js +42 -1
  38. package/src/client/components/core/Css.js +0 -8
  39. package/src/client/components/core/windowGetDimensions.js +229 -162
  40. package/src/index.js +2 -2
  41. package/src/mailer/MailerProvider.js +1 -0
  42. package/src/runtime/express/Dockerfile +41 -0
  43. package/src/runtime/express/Express.js +12 -4
  44. package/src/runtime/lampp/Dockerfile +1 -1
  45. package/src/server/backup.js +20 -0
  46. package/src/server/client-build-live.js +12 -10
  47. package/src/server/client-build.js +136 -91
  48. package/src/server/client-dev-server.js +16 -2
  49. package/src/server/client-icons.js +19 -0
  50. package/src/server/conf.js +495 -69
  51. package/src/server/dns.js +169 -46
  52. package/src/server/downloader.js +65 -24
  53. package/src/server/object-layer.js +260 -162
  54. package/src/server/peer.js +2 -8
  55. package/src/server/proxy.js +93 -76
  56. package/src/server/runtime.js +15 -16
  57. package/src/server/ssr.js +4 -4
  58. package/src/server/tls.js +251 -0
  59. package/src/server/valkey.js +11 -10
  60. package/src/ws/IoInterface.js +2 -1
  61. package/src/ws/IoServer.js +2 -1
  62. package/src/ws/core/core.ws.connection.js +1 -1
  63. package/src/ws/core/core.ws.emit.js +1 -1
  64. package/src/ws/core/core.ws.server.js +1 -1
  65. package/manifests/maas/lxd-preseed.yaml +0 -32
  66. package/src/server/ssl.js +0 -108
  67. /package/{manifests/maas → scripts}/device-scan.sh +0 -0
  68. /package/{manifests/maas → scripts}/gpu-diag.sh +0 -0
  69. /package/{manifests/maas → scripts}/maas-setup.sh +0 -0
  70. /package/{manifests/maas → scripts}/nat-iptables.sh +0 -0
  71. /package/{manifests/maas → scripts}/nvim.sh +0 -0
  72. /package/{manifests/maas → scripts}/snap-clean.sh +0 -0
package/src/server/dns.js CHANGED
@@ -1,8 +1,13 @@
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';
4
10
  import validator from 'validator';
5
- import { publicIp, publicIpv4, publicIpv6 } from 'public-ip';
6
11
  import { loggerFactory } from './logger.js';
7
12
  import UnderpostRootEnv from '../cli/env.js';
8
13
  import dns from 'node:dns';
@@ -13,52 +18,106 @@ dotenv.config();
13
18
 
14
19
  const logger = loggerFactory(import.meta);
15
20
 
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
- };
23
-
24
- const isInternetConnection = (domain = 'google.com') =>
25
- new Promise((resolve) => dns.lookup(domain, {}, (err) => resolve(err ? false : true)));
26
-
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`, {
21
+ /**
22
+ * Main class for handling DNS and IP related operations.
23
+ * All utility methods are implemented as static to serve as a namespace container.
24
+ * @class Dns
25
+ * @augments Dns
26
+ * @memberof DnsManager
27
+ */
28
+ class Dns {
29
+ /**
30
+ * Retrieves the current public IP address (IPv4 or IPv6).
31
+ * @async
32
+ * @static
33
+ * @memberof DnsManager
34
+ * @returns {Promise<string>} The public IP address.
35
+ */
36
+ static async getPublicIp() {
37
+ return await new Promise(async (resolve) => {
38
+ try {
39
+ return axios.get('https://api.ipify.org').then((response) => resolve(response.data));
40
+ } catch (error) {
41
+ logger.error('Error fetching public IP:', { error: error.message, stack: error.stack });
42
+ return resolve(null);
43
+ }
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Checks for active internet connection by performing a DNS lookup on a specified domain.
49
+ * @static
50
+ * @memberof DnsManager
51
+ * @param {string} [domain='google.com'] The domain to check the connection against.
52
+ * @returns {Promise<boolean>} True if connected, false otherwise.
53
+ */
54
+ static isInternetConnection(domain = 'google.com') {
55
+ return new Promise((resolve) => dns.lookup(domain, {}, (err) => resolve(err ? false : true)));
56
+ }
57
+
58
+ /**
59
+ * Determines the default network interface name using shell command.
60
+ * This method is primarily intended for Linux environments.
61
+ * @static
62
+ * @memberof DnsManager
63
+ * @returns {string} The default network interface name.
64
+ * @memberof DnsManager
65
+ */
66
+ static getDefaultNetworkInterface() {
67
+ return shellExec(`ip route | grep default | cut -d ' ' -f 5`, {
32
68
  stdout: true,
33
69
  silent: true,
34
70
  disableLog: true,
35
- }).trim()
36
- ].find((i) => i.family === 'IPv4').address;
71
+ }).trim();
72
+ }
37
73
 
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();
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 = Dns.getDefaultNetworkInterface();
84
+
85
+ // Find the IPv4 address associated with the determined interface
86
+ const networkInfo = os.networkInterfaces()[interfaceName];
87
+
88
+ if (!networkInfo) {
89
+ logger.error(`Could not find network interface: ${interfaceName}`);
90
+ return null;
91
+ }
92
+
93
+ const ipv4 = networkInfo.find((i) => i.family === 'IPv4');
94
+
95
+ if (!ipv4) {
96
+ logger.error(`Could not find IPv4 address for interface: ${interfaceName}`);
97
+ return null;
98
+ }
99
+
100
+ return ipv4.address;
101
+ }
102
+
103
+ /**
104
+ * Performs the dynamic DNS update logic.
105
+ * It checks if the public IP has changed and, if so, updates the configured DNS records.
106
+ * @async
107
+ * @static
108
+ * @memberof DnsManager
109
+ * @param {string} deployList Comma-separated string of deployment IDs to process.
110
+ * @returns {Promise<void>}
111
+ */
112
+ static async callback(deployList) {
113
+ const isOnline = await Dns.isInternetConnection();
55
114
 
56
115
  if (!isOnline) return;
57
116
 
58
117
  let testIp;
59
118
 
60
119
  try {
61
- testIp = await ip.public.ipv4();
120
+ testIp = await Dns.getPublicIp();
62
121
  } catch (error) {
63
122
  logger.error(error, { testIp, stack: error.stack });
64
123
  }
@@ -66,52 +125,97 @@ class Dns {
66
125
  const currentIp = UnderpostRootEnv.API.get('ip');
67
126
 
68
127
  if (validator.isIP(testIp) && currentIp !== testIp) {
69
- logger.info(`new ip`, testIp);
128
+ logger.info(`New IP detected`, testIp);
70
129
  UnderpostRootEnv.API.set('monitor-input', 'pause');
71
130
 
72
131
  for (const _deployId of deployList.split(',')) {
73
132
  const deployId = _deployId.trim();
74
133
  const privateCronConfPath = `./engine-private/conf/${deployId}/conf.cron.json`;
75
134
  const confCronPath = fs.existsSync(privateCronConfPath) ? privateCronConfPath : './conf/conf.cron.json';
135
+
136
+ if (!fs.existsSync(confCronPath)) {
137
+ logger.warn(`Cron config file not found for deployId: ${deployId} at ${confCronPath}`);
138
+ continue;
139
+ }
140
+
76
141
  const confCronData = JSON.parse(fs.readFileSync(confCronPath, 'utf8'));
142
+
143
+ if (!confCronData.records) {
144
+ logger.warn(`'records' field missing in cron config for deployId: ${deployId}`);
145
+ continue;
146
+ }
147
+
148
+ // Iterate through DNS record types (A, AAAA, etc.)
77
149
  for (const recordType of Object.keys(confCronData.records)) {
78
150
  switch (recordType) {
79
151
  case 'A':
152
+ // Process A records for IPv4 update
80
153
  for (const dnsProvider of confCronData.records[recordType]) {
81
154
  if (typeof Dns.services.updateIp[dnsProvider.dns] === 'function')
82
155
  await Dns.services.updateIp[dnsProvider.dns]({ ...dnsProvider, ip: testIp });
83
156
  }
84
157
  break;
85
158
 
159
+ // Add other record types (e.g., AAAA) here if needed
86
160
  default:
87
161
  break;
88
162
  }
89
163
  }
164
+
165
+ // Verify the IP update externally
90
166
  try {
91
167
  const ipUrlTest = `https://${process.env.DEFAULT_DEPLOY_HOST}`;
92
168
  const response = await axios.get(ipUrlTest);
93
169
  const verifyIp = response.request.socket.remoteAddress;
94
170
  logger.info(ipUrlTest + ' verify ip', verifyIp);
95
171
  if (verifyIp === testIp) {
96
- logger.info('ip updated successfully', testIp);
172
+ logger.info('IP updated successfully and verified', testIp);
97
173
  UnderpostRootEnv.API.set('ip', testIp);
98
174
  UnderpostRootEnv.API.delete('monitor-input');
99
- } else logger.error('ip not updated', testIp);
175
+ } else {
176
+ logger.error('IP not updated or verification failed', { expected: testIp, received: verifyIp });
177
+ }
100
178
  } catch (error) {
101
- logger.error(error, error.stack);
102
- logger.error('ip not updated', testIp);
179
+ logger.error('Error during IP update verification step', {
180
+ error: error.message,
181
+ stack: error.stack,
182
+ testIp,
183
+ });
103
184
  }
104
185
  }
105
186
  }
106
- };
187
+ }
107
188
 
189
+ /**
190
+ * Internal collection of external DNS service update functions.
191
+ * @static
192
+ * @memberof DnsManager
193
+ * @property {object} updateIp - Functions keyed by DNS provider name to update A/AAAA records.
194
+ */
108
195
  static services = {
109
196
  updateIp: {
197
+ /**
198
+ * Updates the IP address for a dondominio.com DNS record.
199
+ * @memberof DnsManager
200
+ * @param {object} options
201
+ * @param {string} options.user - The dondominio DDNS username.
202
+ * @param {string} options.api_key - The dondominio DDNS password/API key.
203
+ * @param {string} options.host - The hostname to update.
204
+ * @param {string} options.dns - The name of the DNS provider ('dondominio').
205
+ * @param {string} options.ip - The new IPv4 address to set.
206
+ * @returns {Promise<boolean>} True on success, false on failure.
207
+ */
110
208
  dondominio: (options) => {
111
209
  const { user, api_key, host, dns, ip } = options;
112
210
  const url = `https://dondns.dondominio.com/json/?user=${user}&password=${api_key}&host=${host}&ip=${ip}`;
113
211
  logger.info(`${dns} update ip url`, url);
114
- if (process.env.NODE_ENV !== 'production') return false;
212
+
213
+ // Prevent live IP update in non-production environments
214
+ if (process.env.NODE_ENV !== 'production') {
215
+ logger.warn('Skipping dondominio update in non-production environment.');
216
+ return Promise.resolve(false);
217
+ }
218
+
115
219
  return new Promise((resolve) => {
116
220
  axios
117
221
  .get(url)
@@ -120,15 +224,34 @@ class Dns {
120
224
  return resolve(true);
121
225
  })
122
226
  .catch((error) => {
123
- logger.error(error, `${dns} update ip error`);
227
+ logger.error(error, `${dns} update ip error: ${error.message}`);
124
228
  return resolve(false);
125
229
  });
126
230
  });
127
231
  },
232
+ // Add other DNS provider update functions here
128
233
  },
129
234
  };
130
235
  }
131
236
 
237
+ /**
238
+ * @function isInternetConnection
239
+ * @memberof DnsManager
240
+ * @description Exported function for backward compatibility.
241
+ * @param {string} [domain='google.com']
242
+ * @returns {Promise<boolean>}
243
+ */
244
+ const isInternetConnection = Dns.isInternetConnection;
245
+
246
+ /**
247
+ * @function getLocalIPv4Address
248
+ * @memberof DnsManager
249
+ * @description Exported function for backward compatibility.
250
+ * @returns {string}
251
+ */
252
+ const getLocalIPv4Address = Dns.getLocalIPv4Address;
253
+
254
+ // Export the class as default and all original identifiers for backward compatibility.
132
255
  export default Dns;
133
256
 
134
- export { Dns, ip, isInternetConnection, getLocalIPv4Address };
257
+ export { Dns, 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;