@underpostnet/cyberia 3.2.90 → 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 (129) hide show
  1. package/.env.example +16 -0
  2. package/.github/workflows/coverall.cyberia.ci.yml +87 -0
  3. package/.github/workflows/cyberia-client.cd.yml +1 -18
  4. package/.github/workflows/cyberia-server.cd.yml +1 -18
  5. package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
  6. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
  7. package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
  8. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
  9. package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
  10. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
  11. package/.github/workflows/engine-cyberia.cd.yml +3 -78
  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 +110 -1
  17. package/CLI-HELP.md +139 -9
  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 +73 -53
  23. package/bin/deploy.js +1 -1
  24. package/bin/index.js +73 -53
  25. package/compose.env +16 -0
  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 -1
  34. package/docker-compose.yml +31 -37
  35. package/hardhat/package-lock.json +9 -9
  36. package/hardhat/package.json +3 -3
  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 -1
  41. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  42. package/package.json +2 -2
  43. package/scripts/audit-selinux.sh +64 -0
  44. package/scripts/coverall-test-cyberia.sh +24 -0
  45. package/scripts/coverall-test.sh +24 -0
  46. package/scripts/gpu-diag.sh +0 -0
  47. package/scripts/ip-info.sh +0 -0
  48. package/scripts/k3s-node-setup.sh +18 -15
  49. package/scripts/kubeadm-node-setup.sh +12 -23
  50. package/scripts/link-local-underpost-cli.sh +0 -0
  51. package/scripts/lxd-vm-setup.sh +0 -0
  52. package/scripts/maas-nat-firewalld.sh +0 -0
  53. package/scripts/nat-iptables.sh +2 -0
  54. package/scripts/rhel-grpc-setup.sh +0 -0
  55. package/scripts/rocky-kickstart.sh +25 -9
  56. package/scripts/test-monitor.sh +1 -1
  57. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
  58. package/src/api/cyberia-instance/cyberia-fallback-world.js +8 -1
  59. package/src/api/cyberia-instance/cyberia-instance.controller.js +3 -0
  60. package/src/api/cyberia-instance/cyberia-instance.router.js +13 -0
  61. package/src/api/cyberia-instance/cyberia-instance.service.js +43 -0
  62. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
  63. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +16 -3
  64. package/src/api/object-layer/object-layer.model.js +204 -3
  65. package/src/api/object-layer/object-layer.service.js +1 -14
  66. package/src/cli/baremetal.js +1 -2
  67. package/src/cli/cloud-init.js +1 -1
  68. package/src/cli/cluster.js +73 -68
  69. package/src/cli/db.js +9 -2
  70. package/src/cli/deploy.js +21 -5
  71. package/src/cli/docker-compose.js +1 -1
  72. package/src/cli/env.js +1 -1
  73. package/src/cli/image.js +0 -1
  74. package/src/cli/index.js +121 -9
  75. package/src/cli/lxd.js +1 -1
  76. package/src/cli/monitor.js +1 -1
  77. package/src/cli/release.js +57 -22
  78. package/src/cli/repository.js +11 -9
  79. package/src/cli/run.js +36 -9
  80. package/src/cli/ssh.js +198 -77
  81. package/src/cli/system.js +26 -13
  82. package/src/cli/test.js +1 -1
  83. package/src/cli/vultr.js +583 -0
  84. package/src/cli/wireguard.js +2125 -0
  85. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +368 -0
  86. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +33 -0
  87. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
  88. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  89. package/src/client/public/cyberia-docs/ARCHITECTURE.md +17 -3
  90. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
  91. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  92. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  93. package/src/client/services/cyberia-instance/cyberia-instance.service.js +40 -0
  94. package/src/client/services/object-layer/object-layer.management.js +4 -4
  95. package/src/client-builder/client-build.js +20 -14
  96. package/src/db/mongo/MongooseDB.js +4 -0
  97. package/src/index.js +25 -1
  98. package/src/projects/cyberia/catalog-cyberia.js +5 -0
  99. package/src/projects/cyberia/generate-saga.js +14 -23
  100. package/src/projects/cyberia/instance-data.js +21 -2
  101. package/src/projects/cyberia/object-layer.js +11 -21
  102. package/src/projects/underpost/catalog-underpost.js +4 -1
  103. package/src/runtime/cyberia-client/Dockerfile +1 -1
  104. package/src/runtime/cyberia-client/Dockerfile.dev +1 -1
  105. package/src/runtime/cyberia-server/Dockerfile +1 -1
  106. package/src/runtime/cyberia-server/Dockerfile.dev +1 -1
  107. package/src/runtime/engine-cyberia/Dockerfile +1 -1
  108. package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
  109. package/src/runtime/engine-cyberia/Dockerfile.test +1 -1
  110. package/src/runtime/engine-cyberia/compose.env +16 -0
  111. package/src/runtime/engine-cyberia/docker-compose.yml +31 -37
  112. package/src/server/backup.js +1 -1
  113. package/src/server/conf.js +18 -108
  114. package/src/server/cron.js +249 -51
  115. package/src/server/dns.js +100 -6
  116. package/src/server/environment.js +98 -0
  117. package/src/server/forward-proxy.js +549 -0
  118. package/src/server/middlewares.js +56 -1
  119. package/src/server/process.js +0 -1
  120. package/src/server/selinux.js +185 -0
  121. package/src/server/systemd.js +205 -0
  122. package/src/server/underpost-compression.js +186 -0
  123. package/src/server/underpost-gateway.js +20 -10
  124. package/src/server/underpost-ingress.js +18 -2
  125. package/test/cyberia-instance-conf-defaults.test.js +1 -0
  126. package/test/selinux.test.js +71 -0
  127. package/test/underpost-gateway.test.js +41 -0
  128. package/test/underpost-ingress.test.js +52 -0
  129. package/test/wireguard-edge.test.js +1177 -0
@@ -0,0 +1,52 @@
1
+ apiVersion: batch/v1
2
+ kind: CronJob
3
+ metadata:
4
+ name: dd-cron-vultr
5
+ namespace: default
6
+ labels:
7
+ app: dd-cron-vultr
8
+ managed-by: underpost
9
+ spec:
10
+ schedule: "*/30 * * * *"
11
+ concurrencyPolicy: Forbid
12
+ startingDeadlineSeconds: 200
13
+ successfulJobsHistoryLimit: 3
14
+ failedJobsHistoryLimit: 1
15
+ suspend: false
16
+ jobTemplate:
17
+ metadata:
18
+ labels:
19
+ app: dd-cron-vultr
20
+ managed-by: underpost
21
+ spec:
22
+ template:
23
+ metadata:
24
+ labels:
25
+ app: dd-cron-vultr
26
+ managed-by: underpost
27
+ spec:
28
+ containers:
29
+ - name: dd-cron-vultr
30
+ image: underpost/underpost-engine:v3.3.0
31
+ command:
32
+ - /bin/sh
33
+ - -c
34
+ - >
35
+ cd /home/dd/engine &&
36
+ node bin env dd-cron production &&
37
+ node bin cron dd-cron vultr --git --kubeadm
38
+ volumeMounts:
39
+ - mountPath: /home/dd/engine
40
+ name: underpost-cron-container-volume
41
+ - mountPath: /usr/lib/node_modules/underpost
42
+ name: underpost-share-env
43
+ volumes:
44
+ - hostPath:
45
+ path: /home/dd/engine
46
+ type: Directory
47
+ name: underpost-cron-container-volume
48
+ - hostPath:
49
+ path: /root/.nvm/versions/node/v24.15.0/lib/node_modules/underpost
50
+ type: DirectoryOrCreate
51
+ name: underpost-share-env
52
+ restartPolicy: OnFailure
@@ -20,7 +20,7 @@ spec:
20
20
  spec:
21
21
  containers:
22
22
  - name: dd-cyberia-development-blue
23
- image: underpost/underpost-engine:v3.2.90
23
+ image: underpost/underpost-engine:v3.3.0
24
24
  imagePullPolicy: IfNotPresent
25
25
  envFrom:
26
26
  - secretRef:
@@ -17,7 +17,7 @@ spec:
17
17
  spec:
18
18
  containers:
19
19
  - name: dd-default-development-blue
20
- image: underpost/underpost-engine:v3.2.90
20
+ image: underpost/underpost-engine:v3.3.0
21
21
  # resources:
22
22
  # requests:
23
23
  # memory: "124Ki"
@@ -98,7 +98,7 @@ spec:
98
98
  spec:
99
99
  containers:
100
100
  - name: dd-default-development-green
101
- image: underpost/underpost-engine:v3.2.90
101
+ image: underpost/underpost-engine:v3.3.0
102
102
  # resources:
103
103
  # requests:
104
104
  # memory: "124Ki"
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "main": "src/index.js",
4
4
  "name": "@underpostnet/cyberia",
5
- "version": "3.2.90",
5
+ "version": "3.3.0",
6
6
  "description": "Cyberia CLI — toolchain for the Cyberia MMO data layer, content pipeline, persistence, gRPC services, and ERC-1155 lifecycle on Hyperledger Besu.",
7
7
  "scripts": {
8
8
  "start": "node --max-old-space-size=8192 src/server",
@@ -72,7 +72,7 @@
72
72
  },
73
73
  "homepage": "https://github.com/underpostnet/engine-cyberia#readme",
74
74
  "dependencies": {
75
- "underpost": "^3.2.90",
75
+ "underpost": "^3.3.0",
76
76
  "adm-zip": "^0.6.0",
77
77
  "maxrects-packer": "^2.7.3",
78
78
  "pngjs": "^7.0.0",
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SINCE="recent"
5
+ FAIL_ON_AVC="1"
6
+
7
+ while [ "$#" -gt 0 ]; do
8
+ case "$1" in
9
+ --since)
10
+ [ "$#" -ge 2 ] || { echo "--since requires an ausearch time value" >&2; exit 2; }
11
+ SINCE="$2"
12
+ shift 2
13
+ ;;
14
+ --no-fail)
15
+ FAIL_ON_AVC="0"
16
+ shift
17
+ ;;
18
+ -h|--help)
19
+ echo "Usage: $0 [--since recent|today|boot|TIME] [--no-fail]"
20
+ exit 0
21
+ ;;
22
+ *)
23
+ echo "Unknown option: $1" >&2
24
+ exit 2
25
+ ;;
26
+ esac
27
+ done
28
+
29
+ command -v getenforce >/dev/null 2>&1 || {
30
+ echo "SELinux userspace is unavailable. Install policycoreutils." >&2
31
+ exit 2
32
+ }
33
+ command -v ausearch >/dev/null 2>&1 || {
34
+ echo "ausearch is unavailable. Install audit and start auditd." >&2
35
+ exit 2
36
+ }
37
+
38
+ MODE="$(getenforce)"
39
+ echo "SELinux mode: $MODE"
40
+ [ "$MODE" = "Enforcing" ] || echo "WARNING: expected Enforcing mode" >&2
41
+ command -v sestatus >/dev/null 2>&1 && sestatus
42
+
43
+ AUDIT=(ausearch)
44
+ [ "$(id -u)" -eq 0 ] || AUDIT=(sudo ausearch)
45
+ RAW_FILE="$(mktemp /tmp/underpost-selinux-avc.XXXXXX)"
46
+ trap 'rm -f "$RAW_FILE"' EXIT
47
+
48
+ "${AUDIT[@]}" -m AVC,USER_AVC,SELINUX_ERR -ts "$SINCE" --raw > "$RAW_FILE" 2>/dev/null || true
49
+ if ! grep -q '^type=' "$RAW_FILE"; then
50
+ echo "No SELinux AVC denials found since: $SINCE"
51
+ [ "$MODE" = "Enforcing" ]
52
+ exit
53
+ fi
54
+
55
+ echo "SELinux AVC denials found since: $SINCE" >&2
56
+ "${AUDIT[@]}" -m AVC,USER_AVC,SELINUX_ERR -ts "$SINCE" -i || true
57
+ if command -v sealert >/dev/null 2>&1; then
58
+ echo "sealert analysis:"
59
+ sealert -a "$RAW_FILE" || true
60
+ else
61
+ echo "Install setroubleshoot-server for sealert analysis." >&2
62
+ fi
63
+
64
+ [ "$FAIL_ON_AVC" = "0" ] || exit 1
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ set -u -o pipefail
3
+
4
+ EXCLUDES=(
5
+ --exclude="test/cluster-instances.test.js"
6
+ --exclude="test/deploy-monitor.test.js"
7
+ --exclude="test/deploy-node-placement.test.js"
8
+ --exclude="test/instance-traffic-plan.test.js"
9
+ --exclude="test/sops-secret-store.test.js"
10
+ --exclude="test/underpost-gateway.test.js"
11
+ --exclude="test/underpost-ingress.test.js"
12
+ --exclude="test/wireguard-edge.test.js"
13
+ )
14
+
15
+ TESTS=(
16
+ test/cyberia-instance-conf-defaults.test.js
17
+ test/cyberia-load.test.js
18
+ test/object-layer-item-id.test.js
19
+ test/api.test.js
20
+ test/crypto.test.js
21
+ test/shape-generator.test.js
22
+ )
23
+
24
+ c8 "${EXCLUDES[@]}" mocha "${TESTS[@]}"
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ set -u -o pipefail
3
+
4
+ EXCLUDES=(
5
+ --exclude="test/cyberia-instance-conf-defaults.test.js"
6
+ --exclude="test/cyberia-load.test.js"
7
+ --exclude="test/object-layer-item-id.test.js"
8
+ --exclude="test/api.test.js"
9
+ --exclude="test/crypto.test.js"
10
+ --exclude="test/shape-generator.test.js"
11
+ )
12
+
13
+ TESTS=(
14
+ test/cluster-instances.test.js
15
+ test/deploy-monitor.test.js
16
+ test/deploy-node-placement.test.js
17
+ test/instance-traffic-plan.test.js
18
+ test/sops-secret-store.test.js
19
+ test/underpost-gateway.test.js
20
+ test/underpost-ingress.test.js
21
+ test/wireguard-edge.test.js
22
+ )
23
+
24
+ c8 "${EXCLUDES[@]}" mocha "${TESTS[@]}"
File without changes
File without changes
@@ -49,22 +49,25 @@ if [ -z "$(ls -A "$ENGINE_ROOT")" ]; then
49
49
  fi
50
50
 
51
51
  # ---------------------------------------------------------------------------
52
- # NVM and Node.js required for `node bin ...` entrypoints
52
+ # System-wide Node.js is required by service units under SELinux.
53
53
  # ---------------------------------------------------------------------------
54
- echo "Installing NVM and Node.js v24.15.0..."
55
-
56
- curl -o- https://cdn.jsdelivr.net/gh/nvm-sh/nvm@v0.40.1/install.sh | bash
57
-
58
- export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
59
- # shellcheck disable=SC1090
60
- [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
54
+ if command -v dnf >/dev/null 2>&1; then
55
+ sudo dnf install -y policycoreutils policycoreutils-python-utils selinux-policy-targeted audit
56
+ fi
61
57
 
62
- nvm install 24.15.0
63
- nvm use 24.15.0
64
- nvm alias default 24.15.0
65
- ln -sf "$(command -v node)" /usr/local/bin/node
66
- ln -sf "$(command -v npm)" /usr/local/bin/npm
67
- ln -sf "$(command -v npx)" /usr/local/bin/npx
58
+ if ! command -v node >/dev/null 2>&1 || ! node --version 2>/dev/null | grep -q '^v24'; then
59
+ echo "Installing system-wide Node.js 24..."
60
+ if command -v dnf >/dev/null 2>&1; then
61
+ curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -
62
+ sudo dnf install -y nodejs
63
+ elif command -v apt-get >/dev/null 2>&1; then
64
+ curl -fsSL https://deb.nodesource.com/setup_24.x | sudo bash -
65
+ sudo apt-get install -y nodejs
66
+ else
67
+ echo "ERROR: a supported system package manager is required for Node.js 24" >&2
68
+ exit 1
69
+ fi
70
+ fi
68
71
 
69
72
  echo "
70
73
  ██╗░░░██╗███╗░░██╗██████╗░███████╗██████╗░██████╗░░█████╗░░██████╗████████╗
@@ -116,7 +119,7 @@ if [ "$ROLE" = "control" ]; then
116
119
  curl -sfL https://get.k3s.io | \
117
120
  K3S_URL="https://${CONTROL_IP}:6443" \
118
121
  K3S_TOKEN="${K3S_TOKEN}" \
119
- sh -s - agent
122
+ sh -s - agent $(if command -v selinuxenabled >/dev/null 2>&1 && selinuxenabled; then printf '%s' '--selinux'; fi)
120
123
 
121
124
  echo ""
122
125
  echo "K3s worker joined https://${CONTROL_IP}:6443 successfully."
@@ -5,8 +5,8 @@ set -euo pipefail
5
5
  # Underpost Kubeadm Node Setup (bare-metal physical node)
6
6
  #
7
7
  # Mirrors scripts/k3s-node-setup.sh but for kubeadm on real hardware: it runs on
8
- # the freshly deployed OS (disk-installed Rocky) after first boot, installs NVM +
9
- # Node.js, ensures the engine source is present, and drives the cluster bring-up
8
+ # the freshly deployed OS (disk-installed Rocky) after first boot, installs
9
+ # system-wide Node.js, ensures the engine source is present, and drives the cluster bring-up
10
10
  # through the local engine entrypoint `node bin cluster` (src/cli/cluster.js) —
11
11
  # reusing its kubeadm + Contour + host-init logic instead of reimplementing it.
12
12
  #
@@ -146,33 +146,22 @@ if [ "$JOIN_ONLY" = "1" ]; then
146
146
  fi
147
147
 
148
148
  # ---------------------------------------------------------------------------
149
- # 0. Base prerequisites — a minimal @core Rocky install lacks tar/xz/git, which
150
- # NVM needs to extract Node.js and the engine clone needs. Install them first.
149
+ # 0. Base prerequisites.
151
150
  # ---------------------------------------------------------------------------
152
- log "Installing base prerequisites (tar, xz, gzip, git, curl)..."
153
- sudo dnf install -y tar xz gzip bzip2 git curl ca-certificates which findutils 2>/dev/null \
154
- || dnf install -y tar xz gzip bzip2 git curl ca-certificates which findutils
151
+ log "Installing base prerequisites..."
152
+ sudo dnf install -y tar xz gzip bzip2 git curl ca-certificates which findutils policycoreutils policycoreutils-python-utils selinux-policy-targeted audit 2>/dev/null \
153
+ || dnf install -y tar xz gzip bzip2 git curl ca-certificates which findutils policycoreutils policycoreutils-python-utils selinux-policy-targeted audit
155
154
 
156
155
  # ---------------------------------------------------------------------------
157
- # 1. NVM and Node.js required for the `node bin ...` entrypoints. Idempotent so
158
- # a retried run does not reinstall Node from scratch.
156
+ # 1. System-wide Node.js. Home-directory runtimes cannot be used by hardened
157
+ # systemd services under SELinux.
159
158
  # ---------------------------------------------------------------------------
160
159
  if command -v node >/dev/null 2>&1 && node --version 2>/dev/null | grep -q '^v24'; then
161
- log "Node.js $(node --version) already installed; skipping NVM setup"
160
+ log "Node.js $(node --version) already installed"
162
161
  else
163
- log "Installing NVM and Node.js v24.15.0..."
164
- curl -o- https://cdn.jsdelivr.net/gh/nvm-sh/nvm@v0.40.1/install.sh | bash
165
-
166
- export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
167
- # shellcheck disable=SC1090
168
- [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
169
-
170
- nvm install 24.15.0
171
- nvm use 24.15.0
172
- nvm alias default 24.15.0
173
- ln -sf "$(command -v node)" /usr/local/bin/node
174
- ln -sf "$(command -v npm)" /usr/local/bin/npm
175
- ln -sf "$(command -v npx)" /usr/local/bin/npx
162
+ log "Installing system-wide Node.js 24..."
163
+ curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -
164
+ sudo dnf install -y nodejs
176
165
  fi
177
166
 
178
167
  echo "
File without changes
File without changes
File without changes
@@ -42,12 +42,14 @@ echo "[INFO] Opening Kubernetes control plane ports..."
42
42
 
43
43
  # Kubernetes API Server
44
44
  sudo firewall-cmd --permanent --zone=public --add-port=6443/tcp
45
+ sudo firewall-cmd --permanent --zone=public --add-port=6443/udp
45
46
 
46
47
  # etcd
47
48
  sudo firewall-cmd --permanent --zone=public --add-port=2379-2380/tcp
48
49
 
49
50
  # kubelet API
50
51
  sudo firewall-cmd --permanent --zone=public --add-port=10250/tcp
52
+ sudo firewall-cmd --permanent --zone=public --add-port=10250/udp
51
53
 
52
54
  # kube-scheduler
53
55
  sudo firewall-cmd --permanent --zone=public --add-port=10259/tcp
File without changes
@@ -168,7 +168,7 @@ PermitEmptyPasswords no
168
168
  PasswordAuthentication ${SSHD_PASSWORD_AUTH}
169
169
  ChallengeResponseAuthentication ${SSHD_KBD_INTERACTIVE}
170
170
  KbdInteractiveAuthentication ${SSHD_KBD_INTERACTIVE}
171
- UsePAM no
171
+ UsePAM yes
172
172
  X11Forwarding no
173
173
  PrintMotd no
174
174
  TCPKeepAlive yes
@@ -585,7 +585,7 @@ fi
585
585
  post_status "installing" "\"detail\":\"installing packages\",\"disk\":\"\$REAL_DISK\""
586
586
 
587
587
  ilog "Installing base packages with dnf --installroot ..."
588
- PKGS="@core kernel grub2-tools openssh-server NetworkManager chrony dracut-config-generic rocky-release"
588
+ PKGS="@core kernel grub2-tools openssh-server NetworkManager chrony dracut-config-generic rocky-release policycoreutils policycoreutils-python-utils selinux-policy-targeted audit sudo"
589
589
  if [ "\$FW" = "uefi" ]; then
590
590
  PKGS="\$PKGS grub2-efi-x64 shim-x64 efibootmgr"
591
591
  else
@@ -622,15 +622,10 @@ ilog "Configuring installed system (hostname, network, ssh, users) ..."
622
622
  echo "\${TARGET_HOSTNAME:-rocky9}" > "\$TARGET_MNT/etc/hostname"
623
623
 
624
624
  chroot "\$TARGET_MNT" /bin/bash -s << CHROOTEOF >> "\$KS_LOG" 2>&1
625
- set +e
625
+ set -e
626
626
  systemctl enable sshd NetworkManager chronyd 2>/dev/null
627
627
 
628
- # SELinux -> permissive in the DEPLOYED OS. Critical: with SELinux enforcing on
629
- # first boot, files written from this installer chroot (authorized_keys, home
630
- # dirs) carry the wrong security context, so sshd drops sessions ("Connection
631
- # reset by peer") and PAM can block console logins. Permissive ignores contexts
632
- # (no relabel reboot needed) and matches what kubeadm/cluster.js expect.
633
- sed -i 's/^SELINUX=.*/SELINUX=permissive/' /etc/selinux/config 2>/dev/null || true
628
+ sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
634
629
 
635
630
  # Ensure sshd host keys exist so sshd actually starts on first boot.
636
631
  ssh-keygen -A 2>/dev/null || true
@@ -708,11 +703,19 @@ fi
708
703
  # Key-only SSH on the installed system.
709
704
  mkdir -p /etc/ssh/sshd_config.d
710
705
  cat > /etc/ssh/sshd_config.d/00-underpost.conf <<SSHDEOF
706
+ Port ${SSH_PORT}
711
707
  PermitRootLogin prohibit-password
712
708
  PubkeyAuthentication yes
713
709
  PasswordAuthentication no
710
+ KbdInteractiveAuthentication no
711
+ UsePAM yes
714
712
  SSHDEOF
715
713
 
714
+ if [ "${SSH_PORT}" != "22" ]; then
715
+ semanage port -a -t ssh_port_t -p tcp "${SSH_PORT}" 2>/dev/null || \
716
+ semanage port -m -t ssh_port_t -p tcp "${SSH_PORT}"
717
+ fi
718
+
716
719
  # Create the admin login accounts. create_user <name> writes the authorized key,
717
720
  # wheel membership and passwordless sudo. Passwords are applied after the chroot.
718
721
  create_user() {
@@ -738,11 +741,24 @@ chmod 600 /root/.ssh/authorized_keys
738
741
  create_user "\${ADMIN_USER}"
739
742
  create_user "\${DEPLOY_USER}"
740
743
 
744
+ restorecon -RF /etc/ssh /etc/sudoers.d /root/.ssh /home 2>/dev/null
745
+ touch /.autorelabel
746
+
741
747
  # Build initramfs for the installed kernel.
742
748
  KVER=\\\$(ls /lib/modules | head -1)
743
749
  [ -n "\\\$KVER" ] && dracut -f /boot/initramfs-\\\$KVER.img "\\\$KVER"
744
750
  CHROOTEOF
745
751
 
752
+ CHROOT_RC=\$?
753
+ if [ "\$CHROOT_RC" -ne 0 ]; then
754
+ ilog "FATAL: installed-system configuration failed (rc=\$CHROOT_RC)"
755
+ post_status "failed" "\"detail\":\"installed-system configuration rc=\$CHROOT_RC\""
756
+ for d in dev proc sys run; do umount -l "\$TARGET_MNT/\$d" 2>/dev/null; done
757
+ umount -R "\$TARGET_MNT" 2>/dev/null || true
758
+ rm -f "\$INSTALL_LOCK"
759
+ exit 1
760
+ fi
761
+
746
762
  # Set console passwords AFTER the chroot heredoc, from decoded installer vars.
747
763
  # Primary path: pipe 'user:password' into 'chroot chpasswd' (keeps password
748
764
  # characters out of any heredoc). Fallback: hash with openssl and apply via
@@ -24,7 +24,7 @@ MODE=runtime # runtime | instance
24
24
  ENV=development # development | production
25
25
  DEPLOY_ID=dd-test # deploy id (instance mode: parent of conf.instances.json)
26
26
  INSTANCE_IDS= # instance mode: csv of ids (default: all in conf.instances.json)
27
- IMAGE=underpost/wp:v3.2.90 # runtime mode image (instance mode reads image from conf)
27
+ IMAGE=underpost/wp:v3.3.0 # runtime mode image (instance mode reads image from conf)
28
28
  VERSIONS=green # csv of blue/green versions
29
29
  REPLICAS=1 # replicas per deployment
30
30
  NAMESPACE=default # k8s namespace
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Fallback-world default items — process-local override store.
3
+ *
4
+ * The procedural fallback world is never persisted, so its default item set
5
+ * has no collection to live in. This module holds the set the FallbackWorldEngine
6
+ * view composed, in memory, for the lifetime of the engine process:
7
+ *
8
+ * - written by POST /api/cyberia-instance/fallback-world/hot-reload
9
+ * - read by fetchFullInstance()'s fallback branch (instance-data.js) and
10
+ * GET /api/cyberia-instance/fallback-world[/default-items]
11
+ *
12
+ * Deliberately NOT persisted: an engine restart drops back to the code defaults,
13
+ * so a stale editor session can never become the permanent shape of the world.
14
+ * This keeps the fallback path's "code defaults are authoritative" invariant —
15
+ * the only thing an operator can layer on top is this explicit, volatile list.
16
+ *
17
+ * @module src/api/cyberia-instance/cyberia-fallback-default-items.js
18
+ */
19
+
20
+ /** @typedef {{ id: string, defaultPlayerInventory: boolean }} FallbackDefaultItem */
21
+
22
+ /** @type {FallbackDefaultItem[]} */
23
+ let defaultItems = [];
24
+
25
+ /**
26
+ * Coerce a caller-supplied list into the canonical `{ id, defaultPlayerInventory }`
27
+ * shape. Accepts the legacy `string[]` form and drops blank/duplicate ids so the
28
+ * world generator never sees a malformed entry.
29
+ *
30
+ * @param {Array<string|FallbackDefaultItem>} entries
31
+ * @returns {FallbackDefaultItem[]}
32
+ */
33
+ function normalizeFallbackDefaultItems(entries) {
34
+ if (!Array.isArray(entries)) return [];
35
+ const seen = new Set();
36
+ const normalized = [];
37
+ for (const entry of entries) {
38
+ const raw = typeof entry === 'string' ? { id: entry } : entry;
39
+ const id = typeof raw?.id === 'string' ? raw.id.trim() : '';
40
+ if (!id || seen.has(id)) continue;
41
+ seen.add(id);
42
+ normalized.push({ id, defaultPlayerInventory: !!raw.defaultPlayerInventory });
43
+ }
44
+ return normalized;
45
+ }
46
+
47
+ /**
48
+ * Current override set. Returns a copy so callers cannot mutate the store.
49
+ * @returns {FallbackDefaultItem[]}
50
+ */
51
+ const getFallbackDefaultItems = () => defaultItems.map((entry) => ({ ...entry }));
52
+
53
+ /**
54
+ * Replace the override set. An empty list clears it, restoring pure code defaults.
55
+ * @param {Array<string|FallbackDefaultItem>} entries
56
+ * @returns {FallbackDefaultItem[]} The stored set.
57
+ */
58
+ function setFallbackDefaultItems(entries) {
59
+ defaultItems = normalizeFallbackDefaultItems(entries);
60
+ return getFallbackDefaultItems();
61
+ }
62
+
63
+ export { getFallbackDefaultItems, setFallbackDefaultItems, normalizeFallbackDefaultItems };
@@ -179,7 +179,7 @@ function generateFallbackMap(mapCode, colors, opts = {}) {
179
179
  const portalEntities = generatePortalEntities(mapDims, colors, { grid });
180
180
 
181
181
  // 6. Bots — placed on walkable cells (avoids obstacles, portals, NPCs)
182
- const bots = generateBots(mapDims, colors, { count: opts.botCount, grid });
182
+ const bots = mapCode === 'fallback-map-0' ? [] : generateBots(mapDims, colors, { count: opts.botCount, grid });
183
183
 
184
184
  // 7. Resources — static exploitable entities placed on walkable cells
185
185
  const resources = generateResources(mapDims, colors, { count: opts.resourceCount, grid });
@@ -232,6 +232,11 @@ function generateFallbackMap(mapCode, colors, opts = {}) {
232
232
  * @param {number} [opts.resourceCount] Resources per map (random if omitted).
233
233
  * @param {number} [opts.staticCount] Static decorators per map (random if omitted).
234
234
  * @param {Array} [opts.colors] Override palette.
235
+ * @param {Array<{id: string, defaultPlayerInventory: boolean}>} [opts.itemIds=[]]
236
+ * Instance-level default items, mirroring `CyberiaInstance.itemIds`. Entries
237
+ * flagged `defaultPlayerInventory` are merged into the player entity default
238
+ * by `applyInstanceDefaultPlayerInventory`; every id is also resolved to an
239
+ * atlas at boot. Layout is unaffected, so this stays outside the world seed.
235
240
  * @returns {{
236
241
  * instance: object,
237
242
  * maps: object[],
@@ -256,6 +261,7 @@ function generateFallbackWorld(opts = {}) {
256
261
  // fallback before atlases load. The C client resolves real colours
257
262
  // through domain/presentation_runtime — it does not read this value.
258
263
  colors = PALETTE,
264
+ itemIds = [],
259
265
  } = opts;
260
266
 
261
267
  // Surface item-id drift loudly on the very first build, so a missing
@@ -314,6 +320,7 @@ function generateFallbackWorld(opts = {}) {
314
320
  portals,
315
321
  topologyMode: 'procedural',
316
322
  playerSpawn: { ...DEFAULT_PLAYER_SPAWN },
323
+ itemIds: itemIds.map((entry) => ({ ...entry })),
317
324
  };
318
325
 
319
326
  return {
@@ -30,6 +30,9 @@ const CyberiaInstanceController = buildCrudController(CyberiaInstanceService, {
30
30
  portalConnect: serviceHandler(CyberiaInstanceService.portalConnect),
31
31
  // Moderator/admin-triggered world reload on a running cyberia-server.
32
32
  hotReload: serviceHandler(CyberiaInstanceService.hotReload, { errorStatus: 502 }),
33
+ // Fallback-world default items (volatile) + its own reload trigger.
34
+ fallbackDefaultItems: serviceHandler(CyberiaInstanceService.fallbackDefaultItems),
35
+ fallbackHotReload: serviceHandler(CyberiaInstanceService.fallbackHotReload, { errorStatus: 502 }),
33
36
  // Boot transport (REST fallback of the gRPC CyberiaDataService).
34
37
  bootPing: serviceHandler(CyberiaInstanceBootService.ping),
35
38
  bootObjectLayerBatch: serviceHandler(CyberiaInstanceBootService.objectLayerBatch, { errorStatus: 500 }),
@@ -38,6 +38,19 @@ class CyberiaInstanceRouter {
38
38
  async (req, res) => await CyberiaInstanceController.bootFullInstance(req, res, options),
39
39
  );
40
40
  router.get(`/fallback-world`, async (req, res) => await CyberiaInstanceController.fallbackWorld(req, res, options));
41
+ // Fallback-world default items — volatile, process-local (never persisted).
42
+ // The read is open (same as /fallback-world); staging them and triggering a
43
+ // rebuild is elevated privilege, like every other hot-reload path.
44
+ router.get(
45
+ `/fallback-world/default-items`,
46
+ async (req, res) => await CyberiaInstanceController.fallbackDefaultItems(req, res, options),
47
+ );
48
+ router.post(
49
+ `/fallback-world/hot-reload`,
50
+ options.authMiddleware,
51
+ moderatorGuard,
52
+ async (req, res) => await CyberiaInstanceController.fallbackHotReload(req, res, options),
53
+ );
41
54
  // Instance Map — static topology/presence plus dynamic player capability activity.
42
55
  router.get(
43
56
  `/instance-map/:instanceCode/static`,
@@ -7,6 +7,8 @@ import {
7
7
  fallbackListInstance,
8
8
  DEFAULT_FALLBACK_INSTANCE_CODE,
9
9
  } from './cyberia-fallback-world.js';
10
+ import { getFallbackDefaultItems, setFallbackDefaultItems } from './cyberia-fallback-default-items.js';
11
+ import { CYBERIA_INSTANCE_CONF_DEFAULTS } from '../cyberia-server-defaults/cyberia-server-defaults.js';
10
12
  import { triggerHotReload } from '../../projects/cyberia/hot-reload-trigger.js';
11
13
 
12
14
  const logger = loggerFactory(import.meta);
@@ -183,8 +185,49 @@ class CyberiaInstanceService {
183
185
  botCount: q.botCount ? parseInt(q.botCount, 10) : undefined,
184
186
  obstacleCount: q.obstacleCount ? parseInt(q.obstacleCount, 10) : undefined,
185
187
  foregroundCount: q.foregroundCount ? parseInt(q.foregroundCount, 10) : undefined,
188
+ itemIds: getFallbackDefaultItems(),
186
189
  });
187
190
  };
191
+
192
+ /**
193
+ * Current fallback-world default items — the volatile set the
194
+ * FallbackWorldEngine view last pushed, or `[]` when the engine is running on
195
+ * pure code defaults. Lets the view reopen showing what this process serves.
196
+ *
197
+ * `entityDefaultItemIds` are the ids the code defaults already ship in an
198
+ * entity's `defaultObjectLayers`. Selecting one of them and leaving
199
+ * `defaultPlayerInventory` unchecked REMOVES it (see
200
+ * applyInstanceDefaultPlayerInventory), so the editor needs to be able to warn
201
+ * before an operator silently strips the player's starting skin or weapon.
202
+ */
203
+ static fallbackDefaultItems = async () => {
204
+ const entityDefaultItemIds = new Set();
205
+ for (const entityDefault of CYBERIA_INSTANCE_CONF_DEFAULTS.entityDefaults || []) {
206
+ for (const layer of entityDefault.defaultObjectLayers || []) {
207
+ if (layer.itemId) entityDefaultItemIds.add(layer.itemId);
208
+ }
209
+ }
210
+ return { itemIds: getFallbackDefaultItems(), entityDefaultItemIds: [...entityDefaultItemIds].sort() };
211
+ };
212
+
213
+ /**
214
+ * Stage the fallback world's default items, then ask a running cyberia-server
215
+ * to rebuild. The items are not persisted (see cyberia-fallback-default-items):
216
+ * they are staged here first so the rebuild's `getFullInstance` re-fetch picks
217
+ * them up, which is what makes them reach the server at all.
218
+ *
219
+ * `instanceCode` is intentionally left empty — the target world is addressed by
220
+ * `serverUrl` (origin, or origin + variant sub-path), and naming the synthetic
221
+ * `fallback` code would be rejected by any server serving a real instance code.
222
+ */
223
+ static fallbackHotReload = async (req) => {
224
+ const { serverUrl, mode, itemIds } = req.body || {};
225
+ if (!serverUrl) throw new Error('serverUrl is required');
226
+
227
+ const staged = setFallbackDefaultItems(itemIds || []);
228
+ const { transport, result, grpcError } = await triggerHotReload({ serverUrl, instanceCode: '', mode });
229
+ return { transport, itemIds: staged, grpcError, ...result };
230
+ };
188
231
  }
189
232
 
190
233
  export { CyberiaInstanceService };
@@ -148,6 +148,7 @@ const CyberiaInstanceConfSchema = new Schema(
148
148
  // ── Player defaults ──────────────────────────────────────────────
149
149
  defaultPlayerWidth: { type: Number, default: D.defaultPlayerWidth },
150
150
  defaultPlayerHeight: { type: Number, default: D.defaultPlayerHeight },
151
+ playerBaseSpeed: { type: Number, default: D.playerBaseSpeed },
151
152
  playerBaseLifeRegenMin: { type: Number, default: D.playerBaseLifeRegenMin },
152
153
  playerBaseLifeRegenMax: { type: Number, default: D.playerBaseLifeRegenMax },
153
154
  sumStatsLimit: { type: Number, default: D.sumStatsLimit },