Skip to content

Commit 775cf26

Browse files
committed
Merge remote-tracking branch 'origin/main' into sdn-server-refactor-ui
# Conflicts: # kubo/plugin/plugins/sdnruntime/sdnruntime.go # kubo/plugin/plugins/sdnruntime/sdnruntime_test.go # kubo/sdn/build/release/Dockerfile.linux # kubo/sdn/build/release/build-release.sh # kubo/sdn/flowrt/abi_test.go # kubo/sdn/flowrt/flatsql_query_test.go # kubo/sdn/flowrt/flatsql_store_link.go # kubo/sdn/flowrt/flatsql_store_link_test.go # kubo/sdn/flowrt/runtime.go # kubo/sdn/modulert/hostbridge.go # kubo/sdn/modulert/hostbridge_test.go # kubo/sdn/modulert/module.go # kubo/sdn/modulert/publication_signature.go # kubo/sdn/modulert/publication_signature_test.go # kubo/sdn/sdnflows/installer.go # kubo/sdn/sdnflows/isomorphic_installer_test.go # kubo/sdn/sdnservices/opaque_state.go # kubo/sdn/sdnservices/opaque_state_test.go # kubo/sdn/sdnservices/sdnservices.go # sdn-server/cmd/spacedatanetwork/apps_ui.go # sdn-server/cmd/spacedatanetwork/apps_ui_test.go # sdn-server/cmd/spacedatanetwork/conjunction_ui.go # sdn-server/cmd/spacedatanetwork/conjunction_ui_test.go # sdn-server/cmd/spacedatanetwork/embedded/conjunction_app.html # sdn-server/cmd/spacedatanetwork/embedded/spaceaware_app.html # sdn-server/cmd/spacedatanetwork/main.go
2 parents 459eedd + 68034b9 commit 775cf26

123 files changed

Lines changed: 7216 additions & 10995 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,23 @@ Space Data Network enables real-time sharing of space situational awareness data
3636

3737
## Current UI Surfaces
3838

39-
- `/` serves the **conjunction screening app** — the shipped, browser-first SDN
40-
UI. It is a single self-contained artifact (all JS/CSS/fonts inlined,
41-
no wasm) embedded in the daemon and served from memory. It runs anonymously
42-
against public read endpoints (`/api/v1/{peers,channels,stats}`,
43-
`/api/v1/data/health`) — no login is required or offered.
39+
- `/` on a Kubo-integrated or production SDN node serves the **SDN Node
40+
Console** from `kubo/sdn/sdnui/assets`. Its rail exposes Node, Peers, Data,
41+
Channels, Apps, and Modules, backed by the node's `/sdn/v1` API.
42+
- `/apps/` on the standalone service is the installed-app launcher, with
43+
application pages such as `/apps/conjunction/`. Kubo also exposes installed
44+
application records and pages below `/sdn/v1/apps/`.
4445
- `/login` is the **legacy wallet-bootstrap surface** (wallet creation and
45-
first-admin bootstrap for operators). The conjunction app never links to it;
46-
it is the daemon's own wallet-gated page.
46+
first-admin bootstrap for operators).
4747
- `/webui` is the upstream-style IPFS WebUI (unchanged).
4848
- `/admin` is reserved for admin and auth flows.
4949

50-
The shipped UI is conjunction-only (owner directive 2026-07-11). The full
51-
SpaceAware app (login screen, console, BMC2 boards, orbital, gantt) stays in the
52-
source tree, dormant, behind a dev switch: set `SDN_UI_MODE=spaceaware` to serve
53-
that route skeleton for local development. With the switch unset (the shipped
54-
default), the daemon serves the conjunction app at `/` and returns 404 for the
55-
descoped SpaceAware screens.
50+
The standalone `sdn-server` listener can still serve the embedded conjunction
51+
application at its own `/` route. Production reverse-proxy configuration must
52+
not use that sidecar root as a replacement for the Kubo SDN Node Console:
53+
normal public `/`, `/index.html`, and the console's exact assets route to the
54+
Kubo console, while `/apps/`, wallet, and application API fallbacks remain on
55+
the standalone service.
5656

5757
The SDN browser path uses `sdn-js` plus the generic async capability surfaces from `space-data-module-sdk` and the existing `hd-wallet-wasm` and `hd-wallet-ui` identity stack. It uses direct SDN APIs and browser-safe package exports without a helper service.
5858

deployment/scripts/deploy.sh

Lines changed: 94 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Usage: $0 [OPTIONS] COMMAND [TYPE]
2828
2929
Commands:
3030
deploy Deploy to servers (optionally specify type: full, edge, registry)
31+
cutover-spaceaware
32+
Route spaceaware.io to an already healthy SpaceAware release
3133
status Check status of all servers
3234
logs Fetch logs from servers
3335
stop Stop services on servers
@@ -209,40 +211,67 @@ EOF"
209211
fi
210212
}
211213

212-
configure_spaceaware_public_wss_proxy() {
214+
configure_spaceaware_public_host_route() {
213215
local ip=$1
214-
local config_dir=$2
215216

216-
if [[ "$config_dir" != "/etc/space-data-network" ]]; then
217+
if ! is_spaceaware_config; then
217218
return
218219
fi
219220

220-
ssh_cmd "$ip" "if [ -f /etc/nginx/sites-enabled/spaceaware ]; then
221-
python3 - <<'PY'
222-
from pathlib import Path
221+
ssh_cmd "$ip" "mkdir -p /opt/spacedatanetwork/deployment/spaceaware"
222+
scp_cmd "${DEPLOY_DIR}/spaceaware/install-public-host-route.mjs" "$ip" "/opt/spacedatanetwork/deployment/spaceaware/install-public-host-route.mjs"
223+
scp_cmd "${DEPLOY_DIR}/spaceaware/install-spaceaware-public-host-route.mjs" "$ip" "/opt/spacedatanetwork/deployment/spaceaware/install-spaceaware-public-host-route.mjs"
224+
scp_cmd "${DEPLOY_DIR}/spaceaware/verify-spaceaware-public-host-route.mjs" "$ip" "/opt/spacedatanetwork/deployment/spaceaware/verify-spaceaware-public-host-route.mjs"
225+
ssh_cmd "$ip" "set -euo pipefail
226+
chown root:root \
227+
/opt/spacedatanetwork/deployment/spaceaware/install-public-host-route.mjs \
228+
/opt/spacedatanetwork/deployment/spaceaware/install-spaceaware-public-host-route.mjs \
229+
/opt/spacedatanetwork/deployment/spaceaware/verify-spaceaware-public-host-route.mjs
230+
chmod 0755 \
231+
/opt/spacedatanetwork/deployment/spaceaware/install-public-host-route.mjs \
232+
/opt/spacedatanetwork/deployment/spaceaware/install-spaceaware-public-host-route.mjs \
233+
/opt/spacedatanetwork/deployment/spaceaware/verify-spaceaware-public-host-route.mjs
234+
systemctl cat space-data-network-module-delivery.service >/dev/null
235+
systemctl restart space-data-network-module-delivery.service
236+
systemctl is-active --quiet space-data-network-module-delivery.service
237+
sidecar_ready=false
238+
for attempt in \$(seq 1 30); do
239+
if curl -kfsS --connect-timeout 5 --max-time 15 https://127.0.0.1:18443/ | grep -F 'sdn-node-console-v1' >/dev/null \
240+
&& curl -kfsS --connect-timeout 5 --max-time 15 https://127.0.0.1:18443/ | grep -F '2.0.28' >/dev/null \
241+
&& curl -kfsS --connect-timeout 5 --max-time 15 https://127.0.0.1:18443/wallet/callback | grep -F 'Completing wallet connection' >/dev/null \
242+
&& curl -kfsS --connect-timeout 5 --max-time 15 https://127.0.0.1:18443/api/module-delivery/provider >/dev/null; then
243+
sidecar_ready=true
244+
break
245+
fi
246+
sleep 1
247+
done
248+
test "\$sidecar_ready" = true
249+
node /opt/spacedatanetwork/deployment/spaceaware/install-public-host-route.mjs \
250+
--verify-script /opt/spacedatanetwork/deployment/spaceaware/verify-spaceaware-public-host-route.mjs"
251+
}
223252

224-
path = Path('/etc/nginx/sites-enabled/spaceaware')
225-
text = path.read_text()
226-
next_text = text.replace(
227-
'server_name spaceaware.io www.spaceaware.io;',
228-
'server_name spaceaware.io www.spaceaware.io sdn.spaceaware.io;',
229-
)
230-
next_text = next_text.replace(
231-
'proxy_pass http://127.0.0.1:18080;',
232-
'proxy_pass http://127.0.0.1:8080;',
233-
)
234-
if next_text != text:
235-
backup = path.with_suffix(path.suffix + '.pre-sdn-wss-route')
236-
if not backup.exists():
237-
backup.write_text(text)
238-
path.write_text(next_text)
239-
PY
240-
nginx -t
241-
systemctl reload nginx
242-
fi
243-
if systemctl cat space-data-network-module-delivery.service >/dev/null 2>&1; then
244-
systemctl restart space-data-network-module-delivery.service || true
245-
fi"
253+
cutover_spaceaware_public_host_route() {
254+
local ip=$1
255+
256+
if ! is_spaceaware_config; then
257+
log_error "SpaceAware public-host cutover requires deployment/spaceaware/servers.yaml."
258+
return 1
259+
fi
260+
261+
ssh_cmd "$ip" "mkdir -p /opt/spacedatanetwork/deployment/spaceaware"
262+
scp_cmd "${DEPLOY_DIR}/spaceaware/install-spaceaware-public-host-route.mjs" "$ip" "/opt/spacedatanetwork/deployment/spaceaware/install-spaceaware-public-host-route.mjs"
263+
scp_cmd "${DEPLOY_DIR}/spaceaware/verify-spaceaware-public-host-route.mjs" "$ip" "/opt/spacedatanetwork/deployment/spaceaware/verify-spaceaware-public-host-route.mjs"
264+
scp_cmd "${DEPLOY_DIR}/spaceaware/cutover-spaceaware-public-host-route.sh" "$ip" "/opt/spacedatanetwork/deployment/spaceaware/cutover-spaceaware-public-host-route.sh"
265+
ssh_cmd "$ip" "set -euo pipefail
266+
chown root:root \
267+
/opt/spacedatanetwork/deployment/spaceaware/install-spaceaware-public-host-route.mjs \
268+
/opt/spacedatanetwork/deployment/spaceaware/verify-spaceaware-public-host-route.mjs \
269+
/opt/spacedatanetwork/deployment/spaceaware/cutover-spaceaware-public-host-route.sh
270+
chmod 0755 \
271+
/opt/spacedatanetwork/deployment/spaceaware/install-spaceaware-public-host-route.mjs \
272+
/opt/spacedatanetwork/deployment/spaceaware/verify-spaceaware-public-host-route.mjs \
273+
/opt/spacedatanetwork/deployment/spaceaware/cutover-spaceaware-public-host-route.sh
274+
/opt/spacedatanetwork/deployment/spaceaware/cutover-spaceaware-public-host-route.sh"
246275
}
247276

248277
prepare_full_node_assets() {
@@ -483,7 +512,7 @@ deploy_binary() {
483512

484513
ssh_cmd "$ip" "chmod 755 ${config_dir} && chown root:root ${config_dir}/config.yaml && chmod 644 ${config_dir}/config.yaml && chmod +x /opt/spacedatanetwork/scripts/install-wasmedge.sh /opt/spacedatanetwork/scripts/go-with-wasmedge.sh && WASMEDGE_DIR=/opt/spacedatanetwork/.wasmedge /opt/spacedatanetwork/scripts/go-with-wasmedge.sh build -o /opt/spacedatanetwork/bin/spacedatanetwork ./cmd/spacedatanetwork && chown -R sdn:sdn /opt/spacedatanetwork /var/lib/spacedatanetwork${spaceaware_migration_hardening}"
485514
ssh_cmd "$ip" "systemctl daemon-reload && systemctl enable ${full_service} && systemctl restart ${full_service} && if [ '${full_service}' = 'space-data-network' ]; then systemctl disable --now spacedatanetwork >/dev/null 2>&1 || true; fi"
486-
configure_spaceaware_public_wss_proxy "$ip" "$config_dir"
515+
configure_spaceaware_public_host_route "$ip"
487516

488517
log_success "Deployed full node bundle to $ip"
489518
return
@@ -596,6 +625,34 @@ do_deploy() {
596625
done
597626
}
598627

628+
do_spaceaware_cutover() {
629+
local endpoint
630+
local found="false"
631+
632+
if ! is_spaceaware_config; then
633+
log_error "SpaceAware public-host cutover requires deployment/spaceaware/servers.yaml."
634+
return 1
635+
fi
636+
637+
while IFS= read -r line; do
638+
endpoint="$(parse_server_endpoint "$line")"
639+
if [[ -z "$endpoint" ]]; then
640+
continue
641+
fi
642+
found="true"
643+
if [[ "$DRY_RUN" == "true" ]]; then
644+
log_info "[DRY-RUN] Would cut spaceaware.io over on $endpoint after loopback readiness checks"
645+
else
646+
cutover_spaceaware_public_host_route "$endpoint"
647+
fi
648+
done < <(sed -n "/^full_nodes:/,/^[a-z]/p" "$CONFIG_FILE")
649+
650+
if [[ "$found" != "true" ]]; then
651+
log_error "No full node target found for SpaceAware public-host cutover."
652+
return 1
653+
fi
654+
}
655+
599656
# Parse arguments
600657
SSH_KEY="${HOME}/.ssh/sdn_deploy_key"
601658
SSH_USER="root"
@@ -613,7 +670,7 @@ while [[ $# -gt 0 ]]; do
613670
-b|--binary) USE_DOCKER="false"; shift ;;
614671
-n|--dry-run) DRY_RUN="true"; shift ;;
615672
-h|--help) usage ;;
616-
deploy|status|logs|stop|restart)
673+
deploy|status|logs|stop|restart|cutover-spaceaware)
617674
COMMAND="$1"
618675
TYPE="${2:-}"
619676
shift
@@ -630,6 +687,13 @@ case $COMMAND in
630687
deploy)
631688
do_deploy "$TYPE"
632689
;;
690+
cutover-spaceaware)
691+
if [[ -n "$TYPE" ]]; then
692+
log_error "cutover-spaceaware does not accept a server type."
693+
exit 1
694+
fi
695+
do_spaceaware_cutover
696+
;;
633697
status)
634698
log_info "Checking server status..."
635699
for t in full edge registry; do

deployment/spaceaware/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,66 @@ The deployment config uses `host: sdn.spaceaware.io` on purpose. Keep the real
77
address and key material in `~/.ssh/config`; do not commit machine-specific SSH
88
paths or IP addresses here.
99

10+
The production edge terminates TLS for `spaceaware.io`, `www.spaceaware.io`,
11+
and `sdn.spaceaware.io` in Nginx. The first-stage
12+
`install-public-host-route.mjs` keeps SpaceAware on the local Kubo gateway at
13+
port 5020. For the SDN host, normal `/`, `/index.html`, and the exact SDN Node
14+
Console assets route to port 5020; WebSocket upgrades use port 18080. The
15+
installed-app launcher, wallet, module-delivery APIs, and other SDN fallbacks
16+
remain on the SDN HTTPS sidecar at port 18443.
17+
18+
After a signed SpaceAware external-proxy release has been installed, the
19+
separate `cutover-spaceaware` command splits the shared TLS server into two
20+
host-specific server blocks. SpaceAware and www then use the SpaceAware SDN
21+
HTTP service at `127.0.0.1:5010`, WebSocket upgrades on `/` and `/p2p/*` use
22+
`127.0.0.1:8080`, and terrain paths use `127.0.0.1:8081`. The SDN host retains
23+
the 5020 Node Console, 18443 application sidecar, and 18080 WebSocket split.
24+
The private `/asset-ipfs/` route and immutable CID cache behavior are retained
25+
on both hosts.
26+
27+
Both installers apply their change atomically, require a reviewed source
28+
shape, validate Nginx before reload, and restore the original config if
29+
validation, reload, or transactional public-route verification fails. Durable
30+
backups go under
31+
`/var/backups/spacedatanetwork/nginx`, outside Nginx's `sites-enabled/*`
32+
include. A retry validates and reloads even when the desired file is already
33+
installed, so an interruption between replacement and reload is recoverable.
34+
The installers serialize through `/run/sdn-public-host-route.lock`, recheck the
35+
config immediately around replacement and reload, and never roll back over a
36+
concurrent operator edit.
37+
The final cutover is pinned to the reviewed production source digest and its
38+
deterministic canonical split digest; any manual byte drift fails closed for
39+
operator review instead of being partially transformed.
40+
41+
Both deployment paths run `verify-spaceaware-public-host-route.mjs` after
42+
reload while the original file is still available for rollback. The normal SDN
43+
deploy uses its shared `sdn-public` contract; the final SpaceAware cutover uses
44+
the complete two-host contract. The verifier connects directly to
45+
`127.0.0.1:443` with the production SNI and Host values, bypassing external
46+
DNS/CDN state. It requires the public SpaceAware release identity and callback
47+
bytes to match `/opt/spaceaware/current/web`, validates callback method and
48+
security headers, parses the health and provider JSON contracts, checks both
49+
terrain prefixes, compares each public provider key and peer ID with its direct
50+
5010/18443 backend, compares the public SDN console HTML and all exact console
51+
assets byte-for-byte with the direct 5020 backend, confirms `/apps/` still
52+
matches the 18443 sidecar, and completes RFC 6455 handshakes through both public
53+
hosts (including the SDN root and `/p2p/*`). Any failure restores, validates,
54+
and reloads the pre-cutover Nginx file.
55+
56+
The normal binary deploy invokes only the first-stage SDN installer, and only
57+
when the exact `deployment/spaceaware/servers.yaml` target is selected. The
58+
SpaceAware cutover is an explicit second command. It refuses to run unless all
59+
four SpaceAware units are loaded and active and direct loopback probes for
60+
ports 5010, 8080, and 8081 pass. The loopback gate requires the exact activated
61+
release identity, structured health/provider responses, and a valid WebSocket
62+
handshake. It also requires `wallet.spacedatanetwork.org` to serve its
63+
content-addressed 2.0.28 login/account JavaScript, CSS, and WASM, validating
64+
the entrypoint SRI and every filename digest. It separately fetches all three
65+
activated 2.0.28 wallet assets from `static.spacedatanetwork.org`, accepting
66+
them only when their SHA-384 bytes match the SRI values in the activated
67+
callback and OrbPro HTML. Accepting any arbitrary HTTP status is not
68+
sufficient. It does not restart those services.
69+
1070
From the `space-data-network` repository root:
1171

1272
```bash
@@ -17,6 +77,21 @@ From the `space-data-network` repository root:
1777
deploy full
1878
```
1979

80+
Once the SpaceAware release is active in external-proxy mode, cut its public
81+
hosts over without redeploying or restarting either application:
82+
83+
```bash
84+
./deployment/scripts/deploy.sh \
85+
-c deployment/spaceaware/servers.yaml \
86+
-u root \
87+
cutover-spaceaware
88+
```
89+
90+
Use `-n` first to confirm the exact target. The cutover command installs the
91+
reviewed readiness wrapper and route installer under
92+
`/opt/spacedatanetwork/deployment/spaceaware/`, together with the shared
93+
verifier; no route file is changed until all readiness checks pass.
94+
2095
Before deploying from a development workstation, make sure the local desktop
2196
tool is running so the bundled desktop/Kubo integration is healthy in the same
2297
checkout:
@@ -40,6 +115,8 @@ production service after deploy:
40115
-b \
41116
status
42117
curl -fsS https://sdn.spaceaware.io/ >/dev/null
118+
curl -fsS https://sdn.spaceaware.io/app.js >/dev/null
119+
curl -fsS https://sdn.spaceaware.io/apps/ >/dev/null
43120
curl -fsS https://sdn.spaceaware.io/webui/ >/dev/null
44121
```
45122

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
installer_path="${SPACEAWARE_ROUTE_INSTALLER_PATH:-/opt/spacedatanetwork/deployment/spaceaware/install-spaceaware-public-host-route.mjs}"
6+
verifier_path="${SPACEAWARE_ROUTE_VERIFIER_PATH:-/opt/spacedatanetwork/deployment/spaceaware/verify-spaceaware-public-host-route.mjs}"
7+
max_attempts="${SPACEAWARE_CUTOVER_MAX_ATTEMPTS:-30}"
8+
9+
if [[ ! "${max_attempts}" =~ ^[1-9][0-9]*$ ]]; then
10+
echo "SPACEAWARE_CUTOVER_MAX_ATTEMPTS must be a positive integer." >&2
11+
exit 1
12+
fi
13+
if [[ ! -f "${installer_path}" || -L "${installer_path}" ]]; then
14+
echo "SpaceAware route installer must be a regular file: ${installer_path}" >&2
15+
exit 1
16+
fi
17+
if [[ ! -f "${verifier_path}" || -L "${verifier_path}" ]]; then
18+
echo "SpaceAware route verifier must be a regular file: ${verifier_path}" >&2
19+
exit 1
20+
fi
21+
22+
required_units=(
23+
spaceaware-sdn.service
24+
spaceaware-ipfs.service
25+
spaceaware-ingest.service
26+
spaceaware-terrain-cache.service
27+
)
28+
29+
for unit in "${required_units[@]}"; do
30+
load_state="$(systemctl show --property=LoadState --value "${unit}")"
31+
if [[ "${load_state}" != "loaded" ]]; then
32+
echo "Required SpaceAware unit is not loaded: ${unit} (${load_state:-unknown})" >&2
33+
exit 1
34+
fi
35+
if ! systemctl is-active --quiet "${unit}"; then
36+
echo "Required SpaceAware unit is not active: ${unit}" >&2
37+
exit 1
38+
fi
39+
done
40+
41+
spaceaware_ready=false
42+
for ((attempt = 1; attempt <= max_attempts; attempt += 1)); do
43+
if node "${verifier_path}" --mode loopback; then
44+
spaceaware_ready=true
45+
break
46+
fi
47+
if (( attempt < max_attempts )); then
48+
sleep 1
49+
fi
50+
done
51+
if [[ "${spaceaware_ready}" != true ]]; then
52+
echo "SpaceAware loopback endpoints did not become ready after ${max_attempts} attempts." >&2
53+
exit 1
54+
fi
55+
56+
node "${installer_path}" --verify-script "${verifier_path}"

0 commit comments

Comments
 (0)