This commit is contained in:
2026-08-25 16:20:05 -04:00
parent feb32a1456
commit d026857b99
13 changed files with 93 additions and 18 deletions
+13 -9
View File
@@ -39,7 +39,7 @@ auto-connects to it — see [Installation and First-Run Flow](#installation-and-
| Field | Value | | Field | Value |
| --- | --- | | --- | --- |
| Image | `docker.io/spacesops/spacesops:v1.0.1` | | Image | `docker.io/spacesops/spacesops:v1.0.3` |
| Base | `node:20-alpine` | | Base | `node:20-alpine` |
| Architectures | `x86_64` (linux/amd64), `aarch64` (linux/arm64) | | 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. | | 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`. 2. Reads `spacedAuth.{username,password}` from the Spaces `store.json`.
3. Connects to spaced at `http://spaces.startos:7225` with those credentials, 3. Connects to spaced at `http://spaces.startos:7225` with those credentials,
exporting `SPACED_RPC_URL`, `SPACED_RPC_USER`, `SPACED_RPC_PASSWORD`, and 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 If Spaces is not installed yet, or its credentials have not been seeded, the
service **restarts until they appear** — this is expected. (Dependency 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) | | | `OPERATOR_SECRET_HEX` / `OPERATOR_PUBLIC_HEX` (generated or imported) | |
| `PLATFORM_SESSION_SECRET` (generated) | | | `PLATFORM_SESSION_SECRET` (generated) | |
| `SPACED_RPC_*` (auto-wired from Spaces) | | | `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**) | | | Optional: `OPERATOR_RELAY`, `PLATFORM_MODE`, `COINGECKO_*`, `SUBS_*` (via **Configure Platform**) | |
## Network Access and Interfaces ## 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. | | `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. | | `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. | | `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 ## 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 and sync Spaces before (or alongside) SpacesOps; the service restarts until
those credentials are available. those credentials are available.
2. **The admin password is fixed and well-known.** Admin Basic Auth is hardcoded 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 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 (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**. 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`). 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 4. **Subname-purchase and pricing are off by default.** They activate only when
you fill in the relevant fields in **Configure Platform** (`COINGECKO_*` for 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 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 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 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 manually if used. StartOS always sets `NODE_EXTRA_CA_CERTS` to the StartOS
root CA (fetched via `sdk.getSslCertificate` for the SUBS `*.startos` root CA (fetched via `sdk.getSslCertificate` for the SUBS `*.startos`
hostname when configured, otherwise `spaces.startos`) so outbound HTTPS to 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. crash on its broken `_SCRIPT_DIR` default.
## What Is Unchanged from Upstream ## What Is Unchanged from Upstream
@@ -170,8 +173,8 @@ volume is **not** part of this package's backup.
```yaml ```yaml
package_id: spacesops package_id: spacesops
upstream_version: 1.0.1 upstream_version: 1.0.3
image: docker.io/spacesops/spacesops:v1.0.1 image: docker.io/spacesops/spacesops:v1.0.3
architectures: [x86_64, aarch64] architectures: [x86_64, aarch64]
volumes: volumes:
main: /data main: /data
@@ -193,7 +196,8 @@ startos_managed_env_vars:
- SPACED_RPC_URL # http://spaces.startos:7225 - SPACED_RPC_URL # http://spaces.startos:7225
- SPACED_RPC_USER # from Spaces store.json - SPACED_RPC_USER # from Spaces store.json
- SPACED_RPC_PASSWORD # 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 optional_env_vars: # only set when configured via configure-platform
- COINGECKO_API_KEY - COINGECKO_API_KEY
- COINGECKO_TOKEN_COINS - COINGECKO_TOKEN_COINS
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+19 -1
View File
@@ -4,6 +4,7 @@ import { sdk } from '../sdk'
import { import {
DEFAULT_COINGECKO_TOKEN_COINS, DEFAULT_COINGECKO_TOKEN_COINS,
DEFAULT_OPERATOR_RELAY, DEFAULT_OPERATOR_RELAY,
DEFAULT_PLATFORM_CALLBACK_HOST,
DEFAULT_PLATFORM_MODE, DEFAULT_PLATFORM_MODE,
} from '../utils' } from '../utils'
@@ -103,6 +104,20 @@ const inputSpec = InputSpec.of({
minLength: null, minLength: null,
maxLength: 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( export const configurePlatform = sdk.Action.withInput(
@@ -113,7 +128,7 @@ export const configurePlatform = sdk.Action.withInput(
async ({ effects }) => ({ async ({ effects }) => ({
name: i18n('Configure Platform'), name: i18n('Configure Platform'),
description: i18n( 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, warning: null,
allowedStatuses: 'any', allowedStatuses: 'any',
@@ -136,6 +151,8 @@ export const configurePlatform = sdk.Action.withInput(
subsUrl: store?.subsUrl ?? null, subsUrl: store?.subsUrl ?? null,
subsUser: store?.subsUser ?? null, subsUser: store?.subsUser ?? null,
subsPassword: store?.subsPassword ?? 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, subsUrl: input.subsUrl || null,
subsUser: input.subsUser || null, subsUser: input.subsUser || null,
subsPassword: input.subsPassword || null, subsPassword: input.subsPassword || null,
platformCallbackHost: input.platformCallbackHost?.trim() || null,
}) })
return { return {
+1
View File
@@ -16,6 +16,7 @@ const shape = z.object({
subsUrl: z.string().nullable().catch(null), subsUrl: z.string().nullable().catch(null),
subsUser: z.string().nullable().catch(null), subsUser: z.string().nullable().catch(null),
subsPassword: z.string().nullable().catch(null), subsPassword: z.string().nullable().catch(null),
platformCallbackHost: z.string().nullable().catch(null),
}) })
export const storeJson = FileHelper.json( export const storeJson = FileHelper.json(
+3 -1
View File
@@ -16,7 +16,7 @@ const dict = {
'SUBS RPC Password': 12, 'SUBS RPC Password': 12,
'Optional SUBS RPC password (SUBS_RPC_PASSWORD).': 13, 'Optional SUBS RPC password (SUBS_RPC_PASSWORD).': 13,
'Configure Platform': 14, '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, Success: 16,
'Platform configuration saved. The service is restarting to apply the new settings.': 17, 'Platform configuration saved. The service is restarting to apply the new settings.': 17,
'Operator Secret Key (hex)': 18, 'Operator Secret Key (hex)': 18,
@@ -50,6 +50,8 @@ const dict = {
'Web Interface': 46, 'Web Interface': 46,
'The web interface is ready': 47, 'The web interface is ready': 47,
'The web interface is not ready': 48, '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 } as const
/** /**
+4 -1
View File
@@ -5,6 +5,7 @@ import { sdk } from './sdk'
import { import {
dataDir, dataDir,
DEFAULT_OPERATOR_RELAY, DEFAULT_OPERATOR_RELAY,
DEFAULT_PLATFORM_CALLBACK_HOST,
DEFAULT_PLATFORM_MODE, DEFAULT_PLATFORM_MODE,
SPACED_RPC_URL, SPACED_RPC_URL,
SPACED_WALLETLOAD_NAME, 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.subsUrl) env.SUBS_URI_VALUE = store.subsUrl
if (store.subsUser) env.SUBS_RPC_USER = store.subsUser if (store.subsUser) env.SUBS_RPC_USER = store.subsUser
if (store.subsPassword) env.SUBS_RPC_PASSWORD = store.subsPassword 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 // 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 // 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 // unless NODE_EXTRA_CA_CERTS is already set. Fetch the StartOS root CA for
+1 -1
View File
@@ -14,7 +14,7 @@ export const manifest = setupManifest({
volumes: ['main'], volumes: ['main'],
images: { images: {
spacesops: { spacesops: {
source: { dockerTag: 'spacesops/spacesops:v1.0.1' }, source: { dockerTag: 'spacesops/spacesops:v1.0.3' },
arch: ['x86_64', 'aarch64'], arch: ['x86_64', 'aarch64'],
}, },
}, },
+6 -3
View File
@@ -18,16 +18,16 @@ export const SPACES_PACKAGE_ID = 'spaces'
// Spaces exposes its spaced JSON-RPC as the `spaces-api` interface on 7225, // Spaces exposes its spaced JSON-RPC as the `spaces-api` interface on 7225,
// reachable from a dependent package at this address. // reachable from a dependent package at this address.
export const SPACED_RPC_URL = 'http://spaces.startos:7225' 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) // cannot be changed without rebuilding the image. Surfaced (with a warning)
// via the "Show Admin Credentials" action. // via the "Show Admin Credentials" action.
export const ADMIN_USER = 'admin' export const ADMIN_USER = 'admin'
export const ADMIN_PASSWORD = 'Whatever!' export const ADMIN_PASSWORD = 'Whatever!'
// Where StartOS writes the StartOS root CA for outbound *.startos HTTPS (see // 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 // the image entrypoint's setup-spacesops-env.sh references _SCRIPT_DIR without
// defining it under set -u. // defining it under set -u.
export const nodeExtraCaCertVolumeSubpath = '.startos/startos-root-ca.crt' 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_OPERATOR_RELAY = 'wss://relay.primal.net'
export const DEFAULT_PLATFORM_MODE = 'prod' export const DEFAULT_PLATFORM_MODE = 'prod'
export const DEFAULT_COINGECKO_TOKEN_COINS = 'bitcoin' 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 // 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, // odds of an out-of-range key are ~1 in 2^128; if getPublicKey rejects it,
+5 -2
View File
@@ -1,8 +1,11 @@
import { VersionGraph } from '@start9labs/start-sdk' import { VersionGraph } from '@start9labs/start-sdk'
import { v_1_0_1_0 } from './v1.0.1.0' 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_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({ export const versionGraph = VersionGraph.of({
current: v_1_0_1_1, current: v_1_0_3_0,
other: [v_1_0_1_0], other: [v_1_0_2_1, v_1_0_2_0, v_1_0_1_1, v_1_0_1_0],
}) })
+14
View File
@@ -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,
},
})
+13
View File
@@ -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,
},
})
+14
View File
@@ -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,
},
})