From feb32a1456ef3cfb398398dd14875767e4bc8944 Mon Sep 17 00:00:00 2001 From: spacesops Date: Tue, 23 Jun 2026 12:07:38 -0400 Subject: [PATCH] Upgrade to SpacesOps v1.0.1 and fix v1.0.1:1 startup on StartOS. Bump the image to spacesops/spacesops:v1.0.1, always inject NODE_EXTRA_CA_CERTS from sdk.getSslCertificate so the upstream entrypoint no longer crashes on unset _SCRIPT_DIR, and document the same-host HTTPS behavior in the README. Co-authored-by: Cursor --- README.md | 15 ++++++--- assets/spaceswallet.svg | 64 ++++++++++++++++++++++++++++++++++++ icon.svg | 16 ++++----- spacesops.svg | 14 ++++---- spaceswallet.svg | 64 ++++++++++++++++++++++++++++++++++++ startos/main.ts | 22 +++++++++++++ startos/manifest/index.ts | 2 +- startos/utils.ts | 25 ++++++++++++-- startos/versions/index.ts | 7 ++-- startos/versions/v1.0.0.0.ts | 17 ---------- startos/versions/v1.0.1.0.ts | 14 ++++++++ startos/versions/v1.0.1.1.ts | 13 ++++++++ 12 files changed, 230 insertions(+), 43 deletions(-) create mode 100644 assets/spaceswallet.svg create mode 100644 spaceswallet.svg delete mode 100644 startos/versions/v1.0.0.0.ts create mode 100644 startos/versions/v1.0.1.0.ts create mode 100644 startos/versions/v1.0.1.1.ts diff --git a/README.md b/README.md index e666c2d..5b8c68b 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ auto-connects to it — see [Installation and First-Run Flow](#installation-and- | Field | Value | | --- | --- | -| Image | `docker.io/spacesops/spacesops:v1.0.0` | +| Image | `docker.io/spacesops/spacesops:v1.0.1` | | Base | `node:20-alpine` | | Architectures | `x86_64` (linux/amd64), `aarch64` (linux/arm64) | | Entrypoint | The image's `/app/docker-entrypoint.sh` is **kept**. StartOS runs `/app/docker-entrypoint.sh node server.js` as the daemon. | @@ -139,7 +139,7 @@ volume is **not** part of this package's backup. and sync Spaces before (or alongside) SpacesOps; the service restarts until those credentials are available. 2. **The admin password is fixed and well-known.** Admin Basic Auth is hardcoded - in the v1.0.0 image as `admin` / `Whatever!` with **no environment override**. + in the v1.0.1 image as `admin` / `Whatever!` with **no environment override**. It cannot be changed without rebuilding the image. **Keep this service private (Tor-only) and never expose its admin routes to the public internet** — the admin area can run SQL and manage tenants. See **Show Admin Credentials**. @@ -151,7 +151,11 @@ volume is **not** part of this package's backup. 5. **The SUBS backend is not auto-wired to the Spaces subs daemon in v1.** The Spaces service's subs daemon listens on 7777 with a different API/auth than the SUBS endpoint (port 7244) SpacesOps expects, so SUBS must be configured - manually if used. + manually if used. StartOS always sets `NODE_EXTRA_CA_CERTS` to the StartOS + root CA (fetched via `sdk.getSslCertificate` for the SUBS `*.startos` + hostname when configured, otherwise `spaces.startos`) so outbound HTTPS to + same-host StartOS services succeeds and the v1.0.1 image entrypoint does not + crash on its broken `_SCRIPT_DIR` default. ## What Is Unchanged from Upstream @@ -166,8 +170,8 @@ volume is **not** part of this package's backup. ```yaml package_id: spacesops -upstream_version: 1.0.0 -image: docker.io/spacesops/spacesops:v1.0.0 +upstream_version: 1.0.1 +image: docker.io/spacesops/spacesops:v1.0.1 architectures: [x86_64, aarch64] volumes: main: /data @@ -196,6 +200,7 @@ optional_env_vars: # only set when configured via configure-platform - SUBS_URI_VALUE - SUBS_RPC_USER - SUBS_RPC_PASSWORD + - NODE_EXTRA_CA_CERTS # /data/.startos/startos-root-ca.crt (always set) admin_credentials: username: admin password: Whatever! # FIXED, baked into image, cannot be changed diff --git a/assets/spaceswallet.svg b/assets/spaceswallet.svg new file mode 100644 index 0000000..7df054a --- /dev/null +++ b/assets/spaceswallet.svg @@ -0,0 +1,64 @@ + + + + + + + + @ + SpacesWallet + + diff --git a/icon.svg b/icon.svg index 3a710e9..895ff4b 100644 --- a/icon.svg +++ b/icon.svg @@ -8,7 +8,7 @@ version="1.1" id="svg1" inkscape:version="1.3.2 (091e20e, 2023-11-25)" - sodipodi:docname="spacesops.svg" + sodipodi:docname="icon.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" @@ -25,7 +25,7 @@ inkscape:document-units="mm" inkscape:zoom="0.5" inkscape:cx="365" - inkscape:cy="260" + inkscape:cy="452" inkscape:window-width="1312" inkscape:window-height="995" inkscape:window-x="550" @@ -40,14 +40,14 @@ id="layer1"> @ + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:108.156px;font-family:Outfit;-inkscape-font-specification:'Outfit, @wght=400';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-variation-settings:'wght' 400;fill:#903369;fill-opacity:1;stroke:none;stroke-width:15.3291" + x="10.27656" + y="75.009308">@ diff --git a/spacesops.svg b/spacesops.svg index 3a710e9..b51d91c 100644 --- a/spacesops.svg +++ b/spacesops.svg @@ -25,7 +25,7 @@ inkscape:document-units="mm" inkscape:zoom="0.5" inkscape:cx="365" - inkscape:cy="260" + inkscape:cy="452" inkscape:window-width="1312" inkscape:window-height="995" inkscape:window-x="550" @@ -40,14 +40,14 @@ id="layer1"> @ + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:108.156px;font-family:Outfit;-inkscape-font-specification:'Outfit, @wght=400';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;font-variation-settings:'wght' 400;fill:#903369;fill-opacity:1;stroke:none;stroke-width:15.3291" + x="10.27656" + y="75.009308">@ diff --git a/spaceswallet.svg b/spaceswallet.svg new file mode 100644 index 0000000..907681d --- /dev/null +++ b/spaceswallet.svg @@ -0,0 +1,64 @@ + + + + + + + + @ + SpacesWallet + + diff --git a/startos/main.ts b/startos/main.ts index 468edad..055b899 100644 --- a/startos/main.ts +++ b/startos/main.ts @@ -1,3 +1,4 @@ +import { mkdir, writeFile } from 'fs/promises' import { storeJson } from './fileModels/storeJson' import { i18n } from './i18n' import { sdk } from './sdk' @@ -8,6 +9,10 @@ import { SPACED_RPC_URL, SPACED_WALLETLOAD_NAME, spacesDataDir, + startOsHostnameFromUrl, + nodeExtraCaCertContainerPath, + nodeExtraCaCertVolumeSubpath, + SPACES_PACKAGE_ID, uiPort, } from './utils' @@ -106,6 +111,23 @@ export const main = sdk.setupMain(async ({ effects }) => { if (store.subsUser) env.SUBS_RPC_USER = store.subsUser if (store.subsPassword) env.SUBS_RPC_PASSWORD = store.subsPassword + // v1.0.1's entrypoint sources setup-spacesops-env.sh with set -u. That file + // exports NODE_EXTRA_CA_CERTS using ${_SCRIPT_DIR}, but _SCRIPT_DIR is set on + // a separate non-export line the entrypoint never evals — startup crashes + // unless NODE_EXTRA_CA_CERTS is already set. Fetch the StartOS root CA for + // outbound *.startos HTTPS (SUBS_URI host when configured, else Spaces). + const caHostname = + (store.subsUrl && startOsHostnameFromUrl(store.subsUrl)) || + `${SPACES_PACKAGE_ID}.startos` + const certs = await sdk.getSslCertificate(effects, [caHostname]).const() + const [rootCa] = certs.slice(-1) + await mkdir(sdk.volumes.main.subpath('.startos'), { recursive: true }) + await writeFile( + sdk.volumes.main.subpath(nodeExtraCaCertVolumeSubpath), + rootCa, + ) + env.NODE_EXTRA_CA_CERTS = nodeExtraCaCertContainerPath + return sdk.Daemons.of(effects).addDaemon('spacesops', { subcontainer: sub, exec: { diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts index 98ce97e..69904ed 100644 --- a/startos/manifest/index.ts +++ b/startos/manifest/index.ts @@ -14,7 +14,7 @@ export const manifest = setupManifest({ volumes: ['main'], images: { spacesops: { - source: { dockerTag: 'spacesops/spacesops:v1.0.0' }, + source: { dockerTag: 'spacesops/spacesops:v1.0.1' }, arch: ['x86_64', 'aarch64'], }, }, diff --git a/startos/utils.ts b/startos/utils.ts index 212eda7..af2fc15 100644 --- a/startos/utils.ts +++ b/startos/utils.ts @@ -20,13 +20,34 @@ export const SPACES_PACKAGE_ID = 'spaces' export const SPACED_RPC_URL = 'http://spaces.startos:7225' export const SPACED_WALLETLOAD_NAME = 'main' -// Admin Basic Auth is baked into the v1.0.0 image with NO env override. It +// Admin Basic Auth is baked into the v1.0.1 image with NO env override. It // cannot be changed without rebuilding the image. Surfaced (with a warning) // via the "Show Admin Credentials" action. export const ADMIN_USER = 'admin' export const ADMIN_PASSWORD = 'Whatever!' -// Optional-config defaults (see the "Configure Platform" action). +// Where StartOS writes the StartOS root CA for outbound *.startos HTTPS (see +// main.ts). Always injected as NODE_EXTRA_CA_CERTS — required on v1.0.1+ where +// the image entrypoint's setup-spacesops-env.sh references _SCRIPT_DIR without +// defining it under set -u. +export const nodeExtraCaCertVolumeSubpath = '.startos/startos-root-ca.crt' +export const nodeExtraCaCertContainerPath = `${dataDir}/.startos/startos-root-ca.crt` + +// Legacy aliases (same file path after rename). +export const subsCaCertVolumeSubpath = nodeExtraCaCertVolumeSubpath +export const subsCaCertContainerPath = nodeExtraCaCertContainerPath + +// Returns the hostname when url targets another StartOS service on this server +// (e.g. https://spaces.startos:7777), otherwise null. +export function startOsHostnameFromUrl(url: string): string | null { + try { + const hostname = new URL(url).hostname + return hostname.endsWith('.startos') ? hostname : null + } catch { + return null + } +} + export const DEFAULT_OPERATOR_RELAY = 'wss://relay.primal.net' export const DEFAULT_PLATFORM_MODE = 'prod' export const DEFAULT_COINGECKO_TOKEN_COINS = 'bitcoin' diff --git a/startos/versions/index.ts b/startos/versions/index.ts index 06f2296..f480cb4 100644 --- a/startos/versions/index.ts +++ b/startos/versions/index.ts @@ -1,7 +1,8 @@ import { VersionGraph } from '@start9labs/start-sdk' -import { v_1_0_0_0 } from './v1.0.0.0' +import { v_1_0_1_0 } from './v1.0.1.0' +import { v_1_0_1_1 } from './v1.0.1.1' export const versionGraph = VersionGraph.of({ - current: v_1_0_0_0, - other: [], + current: v_1_0_1_1, + other: [v_1_0_1_0], }) diff --git a/startos/versions/v1.0.0.0.ts b/startos/versions/v1.0.0.0.ts deleted file mode 100644 index f317ee6..0000000 --- a/startos/versions/v1.0.0.0.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' - -export const v_1_0_0_0 = VersionInfo.of({ - version: '1.0.0:0', - releaseNotes: { - en_US: `Initial StartOS package for SpacesOps (upstream v1.0.0). -- Runs the SpacesOps web platform (Express + SQLite) from the prebuilt spacesops/spacesops:v1.0.0 image on x86_64 and aarch64. -- Depends on the Spaces service and auto-connects to its spaced JSON-RPC at spaces.startos:7225 by reading the Spaces RPC credentials from the mounted Spaces volume. -- Generates a Nostr operator keypair and a strong session secret on first install. "Show Operator Credentials" and "Import Operator Key" actions manage the keypair. -- "Show Admin Credentials" surfaces the fixed, well-known admin login baked into the image, with a warning to keep the service private. -- "Configure Platform" optionally sets the Nostr relay, theme mode, CoinGecko pricing, and SUBS backend.`, - }, - migrations: { - up: async ({ effects }) => {}, - down: IMPOSSIBLE, - }, -}) diff --git a/startos/versions/v1.0.1.0.ts b/startos/versions/v1.0.1.0.ts new file mode 100644 index 0000000..333c4b7 --- /dev/null +++ b/startos/versions/v1.0.1.0.ts @@ -0,0 +1,14 @@ +import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' + +export const v_1_0_1_0 = VersionInfo.of({ + version: '1.0.1:0', + releaseNotes: { + en_US: `Upstream upgrade to SpacesOps v1.0.1. +- Bumps the image to spacesops/spacesops:v1.0.1 on x86_64 and aarch64. +- No StartOS-side configuration, action, or migration changes; existing operator keypair, session secret, and Configure-Platform settings are preserved.`, + }, + migrations: { + up: async ({ effects }) => {}, + down: IMPOSSIBLE, + }, +}) diff --git a/startos/versions/v1.0.1.1.ts b/startos/versions/v1.0.1.1.ts new file mode 100644 index 0000000..5a67bd6 --- /dev/null +++ b/startos/versions/v1.0.1.1.ts @@ -0,0 +1,13 @@ +import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' + +export const v_1_0_1_1 = VersionInfo.of({ + version: '1.0.1:1', + releaseNotes: { + en_US: `- Always sets NODE_EXTRA_CA_CERTS before the v1.0.1 image entrypoint runs, fetching the StartOS root CA via sdk.getSslCertificate (SUBS *.startos hostname when configured, otherwise spaces.startos). Fixes startup crash where setup-spacesops-env.sh references unset _SCRIPT_DIR under set -u. +- Enables outbound HTTPS to same-host StartOS services from SpacesOps.`, + }, + migrations: { + up: async ({ effects }) => {}, + down: IMPOSSIBLE, + }, +})