underpost 2.8.86 → 2.8.88

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 (116) hide show
  1. package/.env.development +39 -2
  2. package/.env.production +42 -2
  3. package/.env.test +39 -2
  4. package/.github/workflows/ghpkg.ci.yml +1 -1
  5. package/.github/workflows/npmpkg.ci.yml +1 -1
  6. package/.github/workflows/pwa-microservices-template-page.cd.yml +6 -5
  7. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  8. package/.github/workflows/release.cd.yml +3 -3
  9. package/README.md +76 -2
  10. package/bin/build.js +5 -0
  11. package/bin/deploy.js +93 -27
  12. package/bin/file.js +8 -4
  13. package/bin/util.js +1 -56
  14. package/cli.md +16 -5
  15. package/conf.js +33 -7
  16. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  17. package/manifests/deployment/dd-test-development/deployment.yaml +174 -0
  18. package/manifests/deployment/dd-test-development/proxy.yaml +51 -0
  19. package/manifests/deployment/mongo-express/deployment.yaml +12 -12
  20. package/manifests/maas/nvim.sh +91 -0
  21. package/package.json +8 -15
  22. package/src/api/core/core.router.js +2 -1
  23. package/src/api/default/default.controller.js +6 -1
  24. package/src/api/default/default.router.js +6 -2
  25. package/src/api/default/default.service.js +10 -1
  26. package/src/api/document/document.controller.js +66 -0
  27. package/src/api/document/document.model.js +51 -0
  28. package/src/api/document/document.router.js +24 -0
  29. package/src/api/document/document.service.js +125 -0
  30. package/src/api/file/file.controller.js +15 -1
  31. package/src/api/file/file.router.js +2 -1
  32. package/src/api/file/file.service.js +28 -8
  33. package/src/api/test/test.router.js +1 -1
  34. package/src/api/user/postman_collection.json +216 -0
  35. package/src/api/user/user.controller.js +25 -60
  36. package/src/api/user/user.model.js +29 -7
  37. package/src/api/user/user.router.js +40 -8
  38. package/src/api/user/user.service.js +86 -35
  39. package/src/cli/baremetal.js +33 -3
  40. package/src/cli/cloud-init.js +11 -0
  41. package/src/cli/cluster.js +4 -23
  42. package/src/cli/cron.js +0 -1
  43. package/src/cli/db.js +0 -19
  44. package/src/cli/deploy.js +67 -52
  45. package/src/cli/fs.js +1 -0
  46. package/src/cli/index.js +9 -1
  47. package/src/cli/lxd.js +7 -0
  48. package/src/cli/repository.js +44 -6
  49. package/src/cli/run.js +56 -9
  50. package/src/cli/ssh.js +20 -6
  51. package/src/client/Default.index.js +42 -1
  52. package/src/client/components/core/Account.js +10 -2
  53. package/src/client/components/core/AgGrid.js +30 -8
  54. package/src/client/components/core/Auth.js +99 -56
  55. package/src/client/components/core/BtnIcon.js +3 -2
  56. package/src/client/components/core/CalendarCore.js +2 -3
  57. package/src/client/components/core/CommonJs.js +1 -2
  58. package/src/client/components/core/Content.js +15 -12
  59. package/src/client/components/core/Css.js +2 -1
  60. package/src/client/components/core/CssCore.js +18 -1
  61. package/src/client/components/core/Docs.js +5 -5
  62. package/src/client/components/core/FileExplorer.js +3 -3
  63. package/src/client/components/core/FullScreen.js +19 -28
  64. package/src/client/components/core/Input.js +22 -16
  65. package/src/client/components/core/JoyStick.js +2 -2
  66. package/src/client/components/core/LoadingAnimation.js +2 -2
  67. package/src/client/components/core/LogIn.js +16 -23
  68. package/src/client/components/core/LogOut.js +5 -1
  69. package/src/client/components/core/Logger.js +4 -1
  70. package/src/client/components/core/Modal.js +102 -87
  71. package/src/client/components/core/ObjectLayerEngine.js +229 -4
  72. package/src/client/components/core/ObjectLayerEngineModal.js +442 -0
  73. package/src/client/components/core/Pagination.js +207 -0
  74. package/src/client/components/core/Panel.js +10 -10
  75. package/src/client/components/core/PanelForm.js +130 -33
  76. package/src/client/components/core/Recover.js +2 -2
  77. package/src/client/components/core/Router.js +210 -34
  78. package/src/client/components/core/SignUp.js +1 -2
  79. package/src/client/components/core/Stream.js +1 -1
  80. package/src/client/components/core/ToggleSwitch.js +15 -1
  81. package/src/client/components/core/VanillaJs.js +3 -84
  82. package/src/client/components/core/Worker.js +2 -2
  83. package/src/client/components/default/LogInDefault.js +0 -6
  84. package/src/client/components/default/LogOutDefault.js +0 -16
  85. package/src/client/components/default/MenuDefault.js +97 -44
  86. package/src/client/components/default/RoutesDefault.js +5 -2
  87. package/src/client/public/default/assets/mailer/api-user-default-avatar.png +0 -0
  88. package/src/client/services/core/core.service.js +8 -20
  89. package/src/client/services/default/default.management.js +115 -18
  90. package/src/client/services/default/default.service.js +13 -4
  91. package/src/client/services/document/document.service.js +97 -0
  92. package/src/client/services/file/file.service.js +2 -0
  93. package/src/client/services/test/test.service.js +3 -0
  94. package/src/client/services/user/user.management.js +6 -0
  95. package/src/client/services/user/user.service.js +15 -4
  96. package/src/client/ssr/Render.js +1 -1
  97. package/src/client/ssr/head/DefaultScripts.js +3 -0
  98. package/src/client/ssr/head/Seo.js +1 -0
  99. package/src/index.js +24 -2
  100. package/src/runtime/lampp/Lampp.js +89 -2
  101. package/src/runtime/xampp/Xampp.js +48 -1
  102. package/src/server/auth.js +519 -155
  103. package/src/server/backup.js +2 -2
  104. package/src/server/client-build-docs.js +1 -1
  105. package/src/server/client-build.js +4 -12
  106. package/src/server/client-icons.js +6 -78
  107. package/src/server/conf.js +144 -141
  108. package/src/server/process.js +2 -1
  109. package/src/server/proxy.js +1 -1
  110. package/src/server/runtime.js +136 -288
  111. package/src/server/ssl.js +1 -2
  112. package/src/server/ssr.js +85 -0
  113. package/src/server/start.js +4 -4
  114. package/src/server/valkey.js +2 -1
  115. package/test/api.test.js +3 -2
  116. package/bin/cyberia0.js +0 -78
@@ -1,7 +1,7 @@
1
1
  import { Schema, model } from 'mongoose';
2
2
  import validator from 'validator';
3
3
  import { userRoleEnum } from '../../client/components/core/CommonJs.js';
4
-
4
+ import crypto from 'crypto';
5
5
  // https://mongoosejs.com/docs/2.7.x/docs/schematypes.html
6
6
 
7
7
  const UserSchema = new Schema(
@@ -22,6 +22,19 @@ const UserSchema = new Schema(
22
22
  password: { type: String, trim: true, required: 'Password is required' },
23
23
  username: { type: String, trim: true, unique: true, required: 'Username is required' },
24
24
  role: { type: String, enum: userRoleEnum, default: 'guest' },
25
+ activeSessions: {
26
+ type: [
27
+ {
28
+ tokenHash: { type: String, required: true },
29
+ ip: { type: String },
30
+ userAgent: { type: String },
31
+ expiresAt: { type: Date, required: true },
32
+ host: { type: String },
33
+ path: { type: String },
34
+ },
35
+ ],
36
+ default: [],
37
+ },
25
38
  profileImageId: { type: Schema.Types.ObjectId, ref: 'File' },
26
39
  phoneNumbers: [
27
40
  {
@@ -63,15 +76,24 @@ const UserDto = {
63
76
  return { _id: 1, username: 1, email: 1, role: 1, emailConfirmed: 1, profileImageId: 1 };
64
77
  },
65
78
  getAll: () => {
66
- return { _id: 1, name: 1 };
79
+ return { _id: 1 };
67
80
  },
68
81
  },
69
82
  auth: {
70
- // TODO: -> set login device, location, ip, fingerprint
71
- // and validate on authorization middleware
72
- // -> dynamic refresh 100 tokens per session with 12h interval
73
- // -> back secret per user, registrarion user model -> secret: { type: String }
74
- payload: (user) => ({ _id: user._id.toString(), role: user.role, email: user.email }),
83
+ payload: (user, jwtid, ip, userAgent, host, path) => {
84
+ const tokenPayload = {
85
+ _id: user._id.toString(),
86
+ role: user.role,
87
+ email: user.email,
88
+ ip,
89
+ userAgent,
90
+ host,
91
+ path,
92
+ jwtid: jwtid ?? crypto.randomBytes(8).toString('hex'),
93
+ refreshExpiresAt: Date.now() + parseInt(process.env.REFRESH_EXPIRE_MINUTES) * 60 * 1000,
94
+ };
95
+ return tokenPayload;
96
+ },
75
97
  },
76
98
  };
77
99
 
@@ -1,23 +1,28 @@
1
- import { authMiddleware, hashPassword } from '../../server/auth.js';
1
+ import { hashPassword } from '../../server/auth.js';
2
2
  import fs from 'fs-extra';
3
3
  import { loggerFactory } from '../../server/logger.js';
4
4
  import { UserController } from './user.controller.js';
5
5
  import express from 'express';
6
6
  import { DataBaseProvider } from '../../db/DataBaseProvider.js';
7
+ import { FileFactory } from '../file/file.service.js';
8
+ import { s4 } from '../../client/components/core/CommonJs.js';
7
9
 
8
10
  const logger = loggerFactory(import.meta);
9
11
 
10
12
  const UserRouter = (options) => {
11
13
  const router = express.Router();
14
+ const authMiddleware = options.authMiddleware;
12
15
 
13
16
  (async () => {
14
- const models = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models;
15
- if (models.User) {
16
- try {
17
- const adminUser = await models.User.findOne({ role: 'admin' });
17
+ // admin user seed
18
+ try {
19
+ const models = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models;
20
+ let adminUser;
21
+ if (models.User) {
22
+ adminUser = await models.User.findOne({ role: 'admin' });
18
23
  if (!adminUser) {
19
24
  const defaultPassword = process.env.DEFAULT_ADMIN_PASSWORD || 'changethis';
20
- const hashedPassword = hashPassword(defaultPassword);
25
+ const hashedPassword = await hashPassword(defaultPassword);
21
26
 
22
27
  const result = await models.User.create({
23
28
  username: 'admin',
@@ -29,10 +34,13 @@ const UserRouter = (options) => {
29
34
  });
30
35
  logger.warn('Default admin user created. Please change the default password immediately!', result._doc);
31
36
  }
32
- } catch (error) {
33
- logger.error('Error checking/creating admin user:', error);
34
37
  }
38
+ } catch (error) {
39
+ logger.error('Error checking/creating admin user');
40
+ console.log(error);
35
41
  }
42
+
43
+ // default user avatar seed
36
44
  options.png = {
37
45
  buffer: {
38
46
  'invalid-token': fs.readFileSync(`./src/client/public/default/assets/mailer/api-user-invalid-token.png`),
@@ -40,9 +48,33 @@ const UserRouter = (options) => {
40
48
  check: fs.readFileSync(`./src/client/public/default/assets/mailer/api-user-check.png`),
41
49
  },
42
50
  header: (res) => {
51
+ res.set('Cross-Origin-Resource-Policy', 'cross-origin');
52
+ res.set('Access-Control-Allow-Origin', '*');
53
+ res.set('Access-Control-Allow-Headers', '*');
43
54
  res.set('Content-Type', 'image/png');
44
55
  },
45
56
  };
57
+
58
+ try {
59
+ const models = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models;
60
+ const name = 'api-user-default-avatar.png';
61
+ const imageFile = await models.File.findOne({ name });
62
+ let _id;
63
+ if (imageFile) {
64
+ _id = imageFile._id;
65
+ } else {
66
+ const file = await new models.File(
67
+ FileFactory.create(fs.readFileSync(`./src/client/public/default/assets/mailer/${name}`), name),
68
+ ).save();
69
+ _id = file._id;
70
+ }
71
+ options.getDefaultProfileImageId = async () => {
72
+ return _id;
73
+ };
74
+ } catch (error) {
75
+ logger.error('Error checking/creating default profile image');
76
+ console.log(error);
77
+ }
46
78
  })();
47
79
 
48
80
  router.post(`/mailer/:id`, authMiddleware, async (req, res) => {
@@ -1,5 +1,16 @@
1
1
  import { loggerFactory } from '../../server/logger.js';
2
- import { hashPassword, verifyPassword, hashJWT, verifyJWT, validatePasswordMiddleware } from '../../server/auth.js';
2
+ import {
3
+ hashPassword,
4
+ verifyPassword,
5
+ verifyJWT,
6
+ createSessionAndUserToken,
7
+ createUserAndSession,
8
+ refreshSessionAndToken,
9
+ hashToken,
10
+ jwtSign,
11
+ getBearerToken,
12
+ validatePasswordMiddleware,
13
+ } from '../../server/auth.js';
3
14
  import { MailerProvider } from '../../mailer/MailerProvider.js';
4
15
  import { CoreWsMailerManagement } from '../../ws/core/management/core.ws.mailer.js';
5
16
  import { CoreWsEmit } from '../../ws/core/core.ws.emit.js';
@@ -9,7 +20,6 @@ import { DataBaseProvider } from '../../db/DataBaseProvider.js';
9
20
  import { FileFactory } from '../file/file.service.js';
10
21
  import { UserDto } from './user.model.js';
11
22
  import { selectDtoFactory, ValkeyAPI } from '../../server/valkey.js';
12
- import { getDefaultProfileImageId } from '../../server/client-icons.js';
13
23
 
14
24
  const logger = loggerFactory(import.meta);
15
25
 
@@ -28,8 +38,8 @@ const UserService = {
28
38
 
29
39
  if (!user) throw new Error('Email address does not exist');
30
40
 
31
- const token = hashJWT({ email: req.body.email }, '15m');
32
- const payloadToken = hashJWT({ email: req.body.email }, '15m');
41
+ const token = jwtSign({ email: req.body.email }, options, 15);
42
+ const payloadToken = jwtSign({ email: req.body.email }, options, 15);
33
43
  const id = `${options.host}${options.path}`;
34
44
  const translate = MailerProvider.instance[id].translateTemplates.recoverEmail;
35
45
  const recoverUrl = `${process.env.NODE_ENV === 'development' ? 'http://' : 'https://'}${req.body.hostname}${
@@ -66,7 +76,7 @@ const UserService = {
66
76
  if (req.path.startsWith('/mailer') && req.params.id === 'verify-email') {
67
77
  if (!validator.isEmail(req.body.email)) throw { message: 'invalid email' };
68
78
 
69
- const token = hashJWT({ email: req.body.email });
79
+ const token = jwtSign({ email: req.body.email }, options, 15);
70
80
  const id = `${options.host}${options.path}`;
71
81
  const user = await User.findById(req.auth.user._id);
72
82
 
@@ -129,7 +139,7 @@ const UserService = {
129
139
  if (!user.profileImageId)
130
140
  await User.findByIdAndUpdate(
131
141
  user._id,
132
- { profileImageId: await getDefaultProfileImageId(File) },
142
+ { profileImageId: await options.getDefaultProfileImageId(File) },
133
143
  {
134
144
  runValidators: true,
135
145
  },
@@ -146,8 +156,13 @@ const UserService = {
146
156
  runValidators: true,
147
157
  },
148
158
  );
159
+
160
+ const { jwtid } = await createSessionAndUserToken(user, User, req, res, options);
149
161
  return {
150
- token: hashJWT({ user: UserDto.auth.payload(user) }),
162
+ token: jwtSign(
163
+ UserDto.auth.payload(user, jwtid, req.ip, req.headers['user-agent'], options.host, options.path),
164
+ options,
165
+ ),
151
166
  user,
152
167
  };
153
168
  } else throw new Error(accountLocketMessage());
@@ -194,33 +209,24 @@ const UserService = {
194
209
  } catch (error) {
195
210
  logger.error(error, { params: req.params, body: req.body });
196
211
  }
197
- throw new Error(`invalid email or password, remaining attempts: ${5 - user.failedLoginAttempts}`);
212
+ throw new Error(`Invalid credentials. Remaining attempts: ${5 - user.failedLoginAttempts}`);
198
213
  }
199
- } else throw new Error('invalid email or password');
214
+ } else throw new Error('Invalid credentials');
200
215
 
201
216
  case 'guest': {
202
217
  const user = await ValkeyAPI.valkeyObjectFactory(options, 'user');
203
218
  await ValkeyAPI.setValkeyObject(options, user.email, user);
204
219
  return {
205
- token: hashJWT({ user: UserDto.auth.payload(user) }),
220
+ token: jwtSign(
221
+ UserDto.auth.payload(user, null, req.ip, req.headers['user-agent'], options.host, options.path),
222
+ options,
223
+ ),
206
224
  user: selectDtoFactory(user, UserDto.select.get()),
207
225
  };
208
226
  }
209
227
 
210
228
  default: {
211
- const validatePassword = validatePasswordMiddleware(req.body.password);
212
- if (validatePassword.status === 'error') throw new Error(validatePassword.message);
213
- req.body.password = await hashPassword(req.body.password);
214
- req.body.role = req.body.role === 'guest' ? 'guest' : 'user';
215
- req.body.profileImageId = await getDefaultProfileImageId(File);
216
- const { _id } = await new User(req.body).save();
217
- if (_id) {
218
- const user = await User.findOne({ _id }).select(UserDto.select.get());
219
- return {
220
- token: hashJWT({ user: UserDto.auth.payload(user) }),
221
- user,
222
- };
223
- } else throw new Error('failed to create user');
229
+ return await createUserAndSession(req, res, User, File, options);
224
230
  }
225
231
  }
226
232
  },
@@ -240,7 +246,7 @@ const UserService = {
240
246
  if (req.path.startsWith('/recover')) {
241
247
  let payload;
242
248
  try {
243
- payload = verifyJWT(req.params.id);
249
+ payload = verifyJWT(req.params.id, options);
244
250
  } catch (error) {
245
251
  logger.error(error, { 'req.params.id': req.params.id });
246
252
  options.png.header(res);
@@ -255,7 +261,7 @@ const UserService = {
255
261
  _id,
256
262
  { recoverTimeOut: new Date(+new Date() + 1000 * 60 * 15) },
257
263
  { runValidators: true },
258
- ); // 15m
264
+ );
259
265
  options.png.header(res);
260
266
  return options.png.buffer['recover'];
261
267
  } else {
@@ -267,7 +273,7 @@ const UserService = {
267
273
  if (req.path.startsWith('/mailer')) {
268
274
  let payload;
269
275
  try {
270
- payload = verifyJWT(req.params.id);
276
+ payload = verifyJWT(req.params.id, options);
271
277
  } catch (error) {
272
278
  logger.error(error, { 'req.params.id': req.params.id });
273
279
  options.png.header(res);
@@ -295,8 +301,24 @@ const UserService = {
295
301
  }
296
302
 
297
303
  switch (req.params.id) {
298
- case 'all':
299
- return await User.find().select(UserDto.select.getAll());
304
+ case 'all': {
305
+ if (req.auth.user.role === 'admin') {
306
+ const page = parseInt(req.query.page) || 1;
307
+ const limit = parseInt(req.query.limit) || 10;
308
+ const skip = (page - 1) * limit;
309
+
310
+ const data = await User.find().select(UserDto.select.get()).skip(skip).limit(limit);
311
+ const total = await User.countDocuments();
312
+
313
+ return {
314
+ data,
315
+ page,
316
+ limit,
317
+ total,
318
+ totalPages: Math.ceil(total / limit),
319
+ };
320
+ } else throw new Error(`Invalid token user id`);
321
+ }
300
322
 
301
323
  case 'auth': {
302
324
  let user;
@@ -308,22 +330,33 @@ const UserService = {
308
330
  _id: req.auth.user._id,
309
331
  });
310
332
 
333
+ if (!user) throw new Error('user not found');
334
+
311
335
  const file = await File.findOne({ _id: user.profileImageId });
312
336
 
313
337
  if (!file && !(await ValkeyAPI.getValkeyObject(options, req.auth.user.email))) {
314
338
  await User.findByIdAndUpdate(
315
339
  user._id,
316
- { profileImageId: await getDefaultProfileImageId(File) },
340
+ { profileImageId: await options.getDefaultProfileImageId(File) },
317
341
  {
318
342
  runValidators: true,
319
343
  },
320
344
  );
321
345
  }
322
- return (await ValkeyAPI.getValkeyObject(options, req.auth.user.email))
323
- ? selectDtoFactory(await ValkeyAPI.getValkeyObject(options, req.auth.user.email), UserDto.select.get())
324
- : await User.findOne({
325
- _id: req.auth.user._id,
326
- }).select(UserDto.select.get());
346
+
347
+ const guestUser = await ValkeyAPI.getValkeyObject(options, req.auth.user.email);
348
+ if (guestUser)
349
+ return {
350
+ user: selectDtoFactory(guestUser, UserDto.select.get()),
351
+ token: getBearerToken(req),
352
+ };
353
+
354
+ return {
355
+ token: await refreshSessionAndToken(req, res, User, options),
356
+ user: await User.findOne({
357
+ _id: req.auth.user._id,
358
+ }).select(UserDto.select.get()),
359
+ };
327
360
  }
328
361
 
329
362
  default: {
@@ -347,6 +380,20 @@ const UserService = {
347
380
  delete: async (req, res, options) => {
348
381
  /** @type {import('./user.model.js').UserModel} */
349
382
  const User = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.User;
383
+
384
+ if (req.params.id === 'logout') {
385
+ const refreshToken = req.cookies?.refreshToken;
386
+ if (refreshToken) {
387
+ const hashedToken = hashToken(refreshToken);
388
+ await User.updateOne(
389
+ { 'activeSessions.tokenHash': hashedToken },
390
+ { $pull: { activeSessions: { tokenHash: hashedToken } } },
391
+ );
392
+ }
393
+ res.clearCookie('refreshToken');
394
+ return { message: 'Logged out successfully' };
395
+ }
396
+
350
397
  switch (req.params.id) {
351
398
  default: {
352
399
  const user = await User.findOne({
@@ -402,10 +449,14 @@ const UserService = {
402
449
  }
403
450
 
404
451
  if (req.path.startsWith('/recover')) {
405
- const payload = verifyJWT(req.params.id);
452
+ const payload = verifyJWT(req.params.id, options);
406
453
  const user = await User.findOne({
407
454
  email: payload.email,
408
455
  });
456
+ if (process.env.NODE_ENV === 'development') {
457
+ logger.warn('Only production check image token request on mailer GET /user/recover, set fallback timeout');
458
+ user.recoverTimeOut = new Date(+new Date() + 1000 * 60 * 15);
459
+ }
409
460
  if (user && new Date().getTime() < new Date(user.recoverTimeOut).getTime()) {
410
461
  const validatePassword = validatePasswordMiddleware(req.body.password);
411
462
  if (validatePassword.status === 'error') throw new Error(validatePassword.message);
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Baremetal module for managing the generation and deployment of cloud-init configuration files
3
+ * and associated scripts for baremetal provisioning.
4
+ * @module src/cli/baremetal.js
5
+ * @namespace UnderpostBaremetal
6
+ */
7
+
1
8
  import { getNpmRootPath, getUnderpostRootPath } from '../server/conf.js';
2
9
  import { openTerminal, pbcopy, shellExec } from '../server/process.js';
3
10
  import dotenv from 'dotenv';
@@ -40,6 +47,7 @@ class UnderpostBaremetal {
40
47
  * @param {boolean} [options.nfsUnmount=false] - Flag to unmount the NFS root filesystem.
41
48
  * @param {boolean} [options.nfsSh=false] - Flag to chroot into the NFS environment for shell access.
42
49
  * @param {string} [options.logs=''] - Specifies which logs to display ('dhcp', 'cloud', 'machine', 'cloud-config').
50
+ * @memberof UnderpostBaremetal
43
51
  * @returns {void}
44
52
  */
45
53
  async callback(
@@ -576,6 +584,7 @@ menuentry '${menuentryStr}' {
576
584
  * @param {object} params.maas - MAAS configuration details.
577
585
  * @param {string} params.networkInterfaceName - The name of the network interface.
578
586
  * @returns {Promise<void>} A promise that resolves when commissioning is initiated or after a delay.
587
+ * @memberof UnderpostBaremetal
579
588
  */
580
589
  async commissionMonitor({ macAddress, nfsHostPath, underpostRoot, hostname, maas, networkInterfaceName }) {
581
590
  {
@@ -725,6 +734,7 @@ menuentry '${menuentryStr}' {
725
734
  * This is necessary for cross-architecture execution within a chroot environment.
726
735
  * @param {object} params - The parameters for the function.
727
736
  * @param {string} params.nfsHostPath - The path to the NFS root filesystem on the host.
737
+ * @memberof UnderpostBaremetal
728
738
  * @returns {void}
729
739
  */
730
740
  mountBinfmtMisc({ nfsHostPath }) {
@@ -747,6 +757,7 @@ menuentry '${menuentryStr}' {
747
757
  * @description Deletes all specified machines from MAAS.
748
758
  * @param {object} params - The parameters for the function.
749
759
  * @param {Array<object>} params.machines - An array of machine objects, each with a `system_id`.
760
+ * @memberof UnderpostBaremetal
750
761
  * @returns {Array<object>} An empty array after machines are removed.
751
762
  */
752
763
  removeMachines({ machines }) {
@@ -761,6 +772,7 @@ menuentry '${menuentryStr}' {
761
772
  * @description Clears all observed discoveries in MAAS and optionally forces a new scan.
762
773
  * @param {object} params - The parameters for the function.
763
774
  * @param {boolean} params.force - If true, forces a new discovery scan after clearing.
775
+ * @memberof UnderpostBaremetal
764
776
  * @returns {void}
765
777
  */
766
778
  clearDiscoveries({ force }) {
@@ -776,6 +788,7 @@ menuentry '${menuentryStr}' {
776
788
  * This is used to wait for the target machine to report its MAC address.
777
789
  * @param {object} params - The parameters for the function.
778
790
  * @param {string} params.nfsHostPath - The NFS host path where the MAC file is expected.
791
+ * @memberof UnderpostBaremetal
779
792
  * @returns {Promise<void>} A promise that resolves when the MAC file is found or after a delay.
780
793
  */
781
794
  async macMonitor({ nfsHostPath }) {
@@ -794,6 +807,7 @@ menuentry '${menuentryStr}' {
794
807
  * for cross-architecture execution within a chroot environment.
795
808
  * @param {object} params - The parameters for the function.
796
809
  * @param {string} params.nfsHostPath - The path to the NFS root filesystem on the host.
810
+ * @memberof UnderpostBaremetal
797
811
  * @param {'arm64'|'amd64'} params.debootstrapArch - The target architecture of the debootstrap environment.
798
812
  * @returns {void}
799
813
  */
@@ -825,6 +839,7 @@ menuentry '${menuentryStr}' {
825
839
  * @param {string} params.nfsHostPath - The path to the NFS root filesystem on the host.
826
840
  * @param {'arm64'|'amd64'} params.debootstrapArch - The target architecture of the debootstrap environment.
827
841
  * @param {object} params.callbackMetaData - Metadata about the callback, including runner host architecture.
842
+ * @memberof UnderpostBaremetal
828
843
  * @param {string[]} params.steps - An array of shell commands to execute.
829
844
  * @returns {void}
830
845
  */
@@ -860,6 +875,7 @@ EOF`);
860
875
  * This helps in visualizing and debugging the execution flow of provisioning steps.
861
876
  * @param {string[]} [steps=[]] - An array of shell commands.
862
877
  * @param {boolean} [yaml=true] - If true, formats the output as YAML list items.
878
+ * @memberof UnderpostBaremetal
863
879
  * @returns {string} The formatted string of commands.
864
880
  */
865
881
  stepsRender(steps = [], yaml = true) {
@@ -892,6 +908,7 @@ EOF`);
892
908
  * @param {string} params.workflowId - The identifier for the workflow configuration.
893
909
  * @param {boolean} [params.mount] - If true, attempts to mount the NFS paths.
894
910
  * @param {boolean} [params.unmount] - If true, attempts to unmount the NFS paths.
911
+ * @memberof UnderpostBaremetal
895
912
  * @returns {{isMounted: boolean}} An object indicating whether any NFS path is currently mounted.
896
913
  */
897
914
  nfsMountCallback({ hostname, workflowId, mount, unmount }) {
@@ -929,7 +946,8 @@ EOF`);
929
946
  * @method getHostArch
930
947
  * @description Determines the architecture of the host machine.
931
948
  * This is crucial for cross-compilation and selecting the correct QEMU binaries.
932
- * @returns {'amd64'|'arm64'} The host architecture.
949
+ * @memberof UnderpostBaremetal
950
+ * @returns {{alias: 'amd64'|'arm64', name: 'x86_64'|'aarch64'}} The host architecture.
933
951
  * @throws {Error} If the host architecture is unsupported.
934
952
  */
935
953
  getHostArch() {
@@ -944,12 +962,16 @@ EOF`);
944
962
  * @property {object} systemProvisioningFactory
945
963
  * @description A factory object containing functions for system provisioning based on OS type.
946
964
  * Each OS type (e.g., 'ubuntu') provides methods for base system setup, user creation,
947
- * timezone configuration, and keyboard layout settings.
965
+ * timezone configuration, and keyboard layout settings. *
966
+ * @memberof UnderpostBaremetal
967
+ * @namespace UnderpostBaremetal.systemProvisioningFactory
948
968
  */
949
969
  systemProvisioningFactory: {
950
970
  /**
951
971
  * @property {object} ubuntu
952
972
  * @description Provisioning steps for Ubuntu-based systems.
973
+ * @memberof UnderpostBaremetal.systemProvisioningFactory
974
+ * @namespace UnderpostBaremetal.systemProvisioningFactory.ubuntu
953
975
  */
954
976
  ubuntu: {
955
977
  /**
@@ -959,6 +981,7 @@ EOF`);
959
981
  * kernel modules, cloud-init, SSH server, and other core utilities.
960
982
  * @param {object} params - The parameters for the function.
961
983
  * @param {string} params.kernelLibVersion - The specific kernel library version to install.
984
+ * @memberof UnderpostBaremetal.systemProvisioningFactory.ubuntu
962
985
  * @returns {string[]} An array of shell commands.
963
986
  */
964
987
  base: ({ kernelLibVersion }) => [
@@ -991,6 +1014,7 @@ SOURCES`,
991
1014
  * @method user
992
1015
  * @description Generates shell commands for creating a root user and configuring SSH access.
993
1016
  * This is a critical security step for initial access to the provisioned system.
1017
+ * @memberof UnderpostBaremetal.systemProvisioningFactory.ubuntu
994
1018
  * @returns {string[]} An array of shell commands.
995
1019
  */
996
1020
  user: () => [
@@ -1014,6 +1038,7 @@ SOURCES`,
1014
1038
  * @param {string} params.timezone - The timezone string (e.g., 'America/New_York').
1015
1039
  * @param {string} params.chronyConfPath - The path to the Chrony configuration file.
1016
1040
  * @param {string} [alias='chrony'] - The alias for the chrony service.
1041
+ * @memberof UnderpostBaremetal.systemProvisioningFactory.ubuntu
1017
1042
  * @returns {string[]} An array of shell commands.
1018
1043
  */
1019
1044
  timezone: ({ timezone, chronyConfPath }, alias = 'chrony') => [
@@ -1081,6 +1106,7 @@ logdir /var/log/chrony
1081
1106
  * @method keyboard
1082
1107
  * @description Generates shell commands for configuring the keyboard layout.
1083
1108
  * This ensures correct input behavior on the provisioned system.
1109
+ * @memberof UnderpostBaremetal.systemProvisioningFactory.ubuntu
1084
1110
  * @returns {string[]} An array of shell commands.
1085
1111
  */
1086
1112
  keyboard: () => [
@@ -1099,6 +1125,7 @@ logdir /var/log/chrony
1099
1125
  * This is crucial for allowing baremetal machines to boot via NFS.
1100
1126
  * @param {object} params - The parameters for the function.
1101
1127
  * @param {string} params.nfsHostPath - The path to be exported by the NFS server.
1128
+ * @memberof UnderpostBaremetal
1102
1129
  * @param {string} [params.subnet='192.168.1.0/24'] - The subnet allowed to access the NFS export.
1103
1130
  * @returns {void}
1104
1131
  */
@@ -1169,6 +1196,7 @@ udp-port = 32766
1169
1196
  * @param {string} params.clientIp - The static IP address for the client device.
1170
1197
  * @param {string} params.subnet - The subnet mask for the client device.
1171
1198
  * @param {string} params.gateway - The gateway IP address for the client device.
1199
+ * @memberof UnderpostBaremetal
1172
1200
  * @returns {string} The generated boot configuration content.
1173
1201
  * @throws {Error} If an invalid workflow ID is provided.
1174
1202
  */
@@ -1229,12 +1257,14 @@ GATEWAY=${gateway}`;
1229
1257
  * @property {object} workflowsConfig
1230
1258
  * @description Configuration for different baremetal provisioning workflows.
1231
1259
  * Each workflow defines specific parameters like system provisioning type,
1232
- * kernel version, Chrony settings, debootstrap image details, and NFS mounts.
1260
+ * kernel version, Chrony settings, debootstrap image details, and NFS mounts. *
1261
+ * @memberof UnderpostBaremetal
1233
1262
  */
1234
1263
  workflowsConfig: {
1235
1264
  /**
1236
1265
  * @property {object} rpi4mb
1237
1266
  * @description Configuration for the Raspberry Pi 4 Model B workflow.
1267
+ * @memberof UnderpostBaremetal.workflowsConfig
1238
1268
  */
1239
1269
  rpi4mb: {
1240
1270
  menuentryStr: 'UNDERPOST.NET UEFI/GRUB/MAAS RPi4 commissioning (ARM64)',
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Cloud-init module for managing the generation and deployment of cloud-init configuration files
3
+ * and associated scripts for baremetal provisioning.
4
+ * @module src/cli/cloud-init.js
5
+ * @namespace UnderpostCloudInit
6
+ */
7
+
1
8
  import dotenv from 'dotenv';
2
9
  import { shellExec } from '../server/process.js';
3
10
  import fs from 'fs-extra';
@@ -15,6 +22,7 @@ const logger = loggerFactory(import.meta);
15
22
  * and associated scripts for baremetal provisioning. This class provides methods
16
23
  * to build various shell scripts and a cloud-init configuration file tailored
17
24
  * for MAAS (Metal as a Service) integration.
25
+ * @memberof UnderpostCloudInit
18
26
  */
19
27
  class UnderpostCloudInit {
20
28
  static API = {
@@ -30,6 +38,7 @@ class UnderpostCloudInit {
30
38
  * @param {object} params.callbackMetaData - Metadata about the callback, used for dynamic configuration.
31
39
  * @param {boolean} params.dev - Development mode flag.
32
40
  * @returns {void}
41
+ * @memberof UnderpostCloudInit
33
42
  */
34
43
  buildTools({ workflowId, nfsHostPath, hostname, callbackMetaData, dev }) {
35
44
  // Destructure workflow configuration for easier access.
@@ -281,6 +290,7 @@ curl -X POST \\
281
290
  * @param {object} [authCredentials={}] - Optional MAAS authentication credentials.
282
291
  * @param {string} [path='/etc/cloud/cloud.cfg.d/90_maas.cfg'] - The target path for the cloud-init configuration file.
283
292
  * @returns {string} The generated cloud-init configuration content.
293
+ * @memberof UnderpostCloudInit
284
294
  */
285
295
  configFactory(
286
296
  {
@@ -494,6 +504,7 @@ EOF_MAAS_CFG`;
494
504
  * This method parses the output of `maas apikey` to extract the consumer key,
495
505
  * consumer secret, token key, and token secret.
496
506
  * @returns {object} An object containing the MAAS authentication credentials.
507
+ * @memberof UnderpostCloudInit
497
508
  * @throws {Error} If the MAAS API key format is invalid.
498
509
  */
499
510
  authCredentialsFactory() {
@@ -410,21 +410,10 @@ EOF
410
410
  const successInstance = await UnderpostTest.API.statusMonitor('mongodb-0', 'Running', 'pods', 1000, 60 * 10);
411
411
 
412
412
  if (successInstance) {
413
- if (!options.mongoDbHost) options.mongoDbHost = 'mongodb-service';
413
+ if (!options.mongoDbHost) options.mongoDbHost = 'mongodb-0.mongodb-service';
414
414
  const mongoConfig = {
415
415
  _id: 'rs0',
416
- members: [
417
- {
418
- _id: 0,
419
- host: `${options.mongoDbHost === 'mongodb-service' ? 'mongodb-0.' : ''}${options.mongoDbHost}:27017`,
420
- priority: 1,
421
- },
422
- // {
423
- // _id: 1,
424
- // host: `${options.mongoDbHost === 'mongodb-service' ? 'mongodb-1.' : ''}${options.mongoDbHost}:27017`,
425
- // priority: 1,
426
- // },
427
- ],
416
+ members: options.mongoDbHost.split(',').map((host, index) => ({ _id: index, host: `${host}:27017` })),
428
417
  };
429
418
 
430
419
  shellExec(
@@ -683,17 +672,9 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
683
672
  }
684
673
  },
685
674
 
686
- /**
687
- * @method getResourcesCapacity
688
- * @description Retrieves and returns the allocatable CPU and memory resources
689
- * of the Kubernetes node.
690
- * @param {boolean} [isKubeadmOrK3s=false] - If true, assumes a kubeadm or k3s-managed node;
691
- * otherwise, assumes a Kind worker node.
692
- * @returns {object} An object containing CPU and memory resources with values and units.
693
- */
694
- getResourcesCapacity(isKubeadmOrK3s = false) {
675
+ getResourcesCapacity(node) {
695
676
  const resources = {};
696
- const nodeName = isKubeadmOrK3s ? os.hostname() : 'kind-worker';
677
+ const nodeName = node ?? os.hostname();
697
678
  const info = shellExec(`kubectl describe node ${nodeName} | grep -E '(Allocatable:|Capacity:)' -A 6`, {
698
679
  stdout: true,
699
680
  silent: true,
package/src/cli/cron.js CHANGED
@@ -64,7 +64,6 @@ class UnderpostCron {
64
64
  shellExec(Cmd.cron(deployId, job, name, confCronConfig.jobs[job].expression, options));
65
65
  }
66
66
  }
67
- if (fs.existsSync(`./tmp/await-deploy`)) fs.remove(`./tmp/await-deploy`);
68
67
  return;
69
68
  }
70
69
  for (const _jobId of jobList.split(',')) {