@underpostnet/cyberia 3.2.80 → 3.3.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 (162) hide show
  1. package/.env.example +50 -15
  2. package/.github/workflows/coverall.cyberia.ci.yml +87 -0
  3. package/.github/workflows/cyberia-client.cd.yml +2 -7
  4. package/.github/workflows/cyberia-server.cd.yml +2 -7
  5. package/.github/workflows/docker-image.cyberia-client.ci.yml +4 -4
  6. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +4 -4
  7. package/.github/workflows/docker-image.cyberia-server.ci.yml +4 -4
  8. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +4 -4
  9. package/.github/workflows/docker-image.engine-cyberia.ci.yml +3 -3
  10. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +3 -3
  11. package/.github/workflows/engine-cyberia.cd.yml +3 -67
  12. package/.github/workflows/ghpkg.ci.yml +7 -1
  13. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
  14. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  15. package/.github/workflows/release.cd.yml +1 -9
  16. package/CHANGELOG.md +291 -1
  17. package/CLI-HELP.md +174 -23
  18. package/Dockerfile +1 -1
  19. package/Dockerfile.dev +1 -1
  20. package/Dockerfile.test +1 -1
  21. package/bin/build.js +7 -5
  22. package/bin/cyberia.js +272 -98
  23. package/bin/deploy.js +19 -17
  24. package/bin/index.js +272 -98
  25. package/compose.env +50 -15
  26. package/conf.js +2 -0
  27. package/deploy/dd-cyberia/deploy.sh +42 -0
  28. package/deploy/dd-cyberia/init.sh +63 -0
  29. package/deploy/dd-cyberia/sync-deploy.sh +194 -0
  30. package/deploy/lib/logging.sh +96 -0
  31. package/deploy/pwa-microservices-template/deploy.sh +72 -0
  32. package/deploy/release/deploy.sh +62 -0
  33. package/deployment.yaml +1 -210
  34. package/docker-compose.yml +90 -85
  35. package/hardhat/package-lock.json +139 -131
  36. package/hardhat/package.json +4 -4
  37. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -1
  38. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  39. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +52 -0
  40. package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -210
  41. package/manifests/deployment/dd-cyberia-development/gateway.yaml +80 -0
  42. package/manifests/deployment/dd-cyberia-development/httproute.yaml +504 -0
  43. package/manifests/deployment/dd-cyberia-development/proxy.yaml +12 -12
  44. package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +0 -82
  45. package/manifests/deployment/dd-cyberia-development/traffic-service.yaml +121 -0
  46. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  47. package/manifests/deployment/playwright/deployment.yaml +1 -1
  48. package/manifests/mongodb/kustomization.yaml +4 -1
  49. package/manifests/mongodb/statefulset.yaml +4 -0
  50. package/manifests/mongodb/storage-class.yaml +9 -2
  51. package/nginx.conf +86 -16
  52. package/package.json +2 -2
  53. package/proxy.yaml +12 -12
  54. package/pv-pvc.yaml +0 -82
  55. package/scripts/audit-selinux.sh +64 -0
  56. package/scripts/coverall-test-cyberia.sh +24 -0
  57. package/scripts/coverall-test.sh +24 -0
  58. package/scripts/gpu-diag.sh +0 -0
  59. package/scripts/ip-info.sh +0 -0
  60. package/scripts/k3s-node-setup.sh +18 -15
  61. package/scripts/kubeadm-node-setup.sh +12 -23
  62. package/scripts/link-local-underpost-cli.sh +0 -0
  63. package/scripts/lxd-vm-setup.sh +0 -0
  64. package/scripts/maas-nat-firewalld.sh +0 -0
  65. package/scripts/nat-iptables.sh +12 -4
  66. package/scripts/rhel-grpc-setup.sh +0 -0
  67. package/scripts/rocky-kickstart.sh +25 -9
  68. package/scripts/test-monitor.sh +4 -3
  69. package/src/api/cyberia-action/cyberia-action.model.js +1 -0
  70. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
  71. package/src/api/cyberia-instance/cyberia-fallback-world.js +49 -15
  72. package/src/api/cyberia-instance/cyberia-instance-map.service.js +8 -12
  73. package/src/api/cyberia-instance/cyberia-instance.controller.js +3 -0
  74. package/src/api/cyberia-instance/cyberia-instance.router.js +13 -0
  75. package/src/api/cyberia-instance/cyberia-instance.service.js +43 -0
  76. package/src/api/cyberia-instance/cyberia-portal-connector.js +7 -5
  77. package/src/api/cyberia-instance/cyberia-random-source.js +80 -0
  78. package/src/api/cyberia-instance/cyberia-world-generator.js +4 -3
  79. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
  80. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +87 -1
  81. package/src/api/object-layer/object-layer.model.js +204 -3
  82. package/src/api/object-layer/object-layer.service.js +1 -14
  83. package/src/cli/baremetal.js +1 -2
  84. package/src/cli/cloud-init.js +1 -1
  85. package/src/cli/cluster.js +786 -96
  86. package/src/cli/db.js +11 -4
  87. package/src/cli/deploy.js +1698 -177
  88. package/src/cli/docker-compose.js +19 -178
  89. package/src/cli/env.js +1 -1
  90. package/src/cli/image.js +15 -7
  91. package/src/cli/index.js +245 -44
  92. package/src/cli/ipfs.js +82 -11
  93. package/src/cli/lxd.js +1 -1
  94. package/src/cli/monitor.js +2 -2
  95. package/src/cli/release.js +57 -22
  96. package/src/cli/repository.js +12 -10
  97. package/src/cli/run.js +2195 -427
  98. package/src/cli/secrets.js +969 -0
  99. package/src/cli/ssh.js +206 -105
  100. package/src/cli/system.js +26 -13
  101. package/src/cli/test.js +1 -1
  102. package/src/cli/vultr.js +583 -0
  103. package/src/cli/wireguard.js +2125 -0
  104. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +368 -0
  105. package/src/client/components/cyberia/InstanceSelectionView.js +11 -8
  106. package/src/client/components/cyberia/SharedDefaultsCyberia.js +5 -0
  107. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +33 -0
  108. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
  109. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  110. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +106 -39
  111. package/src/client/public/cyberia-docs/ARCHITECTURE.md +35 -3
  112. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +44 -7
  113. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
  114. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  115. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  116. package/src/client/services/cyberia-instance/cyberia-instance.service.js +40 -0
  117. package/src/client/services/object-layer/object-layer.management.js +4 -4
  118. package/src/client-builder/client-build.js +102 -13
  119. package/src/client-builder/ssr.js +27 -73
  120. package/src/db/mongo/MongoBootstrap.js +295 -54
  121. package/src/db/mongo/MongooseDB.js +51 -32
  122. package/src/index.js +25 -1
  123. package/src/projects/cyberia/besu-genesis-generator.js +3 -2
  124. package/src/projects/cyberia/catalog-cyberia.js +5 -0
  125. package/src/projects/cyberia/generate-saga.js +14 -23
  126. package/src/projects/cyberia/hot-reload-trigger.js +3 -3
  127. package/src/projects/cyberia/instance-data.js +63 -3
  128. package/src/projects/cyberia/object-layer.js +11 -21
  129. package/src/projects/underpost/catalog-underpost.js +4 -1
  130. package/src/runtime/cyberia-client/Dockerfile +1 -1
  131. package/src/runtime/cyberia-client/Dockerfile.dev +1 -1
  132. package/src/runtime/cyberia-server/Dockerfile +1 -1
  133. package/src/runtime/cyberia-server/Dockerfile.dev +1 -1
  134. package/src/runtime/engine-cyberia/Dockerfile +1 -1
  135. package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
  136. package/src/runtime/engine-cyberia/Dockerfile.test +1 -1
  137. package/src/runtime/engine-cyberia/compose.env +50 -15
  138. package/src/runtime/engine-cyberia/docker-compose.yml +90 -85
  139. package/src/runtime/engine-cyberia/nginx.conf +86 -16
  140. package/src/server/backup.js +1 -1
  141. package/src/server/conf.js +1216 -168
  142. package/src/server/cri.js +70 -0
  143. package/src/server/cron.js +249 -51
  144. package/src/server/dns.js +100 -6
  145. package/src/server/environment.js +98 -0
  146. package/src/server/forward-proxy.js +549 -0
  147. package/src/server/middlewares.js +56 -1
  148. package/src/server/process.js +0 -1
  149. package/src/server/selinux.js +185 -0
  150. package/src/server/systemd.js +205 -0
  151. package/src/server/underpost-compression.js +186 -0
  152. package/src/server/underpost-gateway.js +1083 -0
  153. package/src/server/underpost-ingress.js +380 -0
  154. package/test/cluster-instances.test.js +435 -0
  155. package/test/cyberia-instance-conf-defaults.test.js +1 -0
  156. package/test/deploy-node-placement.test.js +45 -0
  157. package/test/instance-traffic-plan.test.js +710 -0
  158. package/test/selinux.test.js +71 -0
  159. package/test/sops-secret-store.test.js +612 -0
  160. package/test/underpost-gateway.test.js +510 -0
  161. package/test/underpost-ingress.test.js +305 -0
  162. package/test/wireguard-edge.test.js +1177 -0
@@ -19,6 +19,7 @@ import path from 'path';
19
19
  import { keccak256 as ethersKeccak256 } from 'ethers';
20
20
  import { loggerFactory } from '../../server/logger.js';
21
21
  import { shellExec } from '../../server/process.js';
22
+ import { crictlCommandFactory } from '../../server/cri.js';
22
23
 
23
24
  const logger = loggerFactory(import.meta);
24
25
 
@@ -1394,8 +1395,8 @@ async function deployBesu(options = {}) {
1394
1395
  // ── Step 2: Pull container images if requested ─────────────────────────
1395
1396
  if (pullImage) {
1396
1397
  logger.info('Pulling Besu images via crictl...');
1397
- shellExec(`sudo crictl pull ${besuImage}`);
1398
- shellExec(`sudo crictl pull ${curlImage}`);
1398
+ shellExec(crictlCommandFactory(`pull ${besuImage}`));
1399
+ shellExec(crictlCommandFactory(`pull ${curlImage}`));
1399
1400
  }
1400
1401
 
1401
1402
  // ── Step 3: Apply all besu resources via kustomize ─────────────────────
@@ -48,6 +48,8 @@ export default {
48
48
  '/.github/workflows/docker-image.cyberia-server.dev.ci.yml',
49
49
  '/.github/workflows/cyberia-client.cd.yml',
50
50
  '/.github/workflows/cyberia-server.cd.yml',
51
+ '/.github/workflows/coverall.cyberia.ci.yml',
52
+ '/scripts/coverall-test-cyberia.sh',
51
53
  ],
52
54
  stripPaths: [
53
55
  './src/projects/cyberia',
@@ -56,8 +58,11 @@ export default {
56
58
  './src/runtime/cyberia-client',
57
59
  './src/runtime/engine-cyberia',
58
60
  './test/cyberia-instance-conf-defaults.test.js',
61
+ './test/cyberia-load.test.js',
62
+ './test/object-layer-item-id.test.js',
59
63
  './test/shape-generator.test.js',
60
64
  './src/client/public/cyberia-docs',
65
+ './scripts/coverall-test-cyberia.sh',
61
66
  'bin/cyberia.js',
62
67
  './hardhat',
63
68
  ],
@@ -18,7 +18,6 @@ import fs from 'fs-extra';
18
18
  import nodePath from 'path';
19
19
  import crypto from 'crypto';
20
20
  import { GeminiClient } from './gemini-client.js';
21
- import { computeSha256 } from './object-layer.js';
22
21
  import { loggerFactory } from '../../server/logger.js';
23
22
 
24
23
  const logger = loggerFactory(import.meta);
@@ -1632,13 +1631,13 @@ function normalizeSagaPayload(raw, { theme }) {
1632
1631
 
1633
1632
  /**
1634
1633
  * Persist normalized object-layer items into the ObjectLayer collection so they
1635
- * are editable in the viewer. Items are created with an empty (`null`) render
1636
- * and an `OFF_CHAIN` ledger; a render/ledger can be set later from
1634
+ * are editable in the viewer. New items get an empty render and an `OFF_CHAIN`
1635
+ * ledger; both can be set later from the object-layer import or from
1637
1636
  * `src/client/components/cyberia/ObjectLayerEngineViewer.js`.
1638
1637
  *
1639
- * Upserts by `data.item.id`. An existing item's render and ledger are PRESERVED
1640
- * (never clobbered back to null) only the textual stats/item fields are
1641
- * refreshed. `sha256` is recomputed over the resulting data.
1638
+ * Upserts through `ObjectLayer.upsertByItemId`, so `data.item.id` stays unique
1639
+ * and an existing item's render, ledger and CIDs are PRESERVED only the
1640
+ * textual stats/item fields are refreshed, and `sha256` is recomputed.
1642
1641
  *
1643
1642
  * @async
1644
1643
  * @param {Object} params
@@ -1647,28 +1646,20 @@ function normalizeSagaPayload(raw, { theme }) {
1647
1646
  * @returns {Promise<number>} Count of items created or updated.
1648
1647
  */
1649
1648
  async function persistObjectLayers({ objectLayers, ObjectLayer }) {
1649
+ await ObjectLayer.ensureUniqueItemIdIndex();
1650
+
1650
1651
  let count = 0;
1651
1652
  for (const ol of objectLayers) {
1652
1653
  const itemId = ol.item?.id;
1653
1654
  if (!itemId) continue;
1654
1655
 
1655
- const existing = await ObjectLayer.findOne({ 'data.item.id': itemId });
1656
-
1657
- // Preserve any render already set via the viewer; default to empty render.
1658
- const hasRender = existing?.data?.render?.cid || existing?.data?.render?.metadataCid;
1659
- const render = hasRender ? existing.data.render : { cid: null, metadataCid: null };
1660
- const ledger = existing?.data?.ledger?.type ? existing.data.ledger : { type: 'OFF_CHAIN', tokenId: '' };
1661
-
1662
- const data = { stats: ol.stats, item: ol.item, ledger, render };
1663
- const sha256 = computeSha256(data);
1664
-
1665
- if (existing) {
1666
- existing.data = data;
1667
- existing.sha256 = sha256;
1668
- await existing.save();
1669
- } else {
1670
- await ObjectLayer.create({ data, sha256, cid: null });
1671
- }
1656
+ // The saga only owns the textual/statistical fields. Ledger and render are
1657
+ // seeded on insert and never refreshed, so a re-import cannot clobber an
1658
+ // on-chain registration or a render produced by the object-layer import.
1659
+ await ObjectLayer.upsertByItemId(
1660
+ { data: { stats: ol.stats, item: ol.item } },
1661
+ { setOnInsert: { cid: null, data: { ledger: { type: 'OFF_CHAIN', tokenId: '' }, render: {} } } },
1662
+ );
1672
1663
  count++;
1673
1664
  }
1674
1665
  return count;
@@ -51,9 +51,9 @@ const serverApiKey = () => process.env.CYBERIA_SERVER_API_KEY || '';
51
51
  *
52
52
  * The URL sub-path is preserved as `basePath` ("", "/FOREST", "/TEST") and kept
53
53
  * on `restBaseUrl`: a path-based multi-instance proxy routes the REST trigger to
54
- * the right variant by that prefix (then strips it via pathRewritePolicy, so the
55
- * backend matches its own instanceCode). Using `url.origin` alone would drop the
56
- * sub-path and land every trigger on the default (root) deployment.
54
+ * the right variant, and cyberia-server consumes that same base path natively.
55
+ * Using `url.origin` alone would drop the sub-path and land every trigger on the
56
+ * default (root) deployment.
57
57
  */
58
58
  const resolveTargets = (rawUrl, { grpcPort = DEFAULT_GRPC_PORT } = {}) => {
59
59
  const trimmed = String(rawUrl || '').trim();
@@ -24,11 +24,38 @@ import {
24
24
  DefaultCyberiaActions,
25
25
  DefaultCyberiaQuests,
26
26
  } from '../../api/cyberia-server-defaults/cyberia-server-defaults.js';
27
- import { DefaultCyberiaItems } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
27
+ import {
28
+ DEFAULT_INSTANCE_CODE,
29
+ DefaultCyberiaItems,
30
+ } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
28
31
  import { generateFallbackWorld } from '../../api/cyberia-instance/cyberia-fallback-world.js';
32
+ import { getFallbackDefaultItems } from '../../api/cyberia-instance/cyberia-fallback-default-items.js';
29
33
 
30
34
  const logger = loggerFactory(import.meta);
31
35
 
36
+ /**
37
+ * Builds the runtime env for one Cyberia MMO variant. The generic instance
38
+ * loader deliberately has no knowledge of these application-specific keys;
39
+ * Cyberia derives them from the normalized variant path/code here.
40
+ * @param {object} context - Instance env build context.
41
+ * @param {object} context.instance - Expanded instance descriptor.
42
+ * @param {Object<string,string>} context.env - Parsed canonical env values.
43
+ * @returns {Object<string,string>} Materialized Cyberia env values.
44
+ */
45
+ function buildCyberiaMmoInstanceEnv({ instance = {}, env = {} }) {
46
+ const built = { ...env };
47
+ const instanceCode = instance.path === '/' ? DEFAULT_INSTANCE_CODE : instance.instanceCode;
48
+ if (instance.runtime === 'cyberia-server') {
49
+ built.INSTANCE_CODE = instanceCode;
50
+ built.CYBERIA_BASE_PATH = instance.path;
51
+ } else if (instance.runtime === 'cyberia-client') {
52
+ built.CYBERIA_INSTANCE_CODE = instanceCode;
53
+ built.CYBERIA_DEFAULT_INSTANCE = DEFAULT_INSTANCE_CODE;
54
+ built.CYBERIA_BASE_PATH = instance.path;
55
+ }
56
+ return built;
57
+ }
58
+
32
59
  /**
33
60
  * Resolves the mongoose model bag for a DB context.
34
61
  * @param {{host?: string, path?: string}|string} context - RouterOptions-style
@@ -304,6 +331,8 @@ function toInstanceMsg(doc) {
304
331
  };
305
332
  }
306
333
 
334
+ const toCraftItemMsg = (i) => ({ itemId: i.itemId || '', qty: i.qty ?? 1 });
335
+
307
336
  // CyberiaAction → CyberiaActionMessage (proto camelCase fields).
308
337
  function toActionMsg(a) {
309
338
  return {
@@ -317,6 +346,17 @@ function toActionMsg(a) {
317
346
  questCode: qd.questCode || '',
318
347
  dialogCode: qd.dialogCode || '',
319
348
  })),
349
+ shopItems: (a.shopItems || []).map((si) => ({
350
+ itemId: si.itemId || '',
351
+ priceItemId: si.priceItemId || 'coin',
352
+ priceQty: si.priceQty ?? 1,
353
+ })),
354
+ craftRecipes: (a.craftRecipes || []).map((r) => ({
355
+ outputItems: (r.outputItems || []).map(toCraftItemMsg),
356
+ ingredients: (r.ingredients || []).map(toCraftItemMsg),
357
+ craftTimeMs: r.craftTimeMs ?? 0,
358
+ })),
359
+ storageSlots: a.storageSlots ?? 0,
320
360
  };
321
361
  }
322
362
 
@@ -382,6 +422,7 @@ function toInstanceConfig(gc) {
382
422
  botAggroRange: gc.botAggroRange ?? fb.botAggroRange,
383
423
  defaultPlayerWidth: gc.defaultPlayerWidth ?? fb.defaultPlayerWidth,
384
424
  defaultPlayerHeight: gc.defaultPlayerHeight ?? fb.defaultPlayerHeight,
425
+ playerBaseSpeed: gc.playerBaseSpeed ?? fb.playerBaseSpeed,
385
426
  playerBaseLifeRegenMin: gc.playerBaseLifeRegenMin ?? fb.playerBaseLifeRegenMin,
386
427
  playerBaseLifeRegenMax: gc.playerBaseLifeRegenMax ?? fb.playerBaseLifeRegenMax,
387
428
  sumStatsLimit: gc.sumStatsLimit ?? fb.sumStatsLimit,
@@ -514,7 +555,7 @@ async function fetchFullInstance(models, requestedInstanceCode) {
514
555
  // ── Fallback: instance not found → return a multi-map procedural world ──
515
556
  if (!inst) {
516
557
  logger.info(`Instance "${instanceCode}" not found — returning fallback world.`);
517
- const world = generateFallbackWorld();
558
+ const world = generateFallbackWorld({ itemIds: getFallbackDefaultItems() });
518
559
  const fallbackConf = buildFallbackConfig();
519
560
 
520
561
  // Collect all objectLayerItemIds from the generated maps so the
@@ -560,6 +601,23 @@ async function fetchFullInstance(models, requestedInstanceCode) {
560
601
 
561
602
  const fallbackConfig = buildFallbackInstanceConfig(fallbackConf, world.instance.itemIds);
562
603
 
604
+ // Opaque version over everything mutable in this payload, mirroring the
605
+ // persisted path below. A constant here would make hot reload a silent
606
+ // no-op: WorldBuilder.ReloadWorld skips the rebuild — and therefore
607
+ // ApplyInstanceConfig — whenever the version is unchanged. The world
608
+ // geometry itself is deterministic (code defaults + seed), so the inputs
609
+ // that can actually differ are the staged default items and the
610
+ // ObjectLayer docs, which are the only things read from the DB here.
611
+ const fallbackVersionParts = ['fallback'];
612
+ for (const entry of world.instance.itemIds || []) {
613
+ fallbackVersionParts.push(`${entry.id}:${entry.defaultPlayerInventory ? 1 : 0}`);
614
+ }
615
+ for (const doc of fallbackOlDocs) fallbackVersionParts.push(String(doc.sha256 || doc._id));
616
+ const fallbackVersion = `fallback-${crypto
617
+ .createHash('sha256')
618
+ .update(fallbackVersionParts.join('|'))
619
+ .digest('hex')}`;
620
+
563
621
  return {
564
622
  instance: toInstanceMsg({ ...world.instance, _id: '', seed: instanceCode }),
565
623
  maps: world.maps.map((m) => ({
@@ -574,7 +632,7 @@ async function fetchFullInstance(models, requestedInstanceCode) {
574
632
  })),
575
633
  objectLayers: fallbackOlDocs.map(toObjectLayerMsg),
576
634
  config: fallbackConfig,
577
- version: 'fallback',
635
+ version: fallbackVersion,
578
636
  // Mission content for the procedural fallback comes straight from
579
637
  // the canonical defaults — no DB, fully self-contained.
580
638
  actions: DefaultCyberiaActions.map(toActionMsg),
@@ -693,6 +751,8 @@ async function fetchFullInstance(models, requestedInstanceCode) {
693
751
  }
694
752
 
695
753
  export {
754
+ buildCyberiaMmoInstanceEnv as buildInstanceEnv,
755
+ buildCyberiaMmoInstanceEnv,
696
756
  getInstanceModels,
697
757
  normalizeEntityDefault,
698
758
  applyInstanceDefaultPlayerInventory,
@@ -10,9 +10,7 @@ import path from 'path';
10
10
  import { PNG } from 'pngjs';
11
11
  import sharp from 'sharp';
12
12
  import { Jimp, intToRGBA, rgbaToInt } from 'jimp';
13
- import crypto from 'crypto';
14
- import stringify from 'fast-json-stable-stringify';
15
-
13
+ import { computeObjectLayerSha256 } from '../../api/object-layer/object-layer.model.js';
16
14
  import { range } from '../../client/components/core/CommonJs.js';
17
15
  import {
18
16
  generateRandomStats,
@@ -450,7 +448,7 @@ export class ObjectLayerEngine {
450
448
  * @memberof CyberiaObjectLayer
451
449
  */
452
450
  static computeSha256(data) {
453
- return crypto.createHash('sha256').update(stringify(data)).digest('hex');
451
+ return computeObjectLayerSha256(data);
454
452
  }
455
453
 
456
454
  /**
@@ -642,23 +640,15 @@ export class ObjectLayerEngine {
642
640
  }
643
641
 
644
642
  // 4. Atomic create/upsert - ObjectLayer is fully populated with all CIDs
645
- let objectLayer;
646
- const existingByItemId = await ObjectLayer.findOne({ 'data.item.id': objectLayerData.data.item.id });
647
- if (existingByItemId) {
648
- const oldRenderFramesId = existingByItemId.objectLayerRenderFramesId;
649
- logger.info(
650
- `ObjectLayer for item "${objectLayerData.data.item.id}" exists (${existingByItemId._id}), replacing atomically...`,
651
- );
652
- objectLayer = await ObjectLayer.findByIdAndUpdate(existingByItemId._id, objectLayerData, {
653
- returnDocument: 'after',
654
- }).populate('objectLayerRenderFramesId');
655
- if (oldRenderFramesId && !oldRenderFramesId.equals(objectLayerRenderFramesDoc._id)) {
656
- await ObjectLayerRenderFrames.findByIdAndDelete(oldRenderFramesId);
657
- }
658
- } else {
659
- objectLayer = await (await ObjectLayer.create(objectLayerData)).populate('objectLayerRenderFramesId');
660
- logger.info(`ObjectLayer created successfully with id: ${objectLayer._id}`);
643
+ const existingByItemId = await ObjectLayer.findByItemId(objectLayerData.data.item.id);
644
+ const oldRenderFramesId = existingByItemId?.objectLayerRenderFramesId;
645
+
646
+ const objectLayer = await (await ObjectLayer.upsertByItemId(objectLayerData)).populate('objectLayerRenderFramesId');
647
+
648
+ if (oldRenderFramesId && !oldRenderFramesId.equals(objectLayerRenderFramesDoc._id)) {
649
+ await ObjectLayerRenderFrames.findByIdAndDelete(oldRenderFramesId);
661
650
  }
651
+ logger.info(`ObjectLayer for item "${objectLayerData.data.item.id}" persisted with id: ${objectLayer._id}`);
662
652
 
663
653
  return { objectLayer, objectLayerRenderFramesDoc };
664
654
  }
@@ -848,7 +838,7 @@ export class ObjectLayerEngine {
848
838
  * @memberof CyberiaObjectLayer
849
839
  */
850
840
  static async resolveCanonicalCid({ itemId, ObjectLayer, ipfsClient = null, options }) {
851
- const objectLayer = await ObjectLayer.findOne({ 'data.item.id': itemId });
841
+ const objectLayer = await ObjectLayer.findByItemId(itemId);
852
842
  if (!objectLayer) {
853
843
  throw new Error(`ObjectLayer "${itemId}" not found in database`);
854
844
  }
@@ -18,7 +18,10 @@ const TEMPLATE_RESTORE_PATHS = [
18
18
  `./.github/workflows/gitlab.ci.yml`,
19
19
  `./.github/workflows/publish.ci.yml`,
20
20
  `./.github/workflows/release.cd.yml`,
21
- `./src/client/services/user/guest.service.js`,
21
+ `./deploy/lib`,
22
+ './deploy/release',
23
+ './deploy/pwa-microservices-template',
24
+ './src/client/services/user/guest.service.js',
22
25
  './src/api/user/guest.service.js',
23
26
  './src/ws/IoInterface.js',
24
27
  './src/ws/IoServer.js',
@@ -14,7 +14,7 @@ RUN make -f Web.mk clean all BUILD_MODE=${BUILD_MODE} OUTPUT_DIR=bin/
14
14
 
15
15
  # --- Runtime Image ---
16
16
  FROM rockylinux/rockylinux:9 AS runtime
17
- ARG UNDERPOST_VERSION=3.2.80
17
+ ARG UNDERPOST_VERSION=3.3.0
18
18
  ARG NODE_VERSION=24.15.0
19
19
 
20
20
  RUN set -eux; \
@@ -43,7 +43,7 @@ RUN make -f Web.mk all BUILD_MODE=${BUILD_MODE} OUTPUT_DIR=bin/
43
43
  # --- Runtime Image ---
44
44
  FROM rockylinux/rockylinux:9 AS runtime
45
45
 
46
- ARG UNDERPOST_VERSION=3.2.80
46
+ ARG UNDERPOST_VERSION=3.3.0
47
47
  ARG NODE_VERSION=24.15.0
48
48
  RUN set -eux; \
49
49
  dnf -y update; \
@@ -11,7 +11,7 @@ RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o server ./cmd/cyberia-se
11
11
 
12
12
  # --- Runtime Image ---
13
13
  FROM rockylinux/rockylinux:9 AS runtime
14
- ARG UNDERPOST_VERSION=3.2.80
14
+ ARG UNDERPOST_VERSION=3.3.0
15
15
  ARG NODE_VERSION=24.15.0
16
16
 
17
17
  RUN set -eux; \
@@ -25,7 +25,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
25
25
  # --- Runtime Image ---
26
26
  FROM rockylinux/rockylinux:9 AS runtime
27
27
 
28
- ARG UNDERPOST_VERSION=3.2.80
28
+ ARG UNDERPOST_VERSION=3.3.0
29
29
  ARG NODE_VERSION=24.15.0
30
30
  RUN set -eux; \
31
31
  dnf -y update; \
@@ -6,7 +6,7 @@
6
6
  # Stage 1 — builder: clone the private deploy, build it, then scrub secrets.
7
7
  # ---------------------------------------------------------------------------
8
8
  FROM rockylinux/rockylinux:9 AS builder
9
- ARG UNDERPOST_VERSION=3.2.80
9
+ ARG UNDERPOST_VERSION=3.3.0
10
10
  # Pin Node to an exact patch: dnf's nodejs:24 module lags (24.14.1) while
11
11
  # underpost's dependencies require >=24.15.0, so install the official binary.
12
12
  ARG NODE_VERSION=24.15.0
@@ -6,7 +6,7 @@
6
6
  # Stage 1 — builder: clone the private deploy, build it, then scrub secrets.
7
7
  # ---------------------------------------------------------------------------
8
8
  FROM rockylinux/rockylinux:9 AS builder
9
- ARG UNDERPOST_VERSION=3.2.80
9
+ ARG UNDERPOST_VERSION=3.3.0
10
10
  # Pin Node to an exact patch: dnf's nodejs:24 module lags (24.14.1) while
11
11
  # underpost's dependencies require >=24.15.0, so install the official binary.
12
12
  ARG NODE_VERSION=24.15.0
@@ -6,7 +6,7 @@
6
6
  # Stage 1 — builder: clone the private deploy, build it, then scrub secrets.
7
7
  # ---------------------------------------------------------------------------
8
8
  FROM rockylinux/rockylinux:9 AS builder
9
- ARG UNDERPOST_VERSION=3.2.80
9
+ ARG UNDERPOST_VERSION=3.3.0
10
10
  # Pin Node to an exact patch: dnf's nodejs:24 module lags (24.14.1) while
11
11
  # underpost's dependencies require >=24.15.0, so install the official binary.
12
12
  ARG NODE_VERSION=24.15.0
@@ -40,29 +40,27 @@ VALKEY_HOST=valkey-service
40
40
  VALKEY_PORT=6379
41
41
 
42
42
  # --- Cyberia instance wiring (compose service DNS) ---------------------------
43
- # cyberia-server-* -> engine-cyberia authority endpoints. INTERNAL only: these
44
- # use the docker service DNS (engine-cyberia) and must never leak to browser
45
- # clients. Per-instance INSTANCE_CODE is set on each generated server service
46
- # from conf.instances.json (multiInstance) — not here.
47
- ENGINE_API_BASE_URL=http://engine-cyberia:4005
48
- ENGINE_GRPC_ADDRESS=engine-cyberia:50051
43
+ # cyberia-server-* -> stable engine gateway for HTTP and the raw engine runtime
44
+ # for gRPC. The public engine-cyberia name belongs to nginx, avoiding ambiguous
45
+ # Docker DNS while keeping one stable HTTP endpoint for every module.
46
+ ENGINE_API_BASE_URL=http://engine-cyberia
47
+ ENGINE_GRPC_ADDRESS=engine-cyberia-runtime:50051
49
48
 
50
49
  # Client-visible Content Authority origin, forwarded to browser/Raylib clients
51
50
  # for every engine-cyberia content/asset/metadata request. Host-reachable
52
- # (docker publishes engine-cyberia :4005). Distinct from ENGINE_API_BASE_URL:
51
+ # (Docker publishes the raw engine runtime on host :4005). Unlike the internal
52
+ # ENGINE_API_BASE_URL, this value is consumed outside the Compose network:
53
53
  # the two responsibilities must never overwrite each other.
54
54
  ENGINE_PUBLIC_URL=http://localhost:4005
55
55
  ENGINE_GRPC_RELOAD_INTERVAL_SEC=300
56
56
 
57
57
  # --- Browser-facing client runtime config (injected by docker-driver.py) -----
58
58
  # The single WASM bundle resolves its instance from the URL sub-path and builds
59
- # its endpoints from these origins. They point at the nginx proxy on localhost:80
60
- # (the single gateway that path-routes WS -> server, /api + /assets -> engine),
61
- # NOT at the raw client/engine container ports — a WS to the client port (8082)
62
- # has no server behind it. So this must be the proxy origin, not :8082 / :4005.
63
- # http://localhost/ -> default instance, ws -> ws://localhost/ws
64
- # http://localhost/FOREST -> FOREST instance, ws -> ws://localhost/FOREST/ws
65
- CYBERIA_WS_ORIGIN=ws://localhost
59
+ # its endpoints from these origins. HTTP client/API traffic uses nginx on :80;
60
+ # WebSocket traffic uses the dedicated multi-path server gateway on :8081.
61
+ # http://localhost/ -> default, ws://localhost:8081/ws
62
+ # http://localhost/FOREST -> FOREST, ws://localhost:8081/FOREST/ws
63
+ CYBERIA_WS_ORIGIN=ws://localhost:8081
66
64
  CYBERIA_ENGINE_API_ORIGIN=http://localhost
67
65
  CYBERIA_DEFAULT_INSTANCE=amethyst-strata-expansion
68
66
 
@@ -72,6 +70,22 @@ CYBERIA_DEFAULT_INSTANCE=amethyst-strata-expansion
72
70
  CYBERIA_SERVER_APP_ENV=development
73
71
  CYBERIA_SERVER_LOG_LEVEL=
74
72
 
73
+ # --- cyberia-server WebSocket admission and rate limits ----------------------
74
+ # Unset keeps the built-in default. An explicit 0 turns that one limit off.
75
+ # The startup log prints the effective profile ("ws limits: ...").
76
+ #
77
+ # Set CYBERIA_DISABLE_CONNECTION_LIMITS=1 to turn every limit off. The load test
78
+ # (test/cyberia-load.test.js) drives every simulated client from one address, so
79
+ # it needs this. NEVER enable it on a public deploy.
80
+ CYBERIA_DISABLE_CONNECTION_LIMITS=
81
+ CYBERIA_MAX_CONNECTIONS=
82
+ CYBERIA_MAX_CONNECTIONS_PER_IP=
83
+ CYBERIA_CONNECT_RATE_PER_IP=
84
+ CYBERIA_CONNECT_BURST_PER_IP=
85
+ CYBERIA_MESSAGE_RATE=
86
+ CYBERIA_MESSAGE_BURST=
87
+ CYBERIA_MAX_STRIKES=
88
+
75
89
  # --- engine-cyberia runtime env (loaded into the container via env_file) -----
76
90
  # The dd-cyberia engine (engine-private/conf/dd-cyberia/conf.server.json) needs
77
91
  # these to boot: DB auth + per-site DB names, the base PORT (the cyberia host is
@@ -82,6 +96,7 @@ NODE_OPTIONS=--max-old-space-size=8192
82
96
  BASE_API=api
83
97
  DEFAULT_DEPLOY_ID=dd-cyberia
84
98
  DEPLOY_ID=dd-cyberia
99
+ # Configuration/DB selector from conf.server.json; this is not a Docker DNS alias.
85
100
  DEFAULT_DEPLOY_HOST=www.cyberiaonline.com
86
101
  DEFAULT_DEPLOY_PATH=/
87
102
  DISABLE_API_RATE_LIMIT=true
@@ -131,7 +146,25 @@ IPFS_CLUSTER_API_URL=http://ipfs-cluster:9094
131
146
  # in docker-compose.yml). Override for a real/shared cluster.
132
147
  # CLUSTER_SECRET=
133
148
 
134
- # --- Host-published ports ----------------------------------------------------
149
+ # --- Local access ------------------------------------------------------------
150
+ # Client gateway: http://localhost/{,FOREST,TEST}
151
+ # Client port alias: http://localhost:8082/{,FOREST,TEST}
152
+ # Server gateway: http://localhost:8081/{,FOREST,TEST}
153
+ # Engine authority: http://localhost:4005
154
+ # Docker-network gateways:
155
+ # http://cyberia-client/{,FOREST,TEST}
156
+ # http://cyberia-server/{,FOREST,TEST}
157
+ # http://engine-cyberia/
158
+ #
159
+ # The *.localhost aliases normally resolve to loopback without configuration:
160
+ # http://client.cyberia.localhost/{,FOREST,TEST}
161
+ # http://server.cyberia.localhost/{,FOREST,TEST}
162
+ # http://engine.cyberia.localhost/
163
+ # `node bin/cyberia run-workflow docker:up` maintains this host entry (the
164
+ # aliases already work between containers):
165
+ # 127.0.0.1 cyberia-client cyberia-server engine-cyberia
166
+ #
167
+ # Host-published ports:
135
168
  PROXY_HTTP_PORT=80
136
169
  IPFS_API_PORT=5001
137
170
  IPFS_GATEWAY_PORT=8080
@@ -140,5 +173,7 @@ MONGO_HOST_PORT=27017
140
173
  VALKEY_NODEPORT=32079
141
174
  ENGINE_CYBERIA_REST_PORT=4005
142
175
  ENGINE_CYBERIA_GRPC_PORT=50051
176
+ # Multi-path nginx server gateway, not a single server container publication.
143
177
  CYBERIA_SERVER_PORT=8081
178
+ # Multi-path nginx client gateway; raw client containers remain internal.
144
179
  CYBERIA_CLIENT_PORT=8082