@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.
- package/.env.example +50 -15
- package/.github/workflows/coverall.cyberia.ci.yml +87 -0
- package/.github/workflows/cyberia-client.cd.yml +2 -7
- package/.github/workflows/cyberia-server.cd.yml +2 -7
- package/.github/workflows/docker-image.cyberia-client.ci.yml +4 -4
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +4 -4
- package/.github/workflows/docker-image.cyberia-server.ci.yml +4 -4
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +4 -4
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +3 -3
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +3 -3
- package/.github/workflows/engine-cyberia.cd.yml +3 -67
- package/.github/workflows/ghpkg.ci.yml +7 -1
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -9
- package/CHANGELOG.md +291 -1
- package/CLI-HELP.md +174 -23
- package/Dockerfile +1 -1
- package/Dockerfile.dev +1 -1
- package/Dockerfile.test +1 -1
- package/bin/build.js +7 -5
- package/bin/cyberia.js +272 -98
- package/bin/deploy.js +19 -17
- package/bin/index.js +272 -98
- package/compose.env +50 -15
- package/conf.js +2 -0
- package/deploy/dd-cyberia/deploy.sh +42 -0
- package/deploy/dd-cyberia/init.sh +63 -0
- package/deploy/dd-cyberia/sync-deploy.sh +194 -0
- package/deploy/lib/logging.sh +96 -0
- package/deploy/pwa-microservices-template/deploy.sh +72 -0
- package/deploy/release/deploy.sh +62 -0
- package/deployment.yaml +1 -210
- package/docker-compose.yml +90 -85
- package/hardhat/package-lock.json +139 -131
- package/hardhat/package.json +4 -4
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +52 -0
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -210
- package/manifests/deployment/dd-cyberia-development/gateway.yaml +80 -0
- package/manifests/deployment/dd-cyberia-development/httproute.yaml +504 -0
- package/manifests/deployment/dd-cyberia-development/proxy.yaml +12 -12
- package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +0 -82
- package/manifests/deployment/dd-cyberia-development/traffic-service.yaml +121 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/playwright/deployment.yaml +1 -1
- package/manifests/mongodb/kustomization.yaml +4 -1
- package/manifests/mongodb/statefulset.yaml +4 -0
- package/manifests/mongodb/storage-class.yaml +9 -2
- package/nginx.conf +86 -16
- package/package.json +2 -2
- package/proxy.yaml +12 -12
- package/pv-pvc.yaml +0 -82
- package/scripts/audit-selinux.sh +64 -0
- package/scripts/coverall-test-cyberia.sh +24 -0
- package/scripts/coverall-test.sh +24 -0
- package/scripts/gpu-diag.sh +0 -0
- package/scripts/ip-info.sh +0 -0
- package/scripts/k3s-node-setup.sh +18 -15
- package/scripts/kubeadm-node-setup.sh +12 -23
- package/scripts/link-local-underpost-cli.sh +0 -0
- package/scripts/lxd-vm-setup.sh +0 -0
- package/scripts/maas-nat-firewalld.sh +0 -0
- package/scripts/nat-iptables.sh +12 -4
- package/scripts/rhel-grpc-setup.sh +0 -0
- package/scripts/rocky-kickstart.sh +25 -9
- package/scripts/test-monitor.sh +4 -3
- package/src/api/cyberia-action/cyberia-action.model.js +1 -0
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +49 -15
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +8 -12
- package/src/api/cyberia-instance/cyberia-instance.controller.js +3 -0
- package/src/api/cyberia-instance/cyberia-instance.router.js +13 -0
- package/src/api/cyberia-instance/cyberia-instance.service.js +43 -0
- package/src/api/cyberia-instance/cyberia-portal-connector.js +7 -5
- package/src/api/cyberia-instance/cyberia-random-source.js +80 -0
- package/src/api/cyberia-instance/cyberia-world-generator.js +4 -3
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +87 -1
- package/src/api/object-layer/object-layer.model.js +204 -3
- package/src/api/object-layer/object-layer.service.js +1 -14
- package/src/cli/baremetal.js +1 -2
- package/src/cli/cloud-init.js +1 -1
- package/src/cli/cluster.js +786 -96
- package/src/cli/db.js +11 -4
- package/src/cli/deploy.js +1698 -177
- package/src/cli/docker-compose.js +19 -178
- package/src/cli/env.js +1 -1
- package/src/cli/image.js +15 -7
- package/src/cli/index.js +245 -44
- package/src/cli/ipfs.js +82 -11
- package/src/cli/lxd.js +1 -1
- package/src/cli/monitor.js +2 -2
- package/src/cli/release.js +57 -22
- package/src/cli/repository.js +12 -10
- package/src/cli/run.js +2195 -427
- package/src/cli/secrets.js +969 -0
- package/src/cli/ssh.js +206 -105
- package/src/cli/system.js +26 -13
- package/src/cli/test.js +1 -1
- package/src/cli/vultr.js +583 -0
- package/src/cli/wireguard.js +2125 -0
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +368 -0
- package/src/client/components/cyberia/InstanceSelectionView.js +11 -8
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +5 -0
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +33 -0
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
- package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +106 -39
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +35 -3
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +44 -7
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +40 -0
- package/src/client/services/object-layer/object-layer.management.js +4 -4
- package/src/client-builder/client-build.js +102 -13
- package/src/client-builder/ssr.js +27 -73
- package/src/db/mongo/MongoBootstrap.js +295 -54
- package/src/db/mongo/MongooseDB.js +51 -32
- package/src/index.js +25 -1
- package/src/projects/cyberia/besu-genesis-generator.js +3 -2
- package/src/projects/cyberia/catalog-cyberia.js +5 -0
- package/src/projects/cyberia/generate-saga.js +14 -23
- package/src/projects/cyberia/hot-reload-trigger.js +3 -3
- package/src/projects/cyberia/instance-data.js +63 -3
- package/src/projects/cyberia/object-layer.js +11 -21
- package/src/projects/underpost/catalog-underpost.js +4 -1
- package/src/runtime/cyberia-client/Dockerfile +1 -1
- package/src/runtime/cyberia-client/Dockerfile.dev +1 -1
- package/src/runtime/cyberia-server/Dockerfile +1 -1
- package/src/runtime/cyberia-server/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.test +1 -1
- package/src/runtime/engine-cyberia/compose.env +50 -15
- package/src/runtime/engine-cyberia/docker-compose.yml +90 -85
- package/src/runtime/engine-cyberia/nginx.conf +86 -16
- package/src/server/backup.js +1 -1
- package/src/server/conf.js +1216 -168
- package/src/server/cri.js +70 -0
- package/src/server/cron.js +249 -51
- package/src/server/dns.js +100 -6
- package/src/server/environment.js +98 -0
- package/src/server/forward-proxy.js +549 -0
- package/src/server/middlewares.js +56 -1
- package/src/server/process.js +0 -1
- package/src/server/selinux.js +185 -0
- package/src/server/systemd.js +205 -0
- package/src/server/underpost-compression.js +186 -0
- package/src/server/underpost-gateway.js +1083 -0
- package/src/server/underpost-ingress.js +380 -0
- package/test/cluster-instances.test.js +435 -0
- package/test/cyberia-instance-conf-defaults.test.js +1 -0
- package/test/deploy-node-placement.test.js +45 -0
- package/test/instance-traffic-plan.test.js +710 -0
- package/test/selinux.test.js +71 -0
- package/test/sops-secret-store.test.js +612 -0
- package/test/underpost-gateway.test.js +510 -0
- package/test/underpost-ingress.test.js +305 -0
- package/test/wireguard-edge.test.js +1177 -0
package/bin/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import { shellExec } from '../src/server/process.js';
|
|
|
17
17
|
import { loggerFactory } from '../src/server/logger.js';
|
|
18
18
|
import { generateBesuManifests, deployBesu, removeBesu } from '../src/projects/cyberia/besu-genesis-generator.js';
|
|
19
19
|
import { DataBaseProviderService } from '../src/db/DataBaseProvider.js';
|
|
20
|
-
import { loadConfServerJson } from '../src/server/conf.js';
|
|
20
|
+
import { etcHostFactory, loadConfServerJson, normalizeInstanceTopology } from '../src/server/conf.js';
|
|
21
21
|
import {
|
|
22
22
|
ObjectLayerEngine,
|
|
23
23
|
resolveCanonicalCid,
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
} from '../src/api/cyberia-server-defaults/cyberia-server-defaults.js';
|
|
52
52
|
|
|
53
53
|
import {
|
|
54
|
+
DEFAULT_INSTANCE_CODE,
|
|
54
55
|
ITEM_TYPES as itemTypes,
|
|
55
56
|
DefaultCyberiaItems,
|
|
56
57
|
} from '../src/client/components/cyberia/SharedDefaultsCyberia.js';
|
|
@@ -95,6 +96,22 @@ async function connectDbForChain({ envPath, mongoHost }) {
|
|
|
95
96
|
/** @type {Function} */
|
|
96
97
|
const logger = loggerFactory(import.meta);
|
|
97
98
|
|
|
99
|
+
const CYBERIA_DOCKER_HOST_ALIASES = ['cyberia-client', 'cyberia-server', 'engine-cyberia'];
|
|
100
|
+
|
|
101
|
+
const installCyberiaDockerHostAliases = () => {
|
|
102
|
+
try {
|
|
103
|
+
const { changed } = etcHostFactory(CYBERIA_DOCKER_HOST_ALIASES, {
|
|
104
|
+
append: true,
|
|
105
|
+
blockId: 'dd-cyberia-docker-compose',
|
|
106
|
+
});
|
|
107
|
+
return { aliases: CYBERIA_DOCKER_HOST_ALIASES, changed };
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (error?.code === 'EACCES' || error?.code === 'EPERM')
|
|
110
|
+
throw new Error('Cannot update /etc/hosts for Cyberia Docker aliases. Re-run the workflow as root.');
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
98
115
|
try {
|
|
99
116
|
const program = new Command();
|
|
100
117
|
|
|
@@ -219,7 +236,6 @@ try {
|
|
|
219
236
|
deployId,
|
|
220
237
|
host,
|
|
221
238
|
path,
|
|
222
|
-
db,
|
|
223
239
|
});
|
|
224
240
|
|
|
225
241
|
await DataBaseProviderService.load({
|
|
@@ -240,6 +256,17 @@ try {
|
|
|
240
256
|
/** @type {import('mongoose').Model} */
|
|
241
257
|
const Ipfs = DataBaseProviderService.getModel('ipfs', { host, path });
|
|
242
258
|
|
|
259
|
+
// Idempotent repair, run only before flows that write: collapses legacy
|
|
260
|
+
// duplicates and upgrades the data.item.id index to unique so every
|
|
261
|
+
// later write has exactly one document to land on. Read-only
|
|
262
|
+
// subcommands stay side-effect free — findByItemId already resolves the
|
|
263
|
+
// same canonical document whether or not duplicates are still present.
|
|
264
|
+
if (options.import || options.importTypes || options.drop || options.generate) {
|
|
265
|
+
const { removedIds, indexUpgraded } = await ObjectLayer.ensureUniqueItemIdIndex();
|
|
266
|
+
if (removedIds.length > 0) logger.warn(`Removed ${removedIds.length} duplicate ObjectLayer document(s)`);
|
|
267
|
+
if (indexUpgraded) logger.info('Upgraded data.item.id index to unique');
|
|
268
|
+
}
|
|
269
|
+
|
|
243
270
|
if (options.drop) {
|
|
244
271
|
// Parse comma-separated item IDs for targeted drop; if none provided, drop everything
|
|
245
272
|
const dropItemIds = itemId
|
|
@@ -423,7 +450,7 @@ try {
|
|
|
423
450
|
});
|
|
424
451
|
|
|
425
452
|
// Check if an ObjectLayer with the same item.id already exists (upsert by item ID)
|
|
426
|
-
const existingOL = await ObjectLayer.
|
|
453
|
+
const existingOL = await ObjectLayer.findByItemId(currentItemId);
|
|
427
454
|
let objectLayer;
|
|
428
455
|
|
|
429
456
|
if (existingOL) {
|
|
@@ -441,7 +468,6 @@ try {
|
|
|
441
468
|
let stagingFileDoc = null;
|
|
442
469
|
let stagingAtlasDoc = null;
|
|
443
470
|
let stagingCid = '';
|
|
444
|
-
let stagingSha256 = '';
|
|
445
471
|
try {
|
|
446
472
|
const itemKey = currentItemId;
|
|
447
473
|
|
|
@@ -530,8 +556,7 @@ try {
|
|
|
530
556
|
stagingData.render.cid = importItemCid;
|
|
531
557
|
stagingData.render.metadataCid = importItemMetadataCid;
|
|
532
558
|
|
|
533
|
-
// Pin data JSON to IPFS
|
|
534
|
-
stagingSha256 = ObjectLayerEngine.computeSha256(stagingData);
|
|
559
|
+
// Pin data JSON to IPFS
|
|
535
560
|
try {
|
|
536
561
|
const ipfsDataResult = await IpfsClient.addJsonToIpfs(
|
|
537
562
|
stagingData,
|
|
@@ -569,10 +594,9 @@ try {
|
|
|
569
594
|
// Create the new RenderFrames doc (only now touches DB)
|
|
570
595
|
const newRenderFrames = await ObjectLayerRenderFrames.create(objectLayerRenderFramesData);
|
|
571
596
|
|
|
572
|
-
// Single atomic
|
|
573
|
-
await ObjectLayer.
|
|
597
|
+
// Single atomic upsert on the natural key; empty staged CIDs keep their stored value
|
|
598
|
+
objectLayer = await ObjectLayer.upsertByItemId({
|
|
574
599
|
data: stagingData,
|
|
575
|
-
sha256: stagingSha256,
|
|
576
600
|
cid: stagingCid,
|
|
577
601
|
objectLayerRenderFramesId: newRenderFrames._id,
|
|
578
602
|
atlasSpriteSheetId: stagingAtlasDoc._id,
|
|
@@ -583,14 +607,13 @@ try {
|
|
|
583
607
|
await ObjectLayerRenderFrames.findByIdAndDelete(oldRenderFramesId);
|
|
584
608
|
}
|
|
585
609
|
|
|
586
|
-
logger.info(`[cut-over] Live document ${
|
|
610
|
+
logger.info(`[cut-over] Live document ${objectLayer._id} updated atomically`);
|
|
587
611
|
} else {
|
|
588
612
|
// Rollback: only File/AtlasSpriteSheet were written, clean those up
|
|
589
613
|
if (stagingFileDoc) await File.findByIdAndDelete(stagingFileDoc._id);
|
|
590
614
|
logger.warn(`[cut-over] Staging rolled back for ${currentItemId}, live document preserved`);
|
|
615
|
+
objectLayer = await ObjectLayer.findByItemId(currentItemId);
|
|
591
616
|
}
|
|
592
|
-
|
|
593
|
-
objectLayer = await ObjectLayer.findById(existingOL._id);
|
|
594
617
|
} else {
|
|
595
618
|
// ── New item: stage everything before creating (same cut-over pattern) ──
|
|
596
619
|
logger.info(`ObjectLayer '${currentItemId}' is new, staging creation...`);
|
|
@@ -605,7 +628,6 @@ try {
|
|
|
605
628
|
let stagingFileDoc = null;
|
|
606
629
|
let stagingAtlasDoc = null;
|
|
607
630
|
let stagingCid = '';
|
|
608
|
-
let stagingSha256 = '';
|
|
609
631
|
try {
|
|
610
632
|
const { buffer, metadata } = await AtlasSpriteSheetGenerator.generateAtlas(
|
|
611
633
|
objectLayerRenderFramesData,
|
|
@@ -689,7 +711,6 @@ try {
|
|
|
689
711
|
stagingData.render.cid = importItemCid;
|
|
690
712
|
stagingData.render.metadataCid = importItemMetadataCid;
|
|
691
713
|
|
|
692
|
-
stagingSha256 = ObjectLayerEngine.computeSha256(stagingData);
|
|
693
714
|
try {
|
|
694
715
|
const ipfsDataResult = await IpfsClient.addJsonToIpfs(
|
|
695
716
|
stagingData,
|
|
@@ -722,9 +743,8 @@ try {
|
|
|
722
743
|
|
|
723
744
|
if (cutoverReady) {
|
|
724
745
|
const newRenderFrames = await ObjectLayerRenderFrames.create(objectLayerRenderFramesData);
|
|
725
|
-
objectLayer = await ObjectLayer.
|
|
746
|
+
objectLayer = await ObjectLayer.upsertByItemId({
|
|
726
747
|
data: stagingData,
|
|
727
|
-
sha256: stagingSha256,
|
|
728
748
|
cid: stagingCid,
|
|
729
749
|
objectLayerRenderFramesId: newRenderFrames._id,
|
|
730
750
|
atlasSpriteSheetId: stagingAtlasDoc._id,
|
|
@@ -826,7 +846,7 @@ try {
|
|
|
826
846
|
|
|
827
847
|
if (shouldGenerateAtlas) {
|
|
828
848
|
// Check if an ObjectLayer with the same item.id already exists (upsert by item ID)
|
|
829
|
-
const existingOL = await ObjectLayer.
|
|
849
|
+
const existingOL = await ObjectLayer.findByItemId(objectLayerId);
|
|
830
850
|
let objectLayer;
|
|
831
851
|
|
|
832
852
|
if (existingOL) {
|
|
@@ -844,7 +864,6 @@ try {
|
|
|
844
864
|
let stagingFileDoc = null;
|
|
845
865
|
let stagingAtlasDoc = null;
|
|
846
866
|
let stagingCid = '';
|
|
847
|
-
let stagingSha256 = '';
|
|
848
867
|
try {
|
|
849
868
|
const itemKey = objectLayerId;
|
|
850
869
|
|
|
@@ -932,8 +951,7 @@ try {
|
|
|
932
951
|
stagingData.render.cid = importAtlasCid;
|
|
933
952
|
stagingData.render.metadataCid = importAtlasMetadataCid;
|
|
934
953
|
|
|
935
|
-
// Pin data JSON to IPFS
|
|
936
|
-
stagingSha256 = ObjectLayerEngine.computeSha256(stagingData);
|
|
954
|
+
// Pin data JSON to IPFS
|
|
937
955
|
try {
|
|
938
956
|
const ipfsDataResult = await IpfsClient.addJsonToIpfs(
|
|
939
957
|
stagingData,
|
|
@@ -969,9 +987,8 @@ try {
|
|
|
969
987
|
const oldRenderFramesId = existingOL.objectLayerRenderFramesId;
|
|
970
988
|
const newRenderFrames = await ObjectLayerRenderFrames.create(entry.objectLayerRenderFramesData);
|
|
971
989
|
|
|
972
|
-
await ObjectLayer.
|
|
990
|
+
objectLayer = await ObjectLayer.upsertByItemId({
|
|
973
991
|
data: stagingData,
|
|
974
|
-
sha256: stagingSha256,
|
|
975
992
|
cid: stagingCid,
|
|
976
993
|
objectLayerRenderFramesId: newRenderFrames._id,
|
|
977
994
|
atlasSpriteSheetId: stagingAtlasDoc._id,
|
|
@@ -980,13 +997,12 @@ try {
|
|
|
980
997
|
if (oldRenderFramesId) {
|
|
981
998
|
await ObjectLayerRenderFrames.findByIdAndDelete(oldRenderFramesId);
|
|
982
999
|
}
|
|
983
|
-
logger.info(`[cut-over] Live document ${
|
|
1000
|
+
logger.info(`[cut-over] Live document ${objectLayer._id} updated atomically`);
|
|
984
1001
|
} else {
|
|
985
1002
|
if (stagingFileDoc) await File.findByIdAndDelete(stagingFileDoc._id);
|
|
986
1003
|
logger.warn(`[cut-over] Staging rolled back for ${objectLayerId}, live document preserved`);
|
|
1004
|
+
objectLayer = await ObjectLayer.findByItemId(objectLayerId);
|
|
987
1005
|
}
|
|
988
|
-
|
|
989
|
-
objectLayer = await ObjectLayer.findById(existingOL._id);
|
|
990
1006
|
} else {
|
|
991
1007
|
// ── New item: stage everything before creating (same cut-over pattern) ──
|
|
992
1008
|
logger.info(`ObjectLayer '${objectLayerId}' is new, staging creation...`);
|
|
@@ -1001,7 +1017,6 @@ try {
|
|
|
1001
1017
|
let stagingFileDoc = null;
|
|
1002
1018
|
let stagingAtlasDoc = null;
|
|
1003
1019
|
let stagingCid = '';
|
|
1004
|
-
let stagingSha256 = '';
|
|
1005
1020
|
try {
|
|
1006
1021
|
const { buffer, metadata } = await AtlasSpriteSheetGenerator.generateAtlas(
|
|
1007
1022
|
entry.objectLayerRenderFramesData,
|
|
@@ -1085,7 +1100,6 @@ try {
|
|
|
1085
1100
|
stagingData.render.cid = importAtlasCid;
|
|
1086
1101
|
stagingData.render.metadataCid = importAtlasMetadataCid;
|
|
1087
1102
|
|
|
1088
|
-
stagingSha256 = ObjectLayerEngine.computeSha256(stagingData);
|
|
1089
1103
|
try {
|
|
1090
1104
|
const ipfsDataResult = await IpfsClient.addJsonToIpfs(
|
|
1091
1105
|
stagingData,
|
|
@@ -1118,9 +1132,8 @@ try {
|
|
|
1118
1132
|
|
|
1119
1133
|
if (cutoverReady) {
|
|
1120
1134
|
const newRenderFrames = await ObjectLayerRenderFrames.create(entry.objectLayerRenderFramesData);
|
|
1121
|
-
objectLayer = await ObjectLayer.
|
|
1135
|
+
objectLayer = await ObjectLayer.upsertByItemId({
|
|
1122
1136
|
data: stagingData,
|
|
1123
|
-
sha256: stagingSha256,
|
|
1124
1137
|
cid: stagingCid,
|
|
1125
1138
|
objectLayerRenderFramesId: newRenderFrames._id,
|
|
1126
1139
|
atlasSpriteSheetId: stagingAtlasDoc._id,
|
|
@@ -1143,7 +1156,7 @@ try {
|
|
|
1143
1156
|
if (existingItemIds.has(objectLayerId)) continue;
|
|
1144
1157
|
|
|
1145
1158
|
// --import all: create documents without atlas generation
|
|
1146
|
-
const existingOL = await ObjectLayer.
|
|
1159
|
+
const existingOL = await ObjectLayer.findByItemId(objectLayerId);
|
|
1147
1160
|
let objectLayer;
|
|
1148
1161
|
|
|
1149
1162
|
if (existingOL) {
|
|
@@ -1156,15 +1169,13 @@ try {
|
|
|
1156
1169
|
if (!stagingData.render) stagingData.render = {};
|
|
1157
1170
|
stagingData.render.cid = '';
|
|
1158
1171
|
stagingData.render.metadataCid = '';
|
|
1159
|
-
const stagingSha256 = ObjectLayerEngine.computeSha256(stagingData);
|
|
1160
1172
|
|
|
1161
1173
|
// Atomic cut-over: create new RenderFrames, swap live doc, delete old
|
|
1162
1174
|
const newRenderFrames = await ObjectLayerRenderFrames.create(entry.objectLayerRenderFramesData);
|
|
1163
1175
|
const oldRenderFramesId = existingOL.objectLayerRenderFramesId;
|
|
1164
1176
|
|
|
1165
|
-
await ObjectLayer.
|
|
1177
|
+
objectLayer = await ObjectLayer.upsertByItemId({
|
|
1166
1178
|
data: stagingData,
|
|
1167
|
-
sha256: stagingSha256,
|
|
1168
1179
|
objectLayerRenderFramesId: newRenderFrames._id,
|
|
1169
1180
|
});
|
|
1170
1181
|
|
|
@@ -1172,20 +1183,17 @@ try {
|
|
|
1172
1183
|
await ObjectLayerRenderFrames.findByIdAndDelete(oldRenderFramesId);
|
|
1173
1184
|
}
|
|
1174
1185
|
|
|
1175
|
-
objectLayer
|
|
1176
|
-
logger.info(`[cut-over] Live document ${existingOL._id} updated atomically (atlas skipped)`);
|
|
1186
|
+
logger.info(`[cut-over] Live document ${objectLayer._id} updated atomically (atlas skipped)`);
|
|
1177
1187
|
} else {
|
|
1178
|
-
// New item
|
|
1188
|
+
// New item (no atlas for bulk import)
|
|
1179
1189
|
const stagingData = JSON.parse(JSON.stringify(entry.data));
|
|
1180
1190
|
if (!stagingData.render) stagingData.render = {};
|
|
1181
1191
|
stagingData.render.cid = '';
|
|
1182
1192
|
stagingData.render.metadataCid = '';
|
|
1183
|
-
const stagingSha256 = ObjectLayerEngine.computeSha256(stagingData);
|
|
1184
1193
|
|
|
1185
1194
|
const newRenderFrames = await ObjectLayerRenderFrames.create(entry.objectLayerRenderFramesData);
|
|
1186
|
-
objectLayer = await ObjectLayer.
|
|
1195
|
+
objectLayer = await ObjectLayer.upsertByItemId({
|
|
1187
1196
|
data: stagingData,
|
|
1188
|
-
sha256: stagingSha256,
|
|
1189
1197
|
objectLayerRenderFramesId: newRenderFrames._id,
|
|
1190
1198
|
});
|
|
1191
1199
|
}
|
|
@@ -1215,9 +1223,7 @@ try {
|
|
|
1215
1223
|
logger.info(`Showing frame for item: ${itemId}, direction: ${direction}, frame: ${frameIndexNum}`);
|
|
1216
1224
|
|
|
1217
1225
|
// Find ObjectLayer by item-id
|
|
1218
|
-
const objectLayer = await ObjectLayer.
|
|
1219
|
-
'objectLayerRenderFramesId',
|
|
1220
|
-
);
|
|
1226
|
+
const objectLayer = await ObjectLayer.findByItemId(itemId).populate('objectLayerRenderFramesId');
|
|
1221
1227
|
|
|
1222
1228
|
if (!objectLayer) {
|
|
1223
1229
|
logger.error(`ObjectLayer not found for item-id: ${itemId}`);
|
|
@@ -1301,9 +1307,7 @@ try {
|
|
|
1301
1307
|
}
|
|
1302
1308
|
|
|
1303
1309
|
// Find ObjectLayer by item-id
|
|
1304
|
-
const objectLayer = await ObjectLayer.
|
|
1305
|
-
'objectLayerRenderFramesId',
|
|
1306
|
-
);
|
|
1310
|
+
const objectLayer = await ObjectLayer.findByItemId(itemId).populate('objectLayerRenderFramesId');
|
|
1307
1311
|
|
|
1308
1312
|
if (!objectLayer) {
|
|
1309
1313
|
logger.error(`ObjectLayer not found for item-id: ${itemId}`);
|
|
@@ -1437,7 +1441,7 @@ try {
|
|
|
1437
1441
|
logger.info(`Looking up atlas sprite sheet for item: ${itemId}`);
|
|
1438
1442
|
|
|
1439
1443
|
// Find ObjectLayer by item-id
|
|
1440
|
-
const objectLayer = await ObjectLayer.
|
|
1444
|
+
const objectLayer = await ObjectLayer.findByItemId(itemId);
|
|
1441
1445
|
|
|
1442
1446
|
if (!objectLayer) {
|
|
1443
1447
|
logger.error(`ObjectLayer not found for item-id: ${itemId}`);
|
|
@@ -1954,7 +1958,7 @@ try {
|
|
|
1954
1958
|
? db.host
|
|
1955
1959
|
: db.host.replace('127.0.0.1', 'mongodb-0.mongodb-service');
|
|
1956
1960
|
|
|
1957
|
-
logger.info('instance env', { env: options.envPath, deployId, host, path
|
|
1961
|
+
logger.info('instance env', { env: options.envPath, deployId, host, path });
|
|
1958
1962
|
|
|
1959
1963
|
await DataBaseProviderService.load({
|
|
1960
1964
|
apis: [
|
|
@@ -2679,6 +2683,12 @@ try {
|
|
|
2679
2683
|
|
|
2680
2684
|
logger.info('Importing instance', { code: instanceCode, backupDir });
|
|
2681
2685
|
|
|
2686
|
+
// Idempotent: a backup restore writes object layers, so collapse any
|
|
2687
|
+
// legacy duplicates and make the data.item.id index unique first.
|
|
2688
|
+
const { removedIds, indexUpgraded } = await ObjectLayer.ensureUniqueItemIdIndex();
|
|
2689
|
+
if (removedIds.length > 0) logger.warn(`Removed ${removedIds.length} duplicate ObjectLayer document(s)`);
|
|
2690
|
+
if (indexUpgraded) logger.info('Upgraded data.item.id index to unique');
|
|
2691
|
+
|
|
2682
2692
|
// Item ids belonging to this instance (collected from imported object
|
|
2683
2693
|
// layers + the instance doc) — used to backfill missing skills from the
|
|
2684
2694
|
// canonical DefaultSkillConfig when the backup predates the skill model.
|
|
@@ -3013,12 +3023,21 @@ try {
|
|
|
3013
3023
|
let olCount = 0;
|
|
3014
3024
|
for (const file of olFiles) {
|
|
3015
3025
|
const olData = fs.readJsonSync(`${olDir}/${file}`);
|
|
3016
|
-
|
|
3017
|
-
if (
|
|
3018
|
-
|
|
3026
|
+
const olItemId = olData.data?.item?.id;
|
|
3027
|
+
if (!olItemId) {
|
|
3028
|
+
logger.warn(`Skipping object-layer file '${file}' — missing data.item.id`);
|
|
3029
|
+
continue;
|
|
3019
3030
|
}
|
|
3020
|
-
|
|
3021
|
-
|
|
3031
|
+
// preserveUUID only applies to items this database does not have yet.
|
|
3032
|
+
// An item that already exists keeps its own _id and simply absorbs the
|
|
3033
|
+
// backup's attribute values, so a restore can never fork data.item.id
|
|
3034
|
+
// into two documents. sha256 needs no separate cleanup: it is derived
|
|
3035
|
+
// from data, so an identical hash implies the same item id.
|
|
3036
|
+
const existingOL = await ObjectLayer.findByItemId(olItemId);
|
|
3037
|
+
if (!existingOL && olData._id) await ObjectLayer.deleteOne({ _id: olData._id });
|
|
3038
|
+
|
|
3039
|
+
await ObjectLayer.upsertByItemId(olData);
|
|
3040
|
+
importedItemIds.add(olItemId);
|
|
3022
3041
|
olCount++;
|
|
3023
3042
|
}
|
|
3024
3043
|
logger.info(`Imported ${olCount} ObjectLayer document(s)`);
|
|
@@ -3892,7 +3911,7 @@ try {
|
|
|
3892
3911
|
? db.host
|
|
3893
3912
|
: db.host.replace('127.0.0.1', 'mongodb-0.mongodb-service');
|
|
3894
3913
|
|
|
3895
|
-
logger.info('generate-saga', { deployId, host, path
|
|
3914
|
+
logger.info('generate-saga', { deployId, host, path });
|
|
3896
3915
|
|
|
3897
3916
|
await DataBaseProviderService.load({
|
|
3898
3917
|
apis: [
|
|
@@ -4835,6 +4854,7 @@ try {
|
|
|
4835
4854
|
'--clean',
|
|
4836
4855
|
'Restore repositories to canonical state (git checkout Dockerfile, etc.) before updating compose.env',
|
|
4837
4856
|
)
|
|
4857
|
+
.option('--test', 'Test DNS connectivity accross cyberia deployments')
|
|
4838
4858
|
.option('--reset', 'Reset the development environment before updating compose.env')
|
|
4839
4859
|
.action((options) => {
|
|
4840
4860
|
if (options.reset) {
|
|
@@ -4848,6 +4868,25 @@ try {
|
|
|
4848
4868
|
shellExec(`node bin run clean ./cyberia-client`);
|
|
4849
4869
|
return;
|
|
4850
4870
|
}
|
|
4871
|
+
if (options.test) {
|
|
4872
|
+
const testHosts = [
|
|
4873
|
+
'localhost',
|
|
4874
|
+
'localhost:4005',
|
|
4875
|
+
'localhost:8081',
|
|
4876
|
+
'localhost:8082',
|
|
4877
|
+
'engine-cyberia',
|
|
4878
|
+
'cyberia-server',
|
|
4879
|
+
'cyberia-client',
|
|
4880
|
+
];
|
|
4881
|
+
const testPaths = ['/', '/TEST', '/FOREST'];
|
|
4882
|
+
for (const host of testHosts) {
|
|
4883
|
+
for (const path of testPaths) {
|
|
4884
|
+
shellExec(`curl -L -v -i -s http://${host}${path} | head -n 10`, {
|
|
4885
|
+
silentOnError: true,
|
|
4886
|
+
});
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
}
|
|
4851
4890
|
const envPath = `./engine-private/conf/dd-cyberia/docker-compose/cyberia/compose.env`;
|
|
4852
4891
|
const canonicalDevDockerfile = './src/runtime/engine-cyberia/Dockerfile.dev';
|
|
4853
4892
|
fs.writeFileSync(
|
|
@@ -4930,7 +4969,7 @@ try {
|
|
|
4930
4969
|
? db.host
|
|
4931
4970
|
: db.host.replace('127.0.0.1', 'mongodb-0.mongodb-service');
|
|
4932
4971
|
|
|
4933
|
-
logger.info('drop-db', { deployId, host, path
|
|
4972
|
+
logger.info('drop-db', { deployId, host, path });
|
|
4934
4973
|
|
|
4935
4974
|
const cyberiaCollections = [
|
|
4936
4975
|
'cyberia-entity',
|
|
@@ -4990,6 +5029,19 @@ try {
|
|
|
4990
5029
|
shellExec(`gh workflow run ${id}.cd.yml -R underpostnet/${id} -f job=deploy`);
|
|
4991
5030
|
});
|
|
4992
5031
|
|
|
5032
|
+
runner
|
|
5033
|
+
.command('test')
|
|
5034
|
+
.option('--n-con <connections>', 'Number of concurrent WebSocket connections')
|
|
5035
|
+
.option('--duration <ms>', 'Load test duration in milliseconds')
|
|
5036
|
+
.option('--tap-freq <seconds>', 'Frequency of tap events in seconds')
|
|
5037
|
+
.action((options) => {
|
|
5038
|
+
shellExec(`CYBERIA_LOAD_WS_URL=ws://localhost:8081/ws \
|
|
5039
|
+
CYBERIA_LOAD_CONNECTIONS=${options.nCon ?? 40} \
|
|
5040
|
+
CYBERIA_LOAD_TAP_FREQUENCY=${options.tapFreq ?? 5} \
|
|
5041
|
+
CYBERIA_LOAD_DURATION_MS=${options.duration ?? 1000 * 60 * 30} \
|
|
5042
|
+
c8 mocha test/cyberia-load.test.js`);
|
|
5043
|
+
});
|
|
5044
|
+
|
|
4993
5045
|
runner
|
|
4994
5046
|
.command('docker-image [id]')
|
|
4995
5047
|
.option('--load-tar', 'Load a pre-built image tar archive into the enabled target(s) without building.')
|
|
@@ -4997,7 +5049,7 @@ try {
|
|
|
4997
5049
|
// no funca
|
|
4998
5050
|
if (options.loadTar) {
|
|
4999
5051
|
for (const imageId of dockerImageIds)
|
|
5000
|
-
if (imageId === id || id === '.') shellExec(`docker load -i ./${imageId}-dev_v3.
|
|
5052
|
+
if (imageId === id || id === '.') shellExec(`docker load -i ./${imageId}-dev_v3.3.0.tar`);
|
|
5001
5053
|
return;
|
|
5002
5054
|
}
|
|
5003
5055
|
switch (id) {
|
|
@@ -5008,7 +5060,7 @@ node bin/build dd-cyberia --update-private
|
|
|
5008
5060
|
node bin image --path src/runtime/engine-cyberia \
|
|
5009
5061
|
--docker-compose --pull-base --build \
|
|
5010
5062
|
--dockerfile-name Dockerfile.dev \
|
|
5011
|
-
--image-name engine-cyberia-dev:v3.
|
|
5063
|
+
--image-name engine-cyberia-dev:v3.3.0 \
|
|
5012
5064
|
--image-out-path .
|
|
5013
5065
|
`);
|
|
5014
5066
|
break;
|
|
@@ -5019,7 +5071,7 @@ cp -f src/runtime/cyberia-server/Dockerfile.dev cyberia-server/Dockerfile.dev
|
|
|
5019
5071
|
node bin image --path cyberia-server \
|
|
5020
5072
|
--docker-compose --pull-base --build \
|
|
5021
5073
|
--dockerfile-name Dockerfile.dev \
|
|
5022
|
-
--image-name cyberia-server-dev:v3.
|
|
5074
|
+
--image-name cyberia-server-dev:v3.3.0 \
|
|
5023
5075
|
--image-out-path .
|
|
5024
5076
|
`);
|
|
5025
5077
|
break;
|
|
@@ -5029,7 +5081,7 @@ cp -f src/runtime/cyberia-client/Dockerfile.dev cyberia-client/Dockerfile.dev
|
|
|
5029
5081
|
node bin image --path cyberia-client \
|
|
5030
5082
|
--docker-compose --pull-base --build \
|
|
5031
5083
|
--dockerfile-name Dockerfile.dev \
|
|
5032
|
-
--image-name cyberia-client-dev:v3.
|
|
5084
|
+
--image-name cyberia-client-dev:v3.3.0 \
|
|
5033
5085
|
--image-out-path .
|
|
5034
5086
|
`);
|
|
5035
5087
|
break;
|
|
@@ -5038,6 +5090,10 @@ node bin image --path cyberia-client \
|
|
|
5038
5090
|
|
|
5039
5091
|
for (const [cmd, action] of Object.entries(DOCKER_SCRIPTS))
|
|
5040
5092
|
runner.command(cmd).action(() => {
|
|
5093
|
+
if (cmd === 'docker:up' || cmd === 'docker:up:build' || cmd === 'docker:restart') {
|
|
5094
|
+
const { aliases, changed } = installCyberiaDockerHostAliases();
|
|
5095
|
+
logger.info(`Docker host aliases ${changed ? 'installed' : 'already configured'}`, { aliases });
|
|
5096
|
+
}
|
|
5041
5097
|
shellExec(action);
|
|
5042
5098
|
});
|
|
5043
5099
|
|
|
@@ -5074,7 +5130,7 @@ node bin image --path cyberia-client \
|
|
|
5074
5130
|
? db.host
|
|
5075
5131
|
: db.host.replace('127.0.0.1', 'mongodb-0.mongodb-service');
|
|
5076
5132
|
|
|
5077
|
-
logger.info('seed-dialogues', { deployId, host, path
|
|
5133
|
+
logger.info('seed-dialogues', { deployId, host, path });
|
|
5078
5134
|
|
|
5079
5135
|
await DataBaseProviderService.load({ apis: ['cyberia-dialogue'], host, path, db });
|
|
5080
5136
|
|
|
@@ -5129,7 +5185,7 @@ node bin image --path cyberia-client \
|
|
|
5129
5185
|
? db.host
|
|
5130
5186
|
: db.host.replace('127.0.0.1', 'mongodb-0.mongodb-service');
|
|
5131
5187
|
|
|
5132
|
-
logger.info('seed-actions-quests', { deployId, host, path
|
|
5188
|
+
logger.info('seed-actions-quests', { deployId, host, path });
|
|
5133
5189
|
|
|
5134
5190
|
await DataBaseProviderService.load({ apis: ['cyberia-action', 'cyberia-quest'], host, path, db });
|
|
5135
5191
|
|
|
@@ -5185,7 +5241,7 @@ node bin image --path cyberia-client \
|
|
|
5185
5241
|
? db.host
|
|
5186
5242
|
: db.host.replace('127.0.0.1', 'mongodb-0.mongodb-service');
|
|
5187
5243
|
|
|
5188
|
-
logger.info('seed-skills', { deployId, host, path
|
|
5244
|
+
logger.info('seed-skills', { deployId, host, path });
|
|
5189
5245
|
|
|
5190
5246
|
await DataBaseProviderService.load({ apis: ['cyberia-skill'], host, path, db });
|
|
5191
5247
|
|
|
@@ -5245,7 +5301,7 @@ node bin image --path cyberia-client \
|
|
|
5245
5301
|
? db.host
|
|
5246
5302
|
: db.host.replace('127.0.0.1', 'mongodb-0.mongodb-service');
|
|
5247
5303
|
|
|
5248
|
-
logger.info('seed-entities', { deployId, host, path
|
|
5304
|
+
logger.info('seed-entities', { deployId, host, path });
|
|
5249
5305
|
|
|
5250
5306
|
await DataBaseProviderService.load({ apis: ['cyberia-entity-type-default'], host, path, db });
|
|
5251
5307
|
|
|
@@ -5353,6 +5409,84 @@ node bin image --path cyberia-client \
|
|
|
5353
5409
|
logger.info('All semantic examples generated.');
|
|
5354
5410
|
});
|
|
5355
5411
|
|
|
5412
|
+
// Instance id → project root. Single source of truth for the workloads this
|
|
5413
|
+
// workflow builds: both the k8s manifests and the status page artifacts each
|
|
5414
|
+
// project ships are resolved from this list plus conf.instances.json.
|
|
5415
|
+
const CYBERIA_INSTANCE_PROJECTS = [
|
|
5416
|
+
{ id: 'mmo-client', rootPath: './cyberia-client' },
|
|
5417
|
+
{ id: 'mmo-server', rootPath: './cyberia-server' },
|
|
5418
|
+
];
|
|
5419
|
+
const CYBERIA_CONF_INSTANCES_PATH = './engine-private/conf/dd-cyberia/conf.instances.json';
|
|
5420
|
+
const CYBERIA_CONF_SSR_PATH = './engine-private/conf/dd-cyberia/conf.ssr.json';
|
|
5421
|
+
// Copy shared by the server and the client for a given status code. A status
|
|
5422
|
+
// without an entry falls back to its conf.ssr.json view title.
|
|
5423
|
+
const CYBERIA_STATUS_PAGE_META = {
|
|
5424
|
+
404: {
|
|
5425
|
+
title: 'Cyberia — Sector Not Found',
|
|
5426
|
+
description: 'Cyberia Online 404 — the requested sector is not on the grid.',
|
|
5427
|
+
},
|
|
5428
|
+
};
|
|
5429
|
+
|
|
5430
|
+
/**
|
|
5431
|
+
* Reads the raw (unexpanded) conf.instances.json entries.
|
|
5432
|
+
* @returns {Array<object>} Instance entries, or an empty list when unavailable.
|
|
5433
|
+
*/
|
|
5434
|
+
const readCyberiaConfInstances = () => {
|
|
5435
|
+
try {
|
|
5436
|
+
return JSON.parse(fs.readFileSync(CYBERIA_CONF_INSTANCES_PATH, 'utf8'));
|
|
5437
|
+
} catch (err) {
|
|
5438
|
+
logger.warn(`Could not read ${CYBERIA_CONF_INSTANCES_PATH}: ${err.message}`);
|
|
5439
|
+
return [];
|
|
5440
|
+
}
|
|
5441
|
+
};
|
|
5442
|
+
|
|
5443
|
+
/**
|
|
5444
|
+
* Resolves the SSR view that renders a status page. The view is declared in
|
|
5445
|
+
* conf.ssr.json as a route whose path is the bare status code (`/404`), the
|
|
5446
|
+
* same declaration the PWA build turns into `/404/index.html`.
|
|
5447
|
+
* @param {string} status - HTTP status code.
|
|
5448
|
+
* @returns {{ client: string, title: string }|null} View descriptor, or null when undeclared.
|
|
5449
|
+
*/
|
|
5450
|
+
const resolveStatusPageView = (status) => {
|
|
5451
|
+
if (!fs.existsSync(CYBERIA_CONF_SSR_PATH)) return null;
|
|
5452
|
+
const confSSR = JSON.parse(fs.readFileSync(CYBERIA_CONF_SSR_PATH, 'utf8'));
|
|
5453
|
+
for (const clientConf of Object.values(confSSR))
|
|
5454
|
+
for (const view of clientConf?.views || []) if (view.path === `/${status}`) return view;
|
|
5455
|
+
return null;
|
|
5456
|
+
};
|
|
5457
|
+
|
|
5458
|
+
/**
|
|
5459
|
+
* Renders one custom status page to a static HTML artifact. The workloads no
|
|
5460
|
+
* longer serve error pages themselves — the document is carried into the
|
|
5461
|
+
* gateway config by `run instance-build-manifest`, so this only has to place
|
|
5462
|
+
* it at the `hostPath` the instance declares.
|
|
5463
|
+
* @param {string} status - HTTP status code.
|
|
5464
|
+
* @param {string} outputPath - Destination HTML path.
|
|
5465
|
+
* @param {boolean} [dev] - Render the development variant.
|
|
5466
|
+
* @returns {boolean} True when the artifact was rendered.
|
|
5467
|
+
*/
|
|
5468
|
+
const buildCyberiaStatusPage = ({ status, outputPath, dev = false }) => {
|
|
5469
|
+
const view = resolveStatusPageView(status);
|
|
5470
|
+
const pagePath = `./src/client/ssr/views/${view?.client || `Cyberia${status}`}.js`;
|
|
5471
|
+
if (!fs.existsSync(pagePath)) {
|
|
5472
|
+
logger.warn(`[build-status-page] No SSR view for status ${status}; skipping`, { pagePath, outputPath });
|
|
5473
|
+
return false;
|
|
5474
|
+
}
|
|
5475
|
+
const meta = CYBERIA_STATUS_PAGE_META[status] || {};
|
|
5476
|
+
const title = meta.title || view?.title || `Cyberia — ${status}`;
|
|
5477
|
+
const description = meta.description || `Cyberia Online ${status}.`;
|
|
5478
|
+
shellExec(
|
|
5479
|
+
`node bin static --page ${pagePath}` +
|
|
5480
|
+
` --output-path ${outputPath}` +
|
|
5481
|
+
` --title '${title}'` +
|
|
5482
|
+
` --favicon /favicon.ico` +
|
|
5483
|
+
` --description '${description}'` +
|
|
5484
|
+
` --lang en` +
|
|
5485
|
+
` --env ${dev ? 'development' : 'production'}`,
|
|
5486
|
+
);
|
|
5487
|
+
return true;
|
|
5488
|
+
};
|
|
5489
|
+
|
|
5356
5490
|
runner
|
|
5357
5491
|
.command('build-manifest')
|
|
5358
5492
|
.option(
|
|
@@ -5385,24 +5519,24 @@ node bin image --path cyberia-client \
|
|
|
5385
5519
|
// A variant declared in conf.instances.json without the instance
|
|
5386
5520
|
// directory is silently skipped so the Dockerfile never tries to
|
|
5387
5521
|
// copy a non-existent directory and the container build does not fail.
|
|
5388
|
-
const confInstancesPath = './engine-private/conf/dd-cyberia/conf.instances.json';
|
|
5389
5522
|
const cyberiaInstancesDir = '/home/dd/cyberia-instances';
|
|
5390
5523
|
let instanceCodes = 'amethyst-strata-expansion,FOREST'; // fallback
|
|
5524
|
+
const confInstancesEntries = readCyberiaConfInstances();
|
|
5391
5525
|
try {
|
|
5392
|
-
const
|
|
5393
|
-
const serverInstances = confInstances.filter((inst) => inst.runtime === 'cyberia-server');
|
|
5526
|
+
const serverInstances = confInstancesEntries.filter((inst) => inst.runtime === 'cyberia-server');
|
|
5394
5527
|
const codes = new Set();
|
|
5395
5528
|
for (const inst of serverInstances) {
|
|
5396
5529
|
if (inst.multiInstance?.variants) {
|
|
5397
|
-
|
|
5398
|
-
|
|
5530
|
+
const topology = normalizeInstanceTopology(inst.multiInstance, `dd-cyberia/${inst.id}`);
|
|
5531
|
+
for (const v of topology.variants) {
|
|
5532
|
+
const code = v.path === '/' ? DEFAULT_INSTANCE_CODE : v.code;
|
|
5399
5533
|
// Skip codes that have no on-disk instance backup dir
|
|
5400
|
-
const instanceDir = `${cyberiaInstancesDir}/instances/${
|
|
5534
|
+
const instanceDir = `${cyberiaInstancesDir}/instances/${code}`;
|
|
5401
5535
|
if (!fs.existsSync(instanceDir)) {
|
|
5402
|
-
logger.info(`[build-manifest] Skipping code "${
|
|
5536
|
+
logger.info(`[build-manifest] Skipping code "${code}": no instance dir at ${instanceDir}`);
|
|
5403
5537
|
continue;
|
|
5404
5538
|
}
|
|
5405
|
-
codes.add(
|
|
5539
|
+
codes.add(code);
|
|
5406
5540
|
}
|
|
5407
5541
|
}
|
|
5408
5542
|
}
|
|
@@ -5413,7 +5547,7 @@ node bin image --path cyberia-client \
|
|
|
5413
5547
|
logger.warn(`[build-manifest] No valid instance codes found; keeping fallback: ${instanceCodes}`);
|
|
5414
5548
|
}
|
|
5415
5549
|
} catch (err) {
|
|
5416
|
-
logger.warn(`[build-manifest] Could not read ${
|
|
5550
|
+
logger.warn(`[build-manifest] Could not read ${CYBERIA_CONF_INSTANCES_PATH}: ${err.message}; using fallback`);
|
|
5417
5551
|
}
|
|
5418
5552
|
|
|
5419
5553
|
// ── Update Dockerfile.dev + Dockerfile INSTANCE_CODES build arg ──────
|
|
@@ -5474,33 +5608,46 @@ node bin image --path cyberia-client \
|
|
|
5474
5608
|
logger.warn(`[build-manifest] Could not update ${catalogPath}: ${err.message}`);
|
|
5475
5609
|
}
|
|
5476
5610
|
|
|
5611
|
+
// ── Build SSR views ──────────────────────────────────────────────────
|
|
5612
|
+
// Status pages are rendered BEFORE the manifests: the gateway manifests
|
|
5613
|
+
// embed each document declared under an instance's `customStatusPages`,
|
|
5614
|
+
// so the artifact has to exist at its `hostPath` by manifest time.
|
|
5615
|
+
const statusPagesBuilt = [];
|
|
5616
|
+
for (const { id, rootPath } of CYBERIA_INSTANCE_PROJECTS) {
|
|
5617
|
+
const instance = confInstancesEntries.find((entry) => entry.id === id);
|
|
5618
|
+
for (const page of instance?.customStatusPages || []) {
|
|
5619
|
+
if (!page?.status || !page?.hostPath) continue;
|
|
5620
|
+
const outputPath = nodePath.normalize(`${rootPath}/${page.hostPath}`);
|
|
5621
|
+
if (buildCyberiaStatusPage({ status: page.status, outputPath, dev: isDev }))
|
|
5622
|
+
statusPagesBuilt.push({ instance: id, status: page.status, outputPath });
|
|
5623
|
+
}
|
|
5624
|
+
}
|
|
5625
|
+
logger.info('[build-manifest] Custom status pages built', statusPagesBuilt);
|
|
5626
|
+
shellExec(
|
|
5627
|
+
`node bin/cyberia run-workflow build-server-dashboard --output-path ./cyberia-server/public/index.html`,
|
|
5628
|
+
);
|
|
5629
|
+
|
|
5477
5630
|
// ── Build dev manifests (always --kind --dev) ────────────────────────
|
|
5478
5631
|
{
|
|
5479
5632
|
const flags = `--kind --dev${nodeFlag}`;
|
|
5480
|
-
|
|
5481
|
-
|
|
5633
|
+
for (const { id, rootPath } of CYBERIA_INSTANCE_PROJECTS)
|
|
5634
|
+
shellExec(`node bin run instance-build-manifest 'dd-cyberia,${id},${rootPath}' ${flags}`);
|
|
5482
5635
|
}
|
|
5483
5636
|
// ── Build prod manifests (--kubeadm, no --dev) ───────────────────────
|
|
5484
5637
|
if (!isDev) {
|
|
5485
5638
|
const flags = `--kubeadm${nodeFlag}`;
|
|
5486
|
-
|
|
5487
|
-
|
|
5639
|
+
for (const { id, rootPath } of CYBERIA_INSTANCE_PROJECTS)
|
|
5640
|
+
shellExec(`node bin run instance-build-manifest 'dd-cyberia,${id},${rootPath}' ${flags}`);
|
|
5488
5641
|
}
|
|
5489
5642
|
|
|
5490
|
-
// ── Build SSR views ──────────────────────────────────────────────────
|
|
5491
|
-
const env404 = isDev ? ' --dev' : '';
|
|
5492
|
-
shellExec(
|
|
5493
|
-
`node bin/cyberia run-workflow build-cyberia-404 --output-path ./cyberia-server/public/404.html${env404}`,
|
|
5494
|
-
);
|
|
5495
|
-
shellExec(`node bin/cyberia run-workflow build-cyberia-404 --output-path ./cyberia-client/bin/404.html${env404}`);
|
|
5496
|
-
shellExec(
|
|
5497
|
-
`node bin/cyberia run-workflow build-server-dashboard --output-path ./cyberia-server/public/index.html`,
|
|
5498
|
-
);
|
|
5499
|
-
|
|
5500
5643
|
// Copy canonical doc sources into the generated project READMEs.
|
|
5501
5644
|
// Edit the canonical sources; never hand-edit these generated outputs.
|
|
5645
|
+
fs.copySync('./deploy/lib', './cyberia-server/deploy/lib', { overwrite: true });
|
|
5646
|
+
fs.copySync('./deploy/lib', './cyberia-client/deploy/lib', { overwrite: true });
|
|
5502
5647
|
fs.copyFileSync('./src/client/public/cyberia-docs/CYBERIA-CLIENT.md', './cyberia-client/README.md');
|
|
5648
|
+
fs.copySync('./deploy/cyberia-client', './cyberia-client/deploy');
|
|
5503
5649
|
fs.copyFileSync('./src/client/public/cyberia-docs/CYBERIA-SERVER.md', './cyberia-server/README.md');
|
|
5650
|
+
fs.copySync('./deploy/cyberia-server', './cyberia-server/deploy');
|
|
5504
5651
|
fs.copyFileSync(
|
|
5505
5652
|
'./.github/workflows/cyberia-client.cd.yml',
|
|
5506
5653
|
'./cyberia-client/.github/workflows/cyberia-client.cd.yml',
|
|
@@ -5565,27 +5712,54 @@ node bin image --path cyberia-client \
|
|
|
5565
5712
|
);
|
|
5566
5713
|
});
|
|
5567
5714
|
|
|
5715
|
+
runner
|
|
5716
|
+
.command('build-status-page')
|
|
5717
|
+
.requiredOption(
|
|
5718
|
+
'--status <status>',
|
|
5719
|
+
'HTTP status code to render (must be declared as an SSR view in conf.ssr.json).',
|
|
5720
|
+
)
|
|
5721
|
+
.option('--dev', 'Build a development variant of the status page.')
|
|
5722
|
+
.option(
|
|
5723
|
+
'--output-path <path>',
|
|
5724
|
+
'Output path for the rendered HTML. Defaults to the `hostPath` the mmo-server instance declares for the status.',
|
|
5725
|
+
)
|
|
5726
|
+
.description(
|
|
5727
|
+
'Build one custom status page artifact. The SSR view is resolved from the conf.ssr.json route whose path is ' +
|
|
5728
|
+
'the bare status code; the rendered document is served by the gateway, not by the workload.',
|
|
5729
|
+
)
|
|
5730
|
+
.action((options) => {
|
|
5731
|
+
const status = `${options.status}`;
|
|
5732
|
+
const statusPage = (
|
|
5733
|
+
readCyberiaConfInstances().find((entry) => entry.id === 'mmo-server')?.customStatusPages || []
|
|
5734
|
+
).find((page) => `${page.status}` === status);
|
|
5735
|
+
const outputPath =
|
|
5736
|
+
options.outputPath || (statusPage ? nodePath.normalize(`./cyberia-server/${statusPage.hostPath}`) : null);
|
|
5737
|
+
if (!outputPath) {
|
|
5738
|
+
logger.error(`[build-status-page] No --output-path and no customStatusPages entry for status ${status}`);
|
|
5739
|
+
return;
|
|
5740
|
+
}
|
|
5741
|
+
buildCyberiaStatusPage({ status, outputPath, dev: !!options.dev });
|
|
5742
|
+
});
|
|
5743
|
+
|
|
5568
5744
|
runner
|
|
5569
5745
|
.command('build-cyberia-404')
|
|
5570
5746
|
.option('--dev', 'Build a development variant of the 404 page.')
|
|
5571
5747
|
.option(
|
|
5572
5748
|
'--output-path <path>',
|
|
5573
5749
|
'Output path for the rendered 404.html (default: ./cyberia-server/public/404.html). ' +
|
|
5574
|
-
'The same page is served, sub-path aware, by every instance variant of both the ' +
|
|
5575
|
-
'cyberia-server
|
|
5750
|
+
'The same page is served, sub-path aware, by the gateway for every instance variant of both the ' +
|
|
5751
|
+
'cyberia-server and cyberia-client workloads.',
|
|
5752
|
+
)
|
|
5753
|
+
.description(
|
|
5754
|
+
'Build the cyberpunk pixel-art "sector not found" (404) page shared by the Cyberia server + client. ' +
|
|
5755
|
+
'Thin alias of `build-status-page --status 404`, kept as the entrypoint the instance CI workflows call.',
|
|
5576
5756
|
)
|
|
5577
|
-
.description('Build the cyberpunk pixel-art "sector not found" (404) page shared by the Cyberia server + client.')
|
|
5578
5757
|
.action((options) => {
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
` --favicon /favicon.ico` +
|
|
5585
|
-
` --description 'Cyberia Online 404 — the requested sector is not on the grid.'` +
|
|
5586
|
-
` --lang en` +
|
|
5587
|
-
` --env ${options.dev ? 'development' : 'production'}`,
|
|
5588
|
-
);
|
|
5758
|
+
buildCyberiaStatusPage({
|
|
5759
|
+
status: '404',
|
|
5760
|
+
outputPath: options.outputPath || './cyberia-server/public/404.html',
|
|
5761
|
+
dev: !!options.dev,
|
|
5762
|
+
});
|
|
5589
5763
|
});
|
|
5590
5764
|
|
|
5591
5765
|
// Passthrough check: if the user invoked a command that is OWNED by the
|