diff --git a/README.md b/README.md index 5b8c68b..454a99c 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.1` | +| Image | `docker.io/spacesops/spacesops:v1.0.3` | | 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. | @@ -86,7 +86,7 @@ credentials. Instead, at startup it: 2. Reads `spacedAuth.{username,password}` from the Spaces `store.json`. 3. Connects to spaced at `http://spaces.startos:7225` with those credentials, exporting `SPACED_RPC_URL`, `SPACED_RPC_USER`, `SPACED_RPC_PASSWORD`, and - `SPACED_WALLETLOAD_NAME=main`. + `SPACED_WALLETLOAD_NAME=default`. If Spaces is not installed yet, or its credentials have not been seeded, the service **restarts until they appear** — this is expected. (Dependency @@ -101,6 +101,7 @@ declarations do not gate startup; SpacesOps handles readiness itself.) | `OPERATOR_SECRET_HEX` / `OPERATOR_PUBLIC_HEX` (generated or imported) | | | `PLATFORM_SESSION_SECRET` (generated) | | | `SPACED_RPC_*` (auto-wired from Spaces) | | +| `PLATFORM_CALLBACK_HOST` (defaults to `spacesops.startos`; override via **Configure Platform**) | | | Optional: `OPERATOR_RELAY`, `PLATFORM_MODE`, `COINGECKO_*`, `SUBS_*` (via **Configure Platform**) | | ## Network Access and Interfaces @@ -116,7 +117,7 @@ declarations do not gate startup; SpacesOps handles readiness itself.) | `show-operator-credentials` | Show Operator Credentials | Enabled | Any | — | Displays the Nostr operator keypair: npub (QR), hex public key, and the masked nsec / hex secret. | | `import-operator-key` | Import Operator Key | Enabled | Any | 64-char hex secret key | Replaces the operator keypair. Derives the public key automatically. **Warns** that it changes the operator identity; the service restarts to apply it. | | `show-admin-credentials` | Show Admin Credentials | Enabled | Any | — | Displays the **fixed** built-in admin login (`admin` / `Whatever!`) with a security warning. | -| `configure-platform` | Configure Platform | Enabled | Any | relay, mode, CoinGecko, SUBS (all optional) | Sets the Nostr relay, theme mode, pricing, and SUBS backend. Saving restarts the service. | +| `configure-platform` | Configure Platform | Enabled | Any | relay, mode, CoinGecko, SUBS, callback host | Sets the Nostr relay, theme mode, pricing, SUBS backend, and `PLATFORM_CALLBACK_HOST`. Saving restarts the service. | ## Backups and Restore @@ -139,7 +140,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.1 image as `admin` / `Whatever!` with **no environment override**. + in the v1.0.3 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**. @@ -147,14 +148,16 @@ volume is **not** part of this package's backup. StartOS reverse proxy can reach the app (it otherwise binds `127.0.0.1:3000`). 4. **Subname-purchase and pricing are off by default.** They activate only when you fill in the relevant fields in **Configure Platform** (`COINGECKO_*` for - pricing, `SUBS_*` for subname purchase + cert-callback flows). + pricing, `SUBS_*` for subname purchase + cert-callback flows). Payment + callback URLs use `PLATFORM_CALLBACK_HOST`, which defaults to `spacesops.startos` + and can be overridden in **Configure Platform**. 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. 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 + same-host StartOS services succeeds and the v1.0.3 image entrypoint does not crash on its broken `_SCRIPT_DIR` default. ## What Is Unchanged from Upstream @@ -170,8 +173,8 @@ volume is **not** part of this package's backup. ```yaml package_id: spacesops -upstream_version: 1.0.1 -image: docker.io/spacesops/spacesops:v1.0.1 +upstream_version: 1.0.3 +image: docker.io/spacesops/spacesops:v1.0.3 architectures: [x86_64, aarch64] volumes: main: /data @@ -193,7 +196,8 @@ startos_managed_env_vars: - SPACED_RPC_URL # http://spaces.startos:7225 - SPACED_RPC_USER # from Spaces store.json - SPACED_RPC_PASSWORD # from Spaces store.json - - SPACED_WALLETLOAD_NAME # main + - SPACED_WALLETLOAD_NAME # default + - PLATFORM_CALLBACK_HOST # store.platformCallbackHost ?? spacesops.startos optional_env_vars: # only set when configured via configure-platform - COINGECKO_API_KEY - COINGECKO_TOKEN_COINS diff --git a/background.png b/background.png new file mode 100644 index 0000000..11e5169 Binary files /dev/null and b/background.png differ diff --git a/spacesops.png b/spacesops.png new file mode 100644 index 0000000..5dc56ca Binary files /dev/null and b/spacesops.png differ diff --git a/startos/actions/configurePlatform.ts b/startos/actions/configurePlatform.ts index 771404c..e0f1be6 100644 --- a/startos/actions/configurePlatform.ts +++ b/startos/actions/configurePlatform.ts @@ -4,6 +4,7 @@ import { sdk } from '../sdk' import { DEFAULT_COINGECKO_TOKEN_COINS, DEFAULT_OPERATOR_RELAY, + DEFAULT_PLATFORM_CALLBACK_HOST, DEFAULT_PLATFORM_MODE, } from '../utils' @@ -103,6 +104,20 @@ const inputSpec = InputSpec.of({ minLength: null, maxLength: null, }), + platformCallbackHost: Value.text({ + name: i18n('CALLBACK HOST'), + description: i18n( + 'Callback hostname SpacesOps uses for cert-callback flows (PLATFORM_CALLBACK_HOST). Leave blank to use spacesops.startos.', + ), + warning: null, + footnote: null, + default: DEFAULT_PLATFORM_CALLBACK_HOST, + required: false, + masked: false, + placeholder: 'spacesops.startos', + minLength: null, + maxLength: null, + }), }) export const configurePlatform = sdk.Action.withInput( @@ -113,7 +128,7 @@ export const configurePlatform = sdk.Action.withInput( async ({ effects }) => ({ name: i18n('Configure Platform'), description: i18n( - 'Set optional SpacesOps settings: Nostr relay, theme mode, CoinGecko pricing, and SUBS backend. Saving restarts the service so the new settings take effect.', + 'Set optional SpacesOps settings: Nostr relay, theme mode, CoinGecko pricing, SUBS backend, and callback host. Saving restarts the service so the new settings take effect.', ), warning: null, allowedStatuses: 'any', @@ -136,6 +151,8 @@ export const configurePlatform = sdk.Action.withInput( subsUrl: store?.subsUrl ?? null, subsUser: store?.subsUser ?? null, subsPassword: store?.subsPassword ?? null, + platformCallbackHost: + store?.platformCallbackHost ?? DEFAULT_PLATFORM_CALLBACK_HOST, } }, @@ -149,6 +166,7 @@ export const configurePlatform = sdk.Action.withInput( subsUrl: input.subsUrl || null, subsUser: input.subsUser || null, subsPassword: input.subsPassword || null, + platformCallbackHost: input.platformCallbackHost?.trim() || null, }) return { diff --git a/startos/fileModels/storeJson.ts b/startos/fileModels/storeJson.ts index 1cc93a4..129fc01 100644 --- a/startos/fileModels/storeJson.ts +++ b/startos/fileModels/storeJson.ts @@ -16,6 +16,7 @@ const shape = z.object({ subsUrl: z.string().nullable().catch(null), subsUser: z.string().nullable().catch(null), subsPassword: z.string().nullable().catch(null), + platformCallbackHost: z.string().nullable().catch(null), }) export const storeJson = FileHelper.json( diff --git a/startos/i18n/dictionaries/default.ts b/startos/i18n/dictionaries/default.ts index c850ba7..29565dd 100644 --- a/startos/i18n/dictionaries/default.ts +++ b/startos/i18n/dictionaries/default.ts @@ -16,7 +16,7 @@ const dict = { 'SUBS RPC Password': 12, 'Optional SUBS RPC password (SUBS_RPC_PASSWORD).': 13, 'Configure Platform': 14, - 'Set optional SpacesOps settings: Nostr relay, theme mode, CoinGecko pricing, and SUBS backend. Saving restarts the service so the new settings take effect.': 15, + 'Set optional SpacesOps settings: Nostr relay, theme mode, CoinGecko pricing, SUBS backend, and callback host. Saving restarts the service so the new settings take effect.': 15, Success: 16, 'Platform configuration saved. The service is restarting to apply the new settings.': 17, 'Operator Secret Key (hex)': 18, @@ -50,6 +50,8 @@ const dict = { 'Web Interface': 46, 'The web interface is ready': 47, 'The web interface is not ready': 48, + 'CALLBACK HOST': 49, + 'Callback hostname SpacesOps uses for cert-callback flows (PLATFORM_CALLBACK_HOST). Leave blank to use spacesops.startos.': 50, } as const /** diff --git a/startos/main.ts b/startos/main.ts index 055b899..df66ea7 100644 --- a/startos/main.ts +++ b/startos/main.ts @@ -5,6 +5,7 @@ import { sdk } from './sdk' import { dataDir, DEFAULT_OPERATOR_RELAY, + DEFAULT_PLATFORM_CALLBACK_HOST, DEFAULT_PLATFORM_MODE, SPACED_RPC_URL, SPACED_WALLETLOAD_NAME, @@ -110,8 +111,10 @@ export const main = sdk.setupMain(async ({ effects }) => { if (store.subsUrl) env.SUBS_URI_VALUE = store.subsUrl if (store.subsUser) env.SUBS_RPC_USER = store.subsUser if (store.subsPassword) env.SUBS_RPC_PASSWORD = store.subsPassword + env.PLATFORM_CALLBACK_HOST = + store.platformCallbackHost ?? DEFAULT_PLATFORM_CALLBACK_HOST - // v1.0.1's entrypoint sources setup-spacesops-env.sh with set -u. That file + // v1.0.3'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 diff --git a/startos/manifest/index.ts b/startos/manifest/index.ts index 69904ed..3854a95 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.1' }, + source: { dockerTag: 'spacesops/spacesops:v1.0.3' }, arch: ['x86_64', 'aarch64'], }, }, diff --git a/startos/utils.ts b/startos/utils.ts index af2fc15..ca85294 100644 --- a/startos/utils.ts +++ b/startos/utils.ts @@ -18,16 +18,16 @@ export const SPACES_PACKAGE_ID = 'spaces' // Spaces exposes its spaced JSON-RPC as the `spaces-api` interface on 7225, // reachable from a dependent package at this address. export const SPACED_RPC_URL = 'http://spaces.startos:7225' -export const SPACED_WALLETLOAD_NAME = 'main' +export const SPACED_WALLETLOAD_NAME = 'default' -// Admin Basic Auth is baked into the v1.0.1 image with NO env override. It +// Admin Basic Auth is baked into the v1.0.3 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!' // 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 +// main.ts). Always injected as NODE_EXTRA_CA_CERTS — required on v1.0.3+ 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' @@ -51,6 +51,9 @@ export function startOsHostnameFromUrl(url: string): string | null { export const DEFAULT_OPERATOR_RELAY = 'wss://relay.primal.net' export const DEFAULT_PLATFORM_MODE = 'prod' export const DEFAULT_COINGECKO_TOKEN_COINS = 'bitcoin' +// Hostname SpacesOps uses when building payment callback URLs +// (http://${PLATFORM_CALLBACK_HOST}:${PORT}/...). +export const DEFAULT_PLATFORM_CALLBACK_HOST = 'spacesops.startos' // 32 random bytes as a 64-char hex string — a secp256k1/Nostr secret key. The // odds of an out-of-range key are ~1 in 2^128; if getPublicKey rejects it, diff --git a/startos/versions/index.ts b/startos/versions/index.ts index f480cb4..f750bcf 100644 --- a/startos/versions/index.ts +++ b/startos/versions/index.ts @@ -1,8 +1,11 @@ import { VersionGraph } from '@start9labs/start-sdk' import { v_1_0_1_0 } from './v1.0.1.0' import { v_1_0_1_1 } from './v1.0.1.1' +import { v_1_0_2_0 } from './v1.0.2.0' +import { v_1_0_2_1 } from './v1.0.2.1' +import { v_1_0_3_0 } from './v1.0.3.0' export const versionGraph = VersionGraph.of({ - current: v_1_0_1_1, - other: [v_1_0_1_0], + current: v_1_0_3_0, + other: [v_1_0_2_1, v_1_0_2_0, v_1_0_1_1, v_1_0_1_0], }) diff --git a/startos/versions/v1.0.2.0.ts b/startos/versions/v1.0.2.0.ts new file mode 100644 index 0000000..3ec2fdb --- /dev/null +++ b/startos/versions/v1.0.2.0.ts @@ -0,0 +1,14 @@ +import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' + +export const v_1_0_2_0 = VersionInfo.of({ + version: '1.0.2:0', + releaseNotes: { + en_US: `Upstream upgrade to SpacesOps v1.0.2. +- Bumps the image to spacesops/spacesops:v1.0.2 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.2.1.ts b/startos/versions/v1.0.2.1.ts new file mode 100644 index 0000000..642952d --- /dev/null +++ b/startos/versions/v1.0.2.1.ts @@ -0,0 +1,13 @@ +import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' + +export const v_1_0_2_1 = VersionInfo.of({ + version: '1.0.2:1', + releaseNotes: { + en_US: `- Adds PLATFORM_CALLBACK_HOST to Configure Platform (defaults to spacesops.startos) for cert-callback and payment callback URLs. +- Sets SPACED_WALLETLOAD_NAME to default (was main).`, + }, + migrations: { + up: async ({ effects }) => {}, + down: IMPOSSIBLE, + }, +}) diff --git a/startos/versions/v1.0.3.0.ts b/startos/versions/v1.0.3.0.ts new file mode 100644 index 0000000..7341348 --- /dev/null +++ b/startos/versions/v1.0.3.0.ts @@ -0,0 +1,14 @@ +import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' + +export const v_1_0_3_0 = VersionInfo.of({ + version: '1.0.3:0', + releaseNotes: { + en_US: `Upstream upgrade to SpacesOps v1.0.3. +- Bumps the image to spacesops/spacesops:v1.0.3 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, + }, +})