cyberia 3.2.9 → 3.2.22

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 (184) hide show
  1. package/.github/workflows/engine-cyberia.cd.yml +7 -0
  2. package/.github/workflows/engine-cyberia.ci.yml +14 -2
  3. package/.github/workflows/ghpkg.ci.yml +1 -0
  4. package/.github/workflows/npmpkg.ci.yml +10 -5
  5. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  6. package/.github/workflows/release.cd.yml +1 -0
  7. package/.vscode/extensions.json +9 -9
  8. package/.vscode/settings.json +20 -4
  9. package/CHANGELOG.md +363 -1
  10. package/CLI-HELP.md +975 -1061
  11. package/README.md +190 -348
  12. package/bin/build.js +102 -125
  13. package/bin/build.template.js +33 -0
  14. package/bin/cyberia.js +238 -56
  15. package/bin/deploy.js +16 -3
  16. package/bin/index.js +238 -56
  17. package/bump.config.js +26 -0
  18. package/conf.js +131 -24
  19. package/deployment.yaml +76 -2
  20. package/hardhat/package-lock.json +113 -144
  21. package/hardhat/package.json +4 -3
  22. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  23. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  24. package/manifests/deployment/dd-cyberia-development/deployment.yaml +76 -2
  25. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  26. package/manifests/kind-config-dev.yaml +8 -0
  27. package/manifests/lxd/lxd-admin-profile.yaml +12 -3
  28. package/manifests/mongodb/pv-pvc.yaml +44 -8
  29. package/manifests/mongodb/statefulset.yaml +55 -68
  30. package/manifests/mongodb-4.4/headless-service.yaml +10 -0
  31. package/manifests/mongodb-4.4/kustomization.yaml +3 -1
  32. package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
  33. package/manifests/mongodb-4.4/pv-pvc.yaml +10 -14
  34. package/manifests/mongodb-4.4/statefulset.yaml +79 -0
  35. package/manifests/mongodb-4.4/storage-class.yaml +9 -0
  36. package/manifests/valkey/statefulset.yaml +1 -1
  37. package/manifests/valkey/valkey-nodeport.yaml +17 -0
  38. package/package.json +31 -19
  39. package/scripts/ipxe-setup.sh +52 -49
  40. package/scripts/k3s-node-setup.sh +81 -46
  41. package/scripts/link-local-underpost-cli.sh +6 -0
  42. package/scripts/lxd-vm-setup.sh +193 -8
  43. package/scripts/maas-nat-firewalld.sh +145 -0
  44. package/scripts/test-monitor.sh +250 -0
  45. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +38 -33
  46. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +16 -16
  47. package/src/api/core/core.router.js +19 -14
  48. package/src/api/core/core.service.js +5 -5
  49. package/src/api/crypto/crypto.router.js +18 -12
  50. package/src/api/crypto/crypto.service.js +3 -3
  51. package/src/api/cyberia-action/cyberia-action.model.js +1 -1
  52. package/src/api/cyberia-action/cyberia-action.router.js +22 -18
  53. package/src/api/cyberia-action/cyberia-action.service.js +5 -5
  54. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +74 -0
  55. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +99 -0
  56. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +98 -0
  57. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +152 -0
  58. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +25 -20
  59. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +6 -6
  60. package/src/api/cyberia-entity/cyberia-entity.router.js +22 -18
  61. package/src/api/cyberia-entity/cyberia-entity.service.js +5 -5
  62. package/src/api/cyberia-instance/cyberia-fallback-world.js +79 -4
  63. package/src/api/cyberia-instance/cyberia-instance.router.js +57 -52
  64. package/src/api/cyberia-instance/cyberia-instance.service.js +10 -10
  65. package/src/api/cyberia-instance/cyberia-world-generator.js +3 -3
  66. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +14 -48
  67. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +22 -18
  68. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +5 -5
  69. package/src/api/cyberia-map/cyberia-map.router.js +35 -30
  70. package/src/api/cyberia-map/cyberia-map.service.js +7 -7
  71. package/src/api/cyberia-quest/cyberia-quest.model.js +1 -1
  72. package/src/api/cyberia-quest/cyberia-quest.router.js +22 -18
  73. package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
  74. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +22 -18
  75. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +5 -5
  76. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +458 -0
  77. package/src/api/default/default.router.js +22 -18
  78. package/src/api/default/default.service.js +5 -5
  79. package/src/api/document/document.router.js +28 -23
  80. package/src/api/document/document.service.js +100 -23
  81. package/src/api/file/file.router.js +19 -13
  82. package/src/api/file/file.service.js +9 -7
  83. package/src/api/instance/instance.router.js +29 -24
  84. package/src/api/instance/instance.service.js +6 -6
  85. package/src/api/ipfs/ipfs.router.js +21 -16
  86. package/src/api/ipfs/ipfs.service.js +8 -8
  87. package/src/api/object-layer/object-layer.router.js +512 -507
  88. package/src/api/object-layer/object-layer.service.js +17 -14
  89. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +22 -18
  90. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +5 -5
  91. package/src/api/test/test.router.js +17 -12
  92. package/src/api/types.js +24 -0
  93. package/src/api/user/guest.service.js +5 -4
  94. package/src/api/user/user.router.js +297 -288
  95. package/src/api/user/user.service.js +100 -35
  96. package/src/cli/baremetal.js +132 -101
  97. package/src/cli/cluster.js +700 -232
  98. package/src/cli/db.js +59 -60
  99. package/src/cli/deploy.js +291 -294
  100. package/src/cli/env.js +1 -4
  101. package/src/cli/fs.js +13 -3
  102. package/src/cli/image.js +58 -4
  103. package/src/cli/index.js +127 -15
  104. package/src/cli/ipfs.js +4 -6
  105. package/src/cli/kubectl.js +4 -1
  106. package/src/cli/lxd.js +1099 -223
  107. package/src/cli/monitor.js +396 -9
  108. package/src/cli/release.js +355 -146
  109. package/src/cli/repository.js +169 -30
  110. package/src/cli/run.js +347 -117
  111. package/src/cli/secrets.js +11 -2
  112. package/src/cli/test.js +9 -3
  113. package/src/client/Default.index.js +9 -3
  114. package/src/client/components/core/Auth.js +5 -0
  115. package/src/client/components/core/ClientEvents.js +76 -0
  116. package/src/client/components/core/EventBus.js +4 -0
  117. package/src/client/components/core/Modal.js +82 -41
  118. package/src/client/components/core/PanelForm.js +14 -10
  119. package/src/client/components/core/Worker.js +162 -363
  120. package/src/client/components/cyberia/MapEngineCyberia.js +1 -1
  121. package/src/client/components/cyberia/SharedDefaultsCyberia.js +330 -0
  122. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +55 -1
  123. package/src/client/public/cyberia-docs/ARCHITECTURE.md +223 -361
  124. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +114 -327
  125. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +200 -222
  126. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +212 -185
  127. package/src/client/public/cyberia-docs/CYBERIA.md +259 -0
  128. package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +2 -2
  129. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +23 -1
  130. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  131. package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +106 -0
  132. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  133. package/src/client/services/cyberia-client-hints/cyberia-client-hints.service.js +99 -0
  134. package/src/client/ssr/views/CyberiaServerMetrics.js +982 -0
  135. package/src/client/sw/core.sw.js +174 -112
  136. package/src/db/DataBaseProvider.js +115 -15
  137. package/src/db/mariadb/MariaDB.js +2 -1
  138. package/src/db/mongo/MongoBootstrap.js +657 -0
  139. package/src/db/mongo/MongooseDB.js +130 -21
  140. package/src/grpc/cyberia/grpc-server.js +25 -57
  141. package/src/index.js +1 -1
  142. package/src/runtime/cyberia-client/Dockerfile +10 -7
  143. package/src/runtime/cyberia-client/Dockerfile.dev +67 -0
  144. package/src/runtime/cyberia-server/Dockerfile +11 -6
  145. package/src/runtime/cyberia-server/Dockerfile.dev +47 -0
  146. package/src/runtime/express/Express.js +2 -2
  147. package/src/runtime/wp/Dockerfile +3 -3
  148. package/src/runtime/wp/Wp.js +8 -5
  149. package/src/server/auth.js +2 -2
  150. package/src/server/catalog-underpost.js +61 -0
  151. package/src/server/catalog.js +77 -0
  152. package/src/server/client-build-docs.js +1 -1
  153. package/src/server/client-build.js +94 -129
  154. package/src/server/conf.js +496 -135
  155. package/src/server/ipfs-client.js +5 -3
  156. package/src/server/process.js +180 -19
  157. package/src/server/proxy.js +9 -2
  158. package/src/server/runtime-status.js +235 -0
  159. package/src/server/runtime.js +1 -1
  160. package/src/server/start.js +44 -11
  161. package/src/server/valkey.js +2 -0
  162. package/src/ws/IoInterface.js +16 -16
  163. package/src/ws/core/channels/core.ws.chat.js +11 -11
  164. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  165. package/src/ws/core/channels/core.ws.stream.js +19 -19
  166. package/src/ws/core/core.ws.connection.js +8 -8
  167. package/src/ws/core/core.ws.server.js +6 -5
  168. package/src/ws/default/channels/default.ws.main.js +10 -10
  169. package/src/ws/default/default.ws.connection.js +4 -4
  170. package/src/ws/default/default.ws.server.js +4 -3
  171. package/test/deploy-monitor.test.js +251 -0
  172. package/bin/file.js +0 -202
  173. package/bin/vs.js +0 -74
  174. package/bin/zed.js +0 -84
  175. package/manifests/deployment/dd-test-development/deployment.yaml +0 -254
  176. package/manifests/deployment/dd-test-development/proxy.yaml +0 -102
  177. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +0 -574
  178. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +0 -467
  179. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  180. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  181. package/src/client/ssr/pages/CyberiaServerMetrics.js +0 -461
  182. /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
  183. /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
  184. /package/src/client/ssr/{pages → views}/Test.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import mongoose from 'mongoose';
2
- import { loggerFactory } from '../../server/logger.js';
3
2
  import { getCapVariableName } from '../../client/components/core/CommonJs.js';
3
+ import { loggerFactory } from '../../server/logger.js';
4
4
 
5
5
  /**
6
6
  * Module for connecting to and loading models for a MongoDB database using Mongoose.
@@ -10,6 +10,32 @@ import { getCapVariableName } from '../../client/components/core/CommonJs.js';
10
10
 
11
11
  const logger = loggerFactory(import.meta);
12
12
 
13
+ const MONGODB_SERVICE_NAME = 'mongodb-service';
14
+ const MONGODB_STATEFULSET_NAME = 'mongodb';
15
+ const MONGODB_DEFAULT_AUTH_SOURCE = 'admin';
16
+ const MONGODB_DEFAULT_REPLICA_SET = 'rs0';
17
+ const MONGODB_DEFAULT_REPLICA_COUNT = 3;
18
+
19
+ /**
20
+ * Resolves MongoDB replica hosts from explicit input or StatefulSet defaults.
21
+ * @param {{hostList?: string, replicaCount?: number}} [options] - Host resolution options.
22
+ * @returns {Array<string>} Normalized host:port entries.
23
+ */
24
+ const resolveMongoReplicaHosts = ({ hostList = '', replicaCount = MONGODB_DEFAULT_REPLICA_COUNT }) => {
25
+ if (hostList) {
26
+ return hostList
27
+ .split(',')
28
+ .map((host) => host.trim())
29
+ .filter(Boolean)
30
+ .map((host) => (host.includes(':') ? host : `${host}:27017`));
31
+ }
32
+
33
+ return Array.from(
34
+ { length: replicaCount },
35
+ (_, index) => `${MONGODB_STATEFULSET_NAME}-${index}.${MONGODB_SERVICE_NAME}:27017`,
36
+ );
37
+ };
38
+
13
39
  /**
14
40
  * @class
15
41
  * @alias MongooseDBService
@@ -23,35 +49,110 @@ const logger = loggerFactory(import.meta);
23
49
  * 3. No built-in defaults — both `host` and `name` are required from the caller or environment.
24
50
  */
25
51
  class MongooseDBService {
52
+ /**
53
+ * Normalizes Mongo host inputs into plain host:port entries.
54
+ * @param {Array<string>|string} hosts - Host input as list or comma-separated string.
55
+ * @returns {Array<string>} Normalized host:port entries.
56
+ */
57
+ normalizeHosts(hosts) {
58
+ const hostEntries = Array.isArray(hosts) ? hosts : `${hosts || ''}`.split(',');
59
+
60
+ return hostEntries
61
+ .map((entry) => `${entry || ''}`.trim())
62
+ .filter(Boolean)
63
+ .map((entry) => entry.replace(/^mongodb:\/\//, '').replace(/\/.*$/, ''));
64
+ }
65
+
66
+ /**
67
+ * Normalizes connection config from object or legacy host/name signature.
68
+ * @param {object|string} configOrHost - Connection config object or host string.
69
+ * @param {string} [name] - Legacy DB name when using host string input.
70
+ * @returns {{authSource: string, dbName: string, directConnection: boolean, hosts: Array<string>, password: string, replicaSet: string, user: string}} Normalized config.
71
+ */
72
+ normalizeConfig(configOrHost, name) {
73
+ const config =
74
+ typeof configOrHost === 'object' && configOrHost !== null ? { ...configOrHost } : { host: configOrHost, name };
75
+
76
+ const rawHosts = config.host || process.env.DB_HOST;
77
+ const hosts = this.normalizeHosts(rawHosts);
78
+ const dbName = config.name || process.env.DB_NAME;
79
+
80
+ if (!hosts.length || !dbName) {
81
+ const missing = [!hosts.length && 'host (db.host|DB_HOST)', !dbName && 'name (db.name|DB_NAME)']
82
+ .filter(Boolean)
83
+ .join(', ');
84
+ throw new Error(`MongooseDBService.connect: missing required parameter(s): ${missing}`);
85
+ }
86
+
87
+ const user = config.user || process.env.DB_USER || '';
88
+ const password = config.password || process.env.DB_PASSWORD || '';
89
+ const hasExplicitReplicaSet = !!(config.replicaSet || process.env.DB_REPLICA_SET);
90
+ const directConnection = hosts.length === 1 && !hasExplicitReplicaSet;
91
+ const replicaSet = directConnection
92
+ ? ''
93
+ : config.replicaSet || process.env.DB_REPLICA_SET || MONGODB_DEFAULT_REPLICA_SET;
94
+ const authSource = config.authSource || process.env.DB_AUTH_SOURCE || (user ? MONGODB_DEFAULT_AUTH_SOURCE : '');
95
+
96
+ return {
97
+ authSource,
98
+ dbName,
99
+ directConnection,
100
+ hosts,
101
+ password,
102
+ replicaSet,
103
+ user,
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Builds a MongoDB URI from normalized config options.
109
+ * @param {object|string} configOrHost - Connection config object or host string.
110
+ * @param {string} [name] - Legacy DB name when using host string input.
111
+ * @returns {string} MongoDB connection URI.
112
+ */
113
+ buildUri(configOrHost, name) {
114
+ const config = this.normalizeConfig(configOrHost, name);
115
+ const credentials =
116
+ config.user && config.password
117
+ ? `${encodeURIComponent(config.user)}:${encodeURIComponent(config.password)}@`
118
+ : '';
119
+ const query = new URLSearchParams();
120
+
121
+ if (config.directConnection) query.set('directConnection', 'true');
122
+ else if (config.replicaSet) query.set('replicaSet', config.replicaSet);
123
+ if (config.authSource) query.set('authSource', config.authSource);
124
+
125
+ return `mongodb://${credentials}${config.hosts.join(',')}/${config.dbName}${query.size ? `?${query.toString()}` : ''}`;
126
+ }
127
+
26
128
  /**
27
129
  * Establishes a Mongoose connection to the specified MongoDB instance.
28
130
  *
29
131
  * @async
30
- * @param {string} host - The MongoDB host URI (e.g., `'mongodb://localhost:27017'`).
31
- * Falls back to `process.env.DB_HOST` when not provided.
32
- * @param {string} name - The database name.
33
- * Falls back to `process.env.DB_NAME` when not provided.
132
+ * @param {object|string} configOrHost - Either a db config object or a legacy host string.
133
+ * @param {string} [configOrHost.host] - Legacy single host or comma-separated host list.
134
+ * @param {string} [configOrHost.name] - The database name.
135
+ * @param {string} [configOrHost.replicaSet] - The MongoDB replica set name.
136
+ * @param {string} [configOrHost.authSource] - The authentication database.
137
+ * @param {string} [configOrHost.user] - The MongoDB username.
138
+ * @param {string} [configOrHost.password] - The MongoDB password.
139
+ * @param {string} [name] - Legacy database name when a host string is passed.
34
140
  * @returns {Promise<mongoose.Connection>} A promise that resolves to the established Mongoose connection object.
35
141
  * @throws {Error} If neither the argument nor the corresponding environment variable supplies a value.
36
142
  */
37
- async connect(host, name) {
38
- host = host || process.env.DB_HOST;
39
- name = name || process.env.DB_NAME;
40
-
41
- if (!host || !name) {
42
- const missing = [!host && 'host (DB_HOST)', !name && 'name (DB_NAME)'].filter(Boolean).join(', ');
43
- throw new Error(`MongooseDBService.connect: missing required parameter(s): ${missing}`);
44
- }
45
-
46
- const uri = `${host}/${name}`;
47
- // logger.info('MongooseDB connect', { host, name, uri });
143
+ async connect(configOrHost, name) {
144
+ const uri = this.buildUri(configOrHost, name);
145
+ if (process.env.NODE_ENV === 'development') logger.info(`Connecting to MongoDB with URI`, uri);
48
146
  return await mongoose
49
147
  .createConnection(uri, {
148
+ autoIndex: process.env.NODE_ENV !== 'production',
149
+ heartbeatFrequencyMS: 10000,
150
+ maxPoolSize: 20,
151
+ minPoolSize: 2,
152
+ retryReads: true,
153
+ retryWrites: true,
50
154
  serverSelectionTimeoutMS: 5000,
51
- // readPreference: 'primary',
52
- // directConnection: true,
53
- // useNewUrlParser: true,
54
- // useUnifiedTopology: true,
155
+ socketTimeoutMS: 45000,
55
156
  })
56
157
  .asPromise();
57
158
  }
@@ -87,4 +188,12 @@ class MongooseDBService {
87
188
  */
88
189
  const MongooseDB = new MongooseDBService();
89
190
 
90
- export { MongooseDB, MongooseDBService as MongooseDBClass };
191
+ export {
192
+ MongooseDB,
193
+ MongooseDBService as MongooseDBClass,
194
+ MONGODB_DEFAULT_REPLICA_COUNT,
195
+ MONGODB_DEFAULT_REPLICA_SET,
196
+ MONGODB_SERVICE_NAME,
197
+ MONGODB_STATEFULSET_NAME,
198
+ resolveMongoReplicaHosts,
199
+ };
@@ -13,13 +13,15 @@ import * as protoLoader from '@grpc/proto-loader';
13
13
  import crypto from 'crypto';
14
14
  import path from 'path';
15
15
  import { fileURLToPath } from 'url';
16
- import { DataBaseProvider } from '../../db/DataBaseProvider.js';
16
+ import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
17
17
  import { loggerFactory } from '../../server/logger.js';
18
18
  import {
19
19
  CYBERIA_INSTANCE_CONF_DEFAULTS as FALLBACK_CONFIG_DEFAULTS,
20
20
  ENTITY_TYPE_DEFAULTS,
21
- STATUS_ICONS,
22
- } from '../../api/cyberia-instance-conf/cyberia-instance-conf.defaults.js';
21
+ // STATUS_ICONS deliberately not imported here — see toInstanceConfig.
22
+ // Server simulation only cares about the numeric u8 IDs (which travel on
23
+ // the AOI wire). Icon stems + border colours live in SharedDefaultsCyberia.
24
+ } from '../../api/cyberia-server-defaults/cyberia-server-defaults.js';
23
25
  import { generateFallbackWorld } from '../../api/cyberia-instance/cyberia-fallback-world.js';
24
26
 
25
27
  const logger = loggerFactory(import.meta);
@@ -27,7 +29,7 @@ const logger = loggerFactory(import.meta);
27
29
  const __filename = fileURLToPath(import.meta.url);
28
30
  const __dirname = path.dirname(__filename);
29
31
 
30
- const PROTO_PATH = path.resolve(__dirname, '../../../cyberia-server/proto/cyberia.proto');
32
+ const PROTO_PATH = path.resolve(__dirname, '../../../cyberia-server/gen/proto/cyberia.proto');
31
33
 
32
34
  const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
33
35
  keepCase: false,
@@ -44,11 +46,11 @@ const proto = grpc.loadPackageDefinition(packageDefinition).cyberia;
44
46
  // ═══════════════════════════════════════════════════════════════════
45
47
 
46
48
  function getModels(dbKey) {
47
- const bucket = DataBaseProvider.instance[dbKey];
48
- if (!bucket || !bucket.mongoose || !bucket.mongoose.models) {
49
- throw new Error(`DataBaseProvider not loaded for key "${dbKey}"`);
49
+ const bucket = DataBaseProviderService.getProvider(dbKey, 'mongoose');
50
+ if (!bucket || !bucket.models) {
51
+ throw new Error(`DataBaseProviderService not loaded for key "${dbKey}"`);
50
52
  }
51
- return bucket.mongoose.models;
53
+ return bucket.models;
52
54
  }
53
55
 
54
56
  function countSharedItemIds(source = [], target = []) {
@@ -72,7 +74,6 @@ function normalizeEntityDefault(entityDefault = {}, canonical = {}) {
72
74
  liveItemIds: [...(entityDefault.liveItemIds ?? canonical.liveItemIds ?? [])],
73
75
  deadItemIds: [...(entityDefault.deadItemIds ?? canonical.deadItemIds ?? [])],
74
76
  dropItemIds: [...(entityDefault.dropItemIds ?? canonical.dropItemIds ?? [])],
75
- colorKey: entityDefault.colorKey ?? canonical.colorKey ?? '',
76
77
  defaultObjectLayers: defaultObjectLayers.map((ol) => ({
77
78
  itemId: ol.itemId || '',
78
79
  active: !!ol.active,
@@ -96,10 +97,6 @@ function selectCanonicalEntityDefaultIndex(entityDefault, canonicalDefaults, use
96
97
  if (firstSameTypeIndex === -1) {
97
98
  firstSameTypeIndex = index;
98
99
  }
99
- if (entityDefault.colorKey && canonical.colorKey === entityDefault.colorKey) {
100
- return index;
101
- }
102
-
103
100
  const liveOverlap = countSharedItemIds(entityDefault.liveItemIds, canonical.liveItemIds);
104
101
  if (
105
102
  liveOverlap > 0 &&
@@ -264,36 +261,24 @@ function toInstanceConfig(gc) {
264
261
  const fb = FALLBACK_CONFIG_DEFAULTS;
265
262
  if (!gc) return buildFallbackConfig();
266
263
 
267
- // Per-key merge: start with canonical defaults, overlay any DB-defined colours.
268
- const dbColorMap = new Map((gc.colors || []).map((c) => [c.key, c]));
269
- const colors = fb.colors.map((c) => {
270
- const ov = dbColorMap.get(c.key);
271
- return ov ? { key: c.key, r: ov.r ?? c.r, g: ov.g ?? c.g, b: ov.b ?? c.b, a: ov.a ?? c.a } : { ...c };
272
- });
273
- // Append any DB colours whose keys are absent from the canonical defaults.
274
- for (const [key, c] of dbColorMap) {
275
- if (!colors.some((fc) => fc.key === key)) {
276
- colors.push({ key, r: c.r ?? 0, g: c.g ?? 0, b: c.b ?? 0, a: c.a ?? 255 });
277
- }
278
- }
264
+ // STRICT BOUNDARY this function produces the *simulation* config for
265
+ // cyberia-server only. Every presentation concern is excluded:
266
+ //
267
+ // - palette (colors), camera tunings, screen factors, devUi,
268
+ // interpolationMs, status-icon visuals, entityDefaults[].colorKey,
269
+ // cellSize, defaultObj* — all of these reach the client through
270
+ // /api/cyberia-client-hints, never through gRPC.
271
+ //
272
+ // The Go simulation does not need any of them to advance world state;
273
+ // the C/WASM cyberia-client owns its own render policy. See
274
+ // src/client/components/cyberia/SharedDefaultsCyberia.js.
279
275
 
280
- // Merge entity defaults while preserving duplicate builds (for example,
281
- // multiple resource or portal variants sharing the same entityType).
282
276
  const gcDefaults = gc.entityDefaults && gc.entityDefaults.length > 0 ? gc.entityDefaults : [];
283
277
  const entityDefaults = mergeEntityDefaults(gcDefaults);
284
278
 
285
279
  return {
286
- cellSize: gc.cellSize ?? fb.cellSize,
287
- fps: gc.fps ?? fb.fps,
288
- interpolationMs: gc.interpolationMs ?? fb.interpolationMs,
289
- defaultObjWidth: gc.defaultObjWidth ?? fb.defaultObjWidth,
290
- defaultObjHeight: gc.defaultObjHeight ?? fb.defaultObjHeight,
291
- cameraSmoothing: gc.cameraSmoothing ?? fb.cameraSmoothing,
292
- cameraZoom: gc.cameraZoom ?? fb.cameraZoom,
293
- defaultWidthScreenFactor: gc.defaultWidthScreenFactor ?? fb.defaultWidthScreenFactor,
294
- defaultHeightScreenFactor: gc.defaultHeightScreenFactor ?? fb.defaultHeightScreenFactor,
295
- devUi: gc.devUi ?? fb.devUi,
296
- colors,
280
+ tickRate: gc.tickRate ?? fb.tickRate,
281
+ snapshotRate: gc.snapshotRate ?? fb.snapshotRate,
297
282
  aoiRadius: gc.aoiRadius ?? fb.aoiRadius,
298
283
  portalHoldTimeMs: gc.portalHoldTimeMs ?? fb.portalHoldTimeMs,
299
284
  portalSpawnRadius: gc.portalSpawnRadius ?? fb.portalSpawnRadius,
@@ -352,23 +337,6 @@ function toInstanceConfig(gc) {
352
337
  onePerType: gc.equipmentRules?.onePerType ?? fb.equipmentRules.onePerType,
353
338
  requireSkin: gc.equipmentRules?.requireSkin ?? fb.equipmentRules.requireSkin,
354
339
  },
355
- // Status icon mapping — u8 ID → icon filename stem + border colour.
356
- // Border colours come from the frozen STATUS_ICONS constant (canonical
357
- // source of truth). DB entries may override iconId but borderColor is
358
- // always canonical — the DB schema defaults are generic grey, not the
359
- // actual per-status colours.
360
- statusIcons: STATUS_ICONS.map((canon) => {
361
- const dbEntry = (gc.statusIcons || []).find((s) => s.id === canon.id);
362
- const bc = canon.borderColor || {};
363
- return {
364
- id: canon.id,
365
- iconId: (dbEntry && dbEntry.iconId) || canon.iconId || '',
366
- borderColorR: bc.r ?? 100,
367
- borderColorG: bc.g ?? 100,
368
- borderColorB: bc.b ?? 100,
369
- borderColorA: bc.a ?? 200,
370
- };
371
- }),
372
340
  };
373
341
  }
374
342
 
@@ -598,8 +566,8 @@ class GrpcServer {
598
566
 
599
567
  /**
600
568
  * @param {Object} opts
601
- * @param {string} opts.host - DataBaseProvider host key
602
- * @param {string} opts.path - DataBaseProvider path key
569
+ * @param {string} opts.host - DataBaseProviderService host key
570
+ * @param {string} opts.path - DataBaseProviderService path key
603
571
  * @param {number} [opts.port=50051]
604
572
  */
605
573
  static async start({ host, path: dbPath, port = 50051 } = {}) {
package/src/index.js CHANGED
@@ -44,7 +44,7 @@ class Underpost {
44
44
  * @type {String}
45
45
  * @memberof Underpost
46
46
  */
47
- static version = 'v3.2.9';
47
+ static version = 'v3.2.22';
48
48
 
49
49
  /**
50
50
  * Required Node.js major version
@@ -1,9 +1,9 @@
1
1
  # BUILD_MODE: RELEASE | DEBUG
2
2
  ARG BUILD_MODE=RELEASE
3
3
 
4
- # --- Build Image
4
+ # --- Build Image ---
5
5
  FROM rockylinux/rockylinux:9 AS builder
6
- # Re-declare ARG after FROM so it is in scope for RUN
6
+
7
7
  ARG BUILD_MODE=RELEASE
8
8
 
9
9
  RUN dnf -y update && \
@@ -44,7 +44,6 @@ RUN dnf groupinstall -y "Development Tools" && \
44
44
  ENV EMSDK=/opt/emsdk
45
45
  ENV PATH="${EMSDK}:${EMSDK}/upstream/emscripten:${PATH}"
46
46
 
47
- # Pin emsdk version for reproducible builds
48
47
  ARG EMSDK_VERSION=5.0.6
49
48
  WORKDIR /opt
50
49
  RUN git clone https://github.com/emscripten-core/emsdk.git ${EMSDK}
@@ -53,19 +52,23 @@ RUN ./emsdk install ${EMSDK_VERSION} && \
53
52
  ./emsdk activate ${EMSDK_VERSION}
54
53
 
55
54
  WORKDIR /cyberia-client
56
- COPY cyberia-client/ .
57
55
 
58
- RUN make -f Web.mk all BUILD_MODE=${BUILD_MODE} OUTPUT_DIR=bin/
56
+ COPY . .
57
+
58
+ RUN make -f Web.mk clean && make -f Web.mk all BUILD_MODE=${BUILD_MODE} OUTPUT_DIR=bin/
59
59
 
60
- # --- Runtime Image
60
+ # --- Runtime Image ---
61
61
  FROM rockylinux/rockylinux:9 AS runtime
62
62
 
63
+ ARG UNDERPOST_VERSION=3.2.9
63
64
  RUN dnf -y update && \
64
65
  dnf -y install epel-release && \
65
66
  dnf -y install --allowerasing curl git python3 && \
66
67
  curl -fsSL https://rpm.nodesource.com/setup_24.x | bash - && \
67
68
  dnf install -y nodejs && \
68
- dnf clean all
69
+ npm install -g underpost@${UNDERPOST_VERSION} && \
70
+ dnf clean all && \
71
+ npm cache clean --force
69
72
 
70
73
  WORKDIR /home/dd/engine/cyberia-client
71
74
 
@@ -0,0 +1,67 @@
1
+ # cyberia-client DEV runtime image.
2
+
3
+ ARG BUILD_MODE=DEBUG
4
+
5
+ # --- Build Image ---
6
+ FROM rockylinux/rockylinux:9 AS builder
7
+ ARG BUILD_MODE=DEBUG
8
+
9
+ RUN dnf -y update && \
10
+ dnf -y install epel-release && \
11
+ dnf -y install --allowerasing \
12
+ git curl wget openssl-devel libnsl perl gnupg2 bzip2 && \
13
+ dnf clean all
14
+
15
+ RUN dnf groupinstall -y "Development Tools" && \
16
+ dnf install -y \
17
+ cmake unzip python3 python3.11 \
18
+ alsa-lib-devel mesa-libGL-devel mesa-libGLU-devel \
19
+ libX11-devel libXrandr-devel libXi-devel libXcursor-devel \
20
+ libXinerama-devel libXfixes-devel \
21
+ freeglut-devel glfw-devel libatomic.x86_64 && \
22
+ dnf clean all && \
23
+ alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 2 && \
24
+ alternatives --set python3 /usr/bin/python3.11
25
+
26
+ ENV EMSDK=/opt/emsdk
27
+ ENV PATH="${EMSDK}:${EMSDK}/upstream/emscripten:${PATH}"
28
+
29
+ ARG EMSDK_VERSION=5.0.6
30
+ WORKDIR /opt
31
+ RUN git clone https://github.com/emscripten-core/emsdk.git ${EMSDK}
32
+ WORKDIR ${EMSDK}
33
+ RUN ./emsdk install ${EMSDK_VERSION} && \
34
+ ./emsdk activate ${EMSDK_VERSION}
35
+
36
+ WORKDIR /cyberia-client
37
+
38
+ COPY . .
39
+
40
+ # Dev build: BUILD_MODE=DEBUG keeps symbols, asserts, source maps.
41
+ RUN make -f Web.mk all BUILD_MODE=${BUILD_MODE} OUTPUT_DIR=bin/
42
+
43
+ # --- Runtime Image ---
44
+ FROM rockylinux/rockylinux:9 AS runtime
45
+
46
+ ARG UNDERPOST_VERSION=3.2.9
47
+ RUN dnf -y update && \
48
+ dnf -y install epel-release && \
49
+ dnf -y install --allowerasing \
50
+ curl git python3 \
51
+ procps-ng strace lsof vim-minimal && \
52
+ curl -fsSL https://rpm.nodesource.com/setup_24.x | bash - && \
53
+ dnf install -y nodejs && \
54
+ npm install -g underpost@${UNDERPOST_VERSION} && \
55
+ dnf clean all
56
+
57
+ WORKDIR /home/dd/engine/cyberia-client
58
+
59
+ COPY --from=builder /cyberia-client/server.py ./server.py
60
+ COPY --from=builder /cyberia-client/bin ./bin/
61
+
62
+ ENV CYBERIA_PORT=8082
63
+ ENV CYBERIA_MODE=development
64
+
65
+ EXPOSE 8081 8082
66
+
67
+ CMD ["sh", "-c", "exec python3 server.py ${CYBERIA_PORT} bin ${CYBERIA_MODE}"]
@@ -1,4 +1,4 @@
1
- # --- Build Image
1
+ # --- Build Image ---
2
2
  FROM rockylinux/rockylinux:9 AS builder
3
3
 
4
4
  RUN dnf -y update && \
@@ -11,27 +11,32 @@ RUN dnf -y update && \
11
11
 
12
12
  WORKDIR /build
13
13
 
14
- # Cache dependency downloads independently of source changes
15
- COPY cyberia-server/go.mod cyberia-server/go.sum ./
14
+
15
+ COPY go.mod go.sum ./
16
16
  RUN go mod download
17
17
 
18
- COPY cyberia-server/ ./
18
+ COPY . ./
19
19
  RUN chmod +x build.sh && ./build.sh
20
20
 
21
- # --- Runtime Image
21
+ # --- Runtime Image ---
22
22
  FROM rockylinux/rockylinux:9 AS runtime
23
23
 
24
+ ARG UNDERPOST_VERSION=3.2.9
24
25
  RUN dnf -y update && \
25
26
  dnf -y install epel-release && \
26
27
  dnf -y install --allowerasing curl git && \
27
28
  curl -fsSL https://rpm.nodesource.com/setup_24.x | bash - && \
28
29
  dnf install -y nodejs && \
29
- dnf clean all
30
+ npm install -g underpost@${UNDERPOST_VERSION} && \
31
+ dnf clean all && \
32
+ npm cache clean --force
30
33
 
31
34
  WORKDIR /home/dd/engine/cyberia-server
32
35
 
33
36
  COPY --from=builder /build/server ./server
34
37
 
38
+ COPY public/ ./public/
39
+
35
40
  EXPOSE 8081
36
41
 
37
42
  ENTRYPOINT ["/home/dd/engine/cyberia-server/server"]
@@ -0,0 +1,47 @@
1
+ # cyberia-server DEV runtime image.
2
+
3
+ # --- Build Image ---
4
+ FROM rockylinux/rockylinux:9 AS builder
5
+
6
+ RUN dnf -y update && \
7
+ dnf -y install epel-release && \
8
+ dnf -y install --allowerasing \
9
+ git \
10
+ make \
11
+ golang && \
12
+ dnf clean all
13
+
14
+ WORKDIR /build
15
+
16
+ COPY go.mod go.sum ./
17
+ RUN go mod download
18
+
19
+ COPY . ./
20
+
21
+ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
22
+ go build -gcflags="all=-N -l" -o server ./cmd/cyberia-server/ && \
23
+ echo "DEV build complete: $(pwd)/server"
24
+
25
+ # --- Runtime Image ---
26
+ FROM rockylinux/rockylinux:9 AS runtime
27
+
28
+ ARG UNDERPOST_VERSION=3.2.9
29
+ RUN dnf -y update && \
30
+ dnf -y install epel-release && \
31
+ dnf -y install --allowerasing \
32
+ curl git \
33
+ procps-ng strace lsof vim-minimal && \
34
+ curl -fsSL https://rpm.nodesource.com/setup_24.x | bash - && \
35
+ dnf install -y nodejs && \
36
+ npm install -g underpost@${UNDERPOST_VERSION} && \
37
+ dnf clean all
38
+
39
+ WORKDIR /home/dd/engine/cyberia-server
40
+
41
+ COPY --from=builder /build/server ./server
42
+
43
+ COPY public/ ./public/
44
+
45
+ EXPOSE 8081
46
+
47
+ ENTRYPOINT ["/home/dd/engine/cyberia-server/server"]
@@ -15,7 +15,7 @@ import { createServer } from 'http';
15
15
  import { loggerFactory, loggerMiddleware } from '../../server/logger.js';
16
16
  import { getCapVariableName, newInstance } from '../../client/components/core/CommonJs.js';
17
17
  import { MailerProvider } from '../../mailer/MailerProvider.js';
18
- import { DataBaseProvider } from '../../db/DataBaseProvider.js';
18
+ import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
19
19
  import { createPeerServer } from '../../server/peer.js';
20
20
  import { createValkeyConnection } from '../../server/valkey.js';
21
21
  import { applySecurity, authMiddlewareFactory } from '../../server/auth.js';
@@ -192,7 +192,7 @@ class ExpressService {
192
192
  }
193
193
 
194
194
  // Database and Valkey connections
195
- if (db && apis) await DataBaseProvider.load({ apis, host, path, db });
195
+ if (db && apis) await DataBaseProviderService.load({ apis, host, path, db });
196
196
 
197
197
  if (valkey) await createValkeyConnection({ host, path }, valkey);
198
198
 
@@ -3,7 +3,7 @@ FROM rockylinux:9
3
3
  # System packages
4
4
  RUN dnf -y update && \
5
5
  dnf -y install epel-release && \
6
- dnf -y install --allowerasing \
6
+ dnf -y install --allowerasing --nobest \
7
7
  bzip2 \
8
8
  sudo \
9
9
  curl \
@@ -20,8 +20,8 @@ RUN dnf -y update && \
20
20
  perl && \
21
21
  dnf clean all
22
22
 
23
- # Download and install XAMPP (PHP 8.2)
24
- RUN curl -L -o /tmp/xampp-linux-installer.run "https://sourceforge.net/projects/xampp/files/XAMPP%20Linux/8.2.12/xampp-linux-x64-8.2.12-0-installer.run" && \
23
+ # Download and install XAMPP (PHP 8.2) with retry logic for flaky SourceForge transfers
24
+ RUN curl -L --retry 5 --retry-delay 10 --retry-max-time 180 -o /tmp/xampp-linux-installer.run "https://sourceforge.net/projects/xampp/files/XAMPP%20Linux/8.2.12/xampp-linux-x64-8.2.12-0-installer.run" && \
25
25
  chmod +x /tmp/xampp-linux-installer.run && \
26
26
  bash -c "/tmp/xampp-linux-installer.run --mode unattended" && \
27
27
  ln -sf /opt/lampp/lampp /usr/bin/lampp
@@ -56,10 +56,11 @@ class WpService {
56
56
  * to `/usr/local/bin/wp` if it is not already present.
57
57
  */
58
58
  static ensureWpCli() {
59
- const existing = shellExec(`PATH="${LAMPP_BIN}:$PATH" which wp 2>/dev/null || true`, {
59
+ const existing = shellExec(`PATH="${LAMPP_BIN}:$PATH" which wp`, {
60
60
  stdout: true,
61
61
  silent: true,
62
62
  disableLog: true,
63
+ silentOnError: true,
63
64
  });
64
65
  if (existing && existing.trim()) return;
65
66
  logger.info('WP-CLI not found — installing to /usr/local/bin/wp');
@@ -76,10 +77,11 @@ class WpService {
76
77
  */
77
78
  static ensureSendmail() {
78
79
  const sendmailPath = '/usr/sbin/sendmail';
79
- const existing = shellExec(`test -x "${sendmailPath}" && echo ok || true`, {
80
+ const existing = shellExec(`test -x "${sendmailPath}" && echo ok`, {
80
81
  stdout: true,
81
82
  silent: true,
82
83
  disableLog: true,
84
+ silentOnError: true,
83
85
  });
84
86
  if (existing && existing.trim() === 'ok') return;
85
87
  logger.info('sendmail stub missing — creating no-op at /usr/sbin/sendmail');
@@ -494,7 +496,7 @@ Thumbs.db
494
496
  * `git clone` yields a fully working site without needing a fresh install.
495
497
  *
496
498
  * Safe to call repeatedly — `git commit` is a no-op when the working tree
497
- * is clean (`|| true` prevents non-zero exit).
499
+ * is clean (`silentOnError: true` swallows the non-zero exit gracefully).
498
500
  *
499
501
  * @param {object} opts
500
502
  * @param {string} opts.siteRoot - Absolute path to the WordPress root.
@@ -514,7 +516,8 @@ Thumbs.db
514
516
 
515
517
  logger.info(`${host}: persisting site to repository`);
516
518
  shellExec(
517
- `cd "${siteRoot}" && git add -A && git commit -m "wp provision ${host} $(date -u +%Y-%m-%dT%H:%M:%SZ)" || true`,
519
+ `cd "${siteRoot}" && git add -A && git commit -m "wp provision ${host} $(date -u +%Y-%m-%dT%H:%M:%SZ)"`,
520
+ { silentOnError: true },
518
521
  );
519
522
  shellExec(`cd "${siteRoot}" && underpost push . ${githubOrg}/${repoName} -f`);
520
523
  logger.info(`${host}: initial commit pushed to ${githubOrg}/${repoName}`);
@@ -627,7 +630,7 @@ if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROT
627
630
 
628
631
  // MariaDB export is handled by the shared db.js backup flow — no duplicate dump here.
629
632
  if (fs.existsSync(path.join(siteRoot, '.git'))) {
630
- shellExec(`cd "${siteRoot}" && git add -A && git commit -m "wp backup $(date -u +%Y-%m-%dT%H:%M:%SZ)" || true`);
633
+ shellExec(`cd "${siteRoot}" && git add -A && git commit -m "wp backup $(date -u +%Y-%m-%dT%H:%M:%SZ)"`, { silentOnError: true });
631
634
  shellExec(`cd "${siteRoot}" && underpost push . ${githubOrg}/${repository.split('/').pop().split('.')[0]}`);
632
635
  logger.info(`backup: git push done for ${siteRoot}`);
633
636
  } else {
@@ -20,7 +20,7 @@ import rateLimit from 'express-rate-limit';
20
20
  import slowDown from 'express-slow-down';
21
21
  import cors from 'cors';
22
22
  import cookieParser from 'cookie-parser';
23
- import { DataBaseProvider } from '../db/DataBaseProvider.js';
23
+ import { DataBaseProviderService } from '../db/DataBaseProvider.js';
24
24
  import { isDevProxyContext } from './conf.js';
25
25
 
26
26
  const logger = loggerFactory(import.meta);
@@ -229,7 +229,7 @@ const authMiddlewareFactory = (options = { host: '', path: '' }) => {
229
229
 
230
230
  // Non-guest verify session exists
231
231
  if (payload.jwtid && payload.role !== 'guest') {
232
- const User = DataBaseProvider.instance[`${payload.host}${payload.path}`].mongoose.models.User;
232
+ const User = DataBaseProviderService.getModel('user', { host: payload.host, path: payload.path });
233
233
  const user = await User.findOne({ _id: payload._id, 'activeSessions._id': payload.jwtid }).lean();
234
234
 
235
235
  if (!user) {