Compare commits
2
Commits
feb32a1456
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d054ba22c9 | ||
|
|
d026857b99 |
@@ -1,4 +1,4 @@
|
|||||||
# spacesops ships linux/amd64 + linux/arm64 only (no riscv), so build both.
|
# spacesops ships linux/amd64 + linux/arm64 only (no riscv), so build both.
|
||||||
ARCHES := x86 arm
|
ARCHES := x86 arm
|
||||||
# overrides to s9pk.mk must precede the include statement
|
# overrides to s9pk.mk must precede the include statement
|
||||||
include s9pk.mk
|
include node_modules/@start9labs/start-sdk/s9pk.mk
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ auto-connects to it — see [Installation and First-Run Flow](#installation-and-
|
|||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Image | `docker.io/spacesops/spacesops:v1.0.1` |
|
| Package version | `1.0.4:0` |
|
||||||
|
| Requires | StartOS `0.4.0-beta.10` or later (built with `@start9labs/start-sdk` 2.x) |
|
||||||
|
| Image | `docker.io/spacesops/spacesops:v1.0.4` |
|
||||||
| 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 +88,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,13 +103,18 @@ 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
|
||||||
|
|
||||||
| Interface | Port | Protocol | Exposure | Notes |
|
| Interface | Port | Protocol | Type | Notes |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| Web UI | 7264 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | The SpacesOps web platform + admin area. The app provides its own auth; the admin area uses a **fixed built-in credential** (see warning below). |
|
| Web UI | 7264 | HTTP | `ui` | The SpacesOps web platform + admin area. The app provides its own auth; the admin area uses a **fixed built-in credential** (see warning below). |
|
||||||
|
|
||||||
|
The package binds port 7264 and exports a single `ui` interface. **Where that
|
||||||
|
interface is reachable is the user's choice**, made in StartOS — the package
|
||||||
|
neither provisions nor implies any particular address.
|
||||||
|
|
||||||
## Actions
|
## Actions
|
||||||
|
|
||||||
@@ -116,7 +123,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
|
||||||
|
|
||||||
@@ -138,24 +145,31 @@ volume is **not** part of this package's backup.
|
|||||||
by reading the Spaces RPC credentials from the mounted Spaces volume. Install
|
by reading the Spaces RPC credentials from the mounted Spaces volume. Install
|
||||||
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 well-known.** The app resolves admin Basic Auth as
|
||||||
in the v1.0.1 image as `admin` / `Whatever!` with **no environment override**.
|
`process.env.PLATFORM_ADMIN_USERNAME || 'admin'` and
|
||||||
It cannot be changed without rebuilding the image. **Keep this service private
|
`process.env.PLATFORM_ADMIN_PASSWORD || 'Whatever!'`. This package does not
|
||||||
(Tor-only) and never expose its admin routes to the public internet** — the
|
currently set either variable, so the well-known defaults are live. **Keep
|
||||||
admin area can run SQL and manage tenants. See **Show Admin Credentials**.
|
this service private and do not make its admin routes broadly reachable** —
|
||||||
|
the admin area can run SQL and manage tenants. See **Show Admin
|
||||||
|
Credentials**. Setting `PLATFORM_ADMIN_PASSWORD` to a generated secret (as
|
||||||
|
this package already does for `PLATFORM_SESSION_SECRET`) would close this
|
||||||
|
gap; see `TODO.md`.
|
||||||
3. **`PLATFORM_HOST` is forced to `0.0.0.0` and `PLATFORM_PORT` to `7264`** so the
|
3. **`PLATFORM_HOST` is forced to `0.0.0.0` and `PLATFORM_PORT` to `7264`** so the
|
||||||
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 this box's
|
||||||
root CA (fetched via `sdk.getSslCertificate` for the SUBS `*.startos`
|
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`). This overrides the
|
||||||
same-host StartOS services succeeds and the v1.0.1 image entrypoint does not
|
image's own default of `/app/certs/startos-local-root-ca.pem`, a root CA
|
||||||
crash on its broken `_SCRIPT_DIR` default.
|
baked in at build time from a different machine, so outbound HTTPS to
|
||||||
|
same-host StartOS services succeeds.
|
||||||
|
|
||||||
## What Is Unchanged from Upstream
|
## What Is Unchanged from Upstream
|
||||||
|
|
||||||
@@ -170,8 +184,11 @@ volume is **not** part of this package's backup.
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
package_id: spacesops
|
package_id: spacesops
|
||||||
upstream_version: 1.0.1
|
package_version: 1.0.4:0
|
||||||
image: docker.io/spacesops/spacesops:v1.0.1
|
upstream_version: 1.0.4
|
||||||
|
image: docker.io/spacesops/spacesops:v1.0.4
|
||||||
|
min_startos_version: 0.4.0-beta.10
|
||||||
|
start_sdk_version: 2.0.9
|
||||||
architectures: [x86_64, aarch64]
|
architectures: [x86_64, aarch64]
|
||||||
volumes:
|
volumes:
|
||||||
main: /data
|
main: /data
|
||||||
@@ -193,7 +210,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
|
||||||
@@ -202,8 +220,9 @@ optional_env_vars: # only set when configured via configure-platform
|
|||||||
- SUBS_RPC_PASSWORD
|
- SUBS_RPC_PASSWORD
|
||||||
- NODE_EXTRA_CA_CERTS # /data/.startos/startos-root-ca.crt (always set)
|
- NODE_EXTRA_CA_CERTS # /data/.startos/startos-root-ca.crt (always set)
|
||||||
admin_credentials:
|
admin_credentials:
|
||||||
username: admin
|
username: admin # app default; override via PLATFORM_ADMIN_USERNAME
|
||||||
password: Whatever! # FIXED, baked into image, cannot be changed
|
password: Whatever! # app default; override via PLATFORM_ADMIN_PASSWORD
|
||||||
|
overridden_by_package: false # package does not set either var today
|
||||||
note: keep service private; admin area can run SQL and manage tenants
|
note: keep service private; admin area can run SQL and manage tenants
|
||||||
actions:
|
actions:
|
||||||
- show-operator-credentials
|
- show-operator-credentials
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
|
## Set a generated admin password
|
||||||
|
|
||||||
|
The app resolves its admin Basic Auth credentials as:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const ADMIN_USERNAME = process.env.PLATFORM_ADMIN_USERNAME || 'admin'
|
||||||
|
const ADMIN_PASSWORD = process.env.PLATFORM_ADMIN_PASSWORD || 'Whatever!'
|
||||||
|
```
|
||||||
|
|
||||||
|
(verified in `/app/server.js` of `spacesops/spacesops:v1.0.4`)
|
||||||
|
|
||||||
|
These are **defaults, not hardcoded values** — both are overridable via
|
||||||
|
environment variables. The package currently sets neither, so every install
|
||||||
|
ships the same publicly known admin password, guarding an area that can run SQL
|
||||||
|
and manage tenants.
|
||||||
|
|
||||||
|
The fix mirrors what the package already does for `PLATFORM_SESSION_SECRET`:
|
||||||
|
|
||||||
|
1. Add an `adminPassword` field to `fileModels/storeJson.ts`.
|
||||||
|
2. Seed it in a `setupOnInit` task with `utils.getDefaultString(...)`, the same
|
||||||
|
way `init/taskSessionSecret.ts` does.
|
||||||
|
3. Inject `PLATFORM_ADMIN_PASSWORD` (and optionally
|
||||||
|
`PLATFORM_ADMIN_USERNAME`) from the store in `main.ts`.
|
||||||
|
4. Update the **Show Admin Credentials** action to read the generated value
|
||||||
|
instead of the `ADMIN_USER` / `ADMIN_PASSWORD` constants in `utils.ts`.
|
||||||
|
5. Add a "Reset Admin Password" action (see `recipe-reset-password.md`).
|
||||||
|
6. Update `README.md` limitation 2 and the `instructions.md` admin section,
|
||||||
|
which currently document the well-known default as the live credential.
|
||||||
|
|
||||||
|
This is a behavior change for existing installs — a user who bookmarked the old
|
||||||
|
password would be locked out — so it needs release notes calling it out, and
|
||||||
|
possibly a migration that seeds the store for already-installed services.
|
||||||
|
|
||||||
|
## Verify on a live box
|
||||||
|
|
||||||
|
The 2.x SDK migration and the v1.0.4 bump have been compiled, linted, and
|
||||||
|
packed, but not yet installed and exercised on a StartOS 0.4.0-beta.10+ host.
|
||||||
|
Confirm the service starts, the web UI is reachable, the Spaces credential probe
|
||||||
|
succeeds, and outbound HTTPS to `*.startos` works with the injected
|
||||||
|
`NODE_EXTRA_CA_CERTS`.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
+5
-4
@@ -24,15 +24,16 @@ SpacesOps dashboard.
|
|||||||
|
|
||||||
## ⚠️ Admin credentials — important
|
## ⚠️ Admin credentials — important
|
||||||
|
|
||||||
The admin area is protected by a **fixed, well-known** login that is baked into
|
The admin area is protected by a **well-known default** login that this version
|
||||||
the image and **cannot be changed**:
|
of the package does not change:
|
||||||
|
|
||||||
- **Username:** `admin`
|
- **Username:** `admin`
|
||||||
- **Password:** `Whatever!`
|
- **Password:** `Whatever!`
|
||||||
|
|
||||||
The **Show Admin Credentials** action displays these. Because the admin area can
|
The **Show Admin Credentials** action displays these. Because the admin area can
|
||||||
run SQL and manage tenants, **keep this service private** — use it over Tor or
|
run SQL and manage tenants, **keep this service private** — restrict how you
|
||||||
your LAN, and do **not** expose its admin routes to the public internet.
|
make it reachable in StartOS, and do **not** make its admin routes broadly
|
||||||
|
accessible.
|
||||||
|
|
||||||
## Actions
|
## Actions
|
||||||
|
|
||||||
|
|||||||
Generated
+1310
-25
File diff suppressed because it is too large
Load Diff
+6
-3
@@ -6,14 +6,17 @@
|
|||||||
"check": "tsc --noEmit"
|
"check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@start9labs/start-sdk": "1.5.1"
|
"@start9labs/start-sdk": "2.0.9",
|
||||||
|
"nostr-tools": "^2.10.4"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"@start9labs/start-sdk": "$@start9labs/start-sdk"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.19.0",
|
"@types/node": "^22.19.0",
|
||||||
"@vercel/ncc": "^0.38.4",
|
"@vercel/ncc": "^0.38.4",
|
||||||
"nostr-tools": "^2.10.4",
|
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^6.0.3"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
# ** Plumbing. DO NOT EDIT **.
|
|
||||||
# This file is imported by ./Makefile. Make edits there
|
|
||||||
|
|
||||||
PACKAGE_ID := $(shell awk -F"'" '/id:/ {print $$2}' startos/manifest/index.ts)
|
|
||||||
INGREDIENTS := $(shell start-cli s9pk list-ingredients 2>/dev/null)
|
|
||||||
# Resolve the actual git dir so this works inside git worktrees, where .git
|
|
||||||
# is a file pointing at <main>/.git/worktrees/<name> rather than a directory.
|
|
||||||
GIT_DIR := $(shell git rev-parse --git-dir 2>/dev/null)
|
|
||||||
GIT_DEPS := $(if $(GIT_DIR),$(GIT_DIR)/HEAD $(GIT_DIR)/index)
|
|
||||||
ARCHES ?= x86 arm riscv
|
|
||||||
# TARGETS is the list of leaf make-targets the build matrix fans out over.
|
|
||||||
# Defaults to the arches; variant packages override (e.g. immich, ollama, vllm
|
|
||||||
# set this to a list of variant or variant-arch leaf targets).
|
|
||||||
TARGETS ?= $(ARCHES)
|
|
||||||
ifdef VARIANT
|
|
||||||
BASE_NAME := $(PACKAGE_ID)_$(VARIANT)
|
|
||||||
else
|
|
||||||
BASE_NAME := $(PACKAGE_ID)
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: all arches aarch64 x86_64 riscv64 arm arm64 x86 riscv arch/* clean install check-deps check-init package ingredients
|
|
||||||
.DELETE_ON_ERROR:
|
|
||||||
.SECONDARY:
|
|
||||||
|
|
||||||
define SUMMARY
|
|
||||||
@manifest=$$(start-cli s9pk inspect $(1) manifest); \
|
|
||||||
size=$$(du -h $(1) | awk '{print $$1}'); \
|
|
||||||
title=$$(printf '%s' "$$manifest" | jq -r .title); \
|
|
||||||
version=$$(printf '%s' "$$manifest" | jq -r .version); \
|
|
||||||
arches=$$(printf '%s' "$$manifest" | jq -r '[.images[].arch // []] | flatten | unique | join(", ")'); \
|
|
||||||
sdkv=$$(printf '%s' "$$manifest" | jq -r .sdkVersion); \
|
|
||||||
gitHash=$$(printf '%s' "$$manifest" | jq -r .gitHash | sed -E 's/(.*-modified)$$/\x1b[0;31m\1\x1b[0m/'); \
|
|
||||||
printf "\n"; \
|
|
||||||
printf "\033[1;32m✅ Build Complete!\033[0m\n"; \
|
|
||||||
printf "\n"; \
|
|
||||||
printf "\033[1;37m📦 $$title\033[0m \033[36mv$$version\033[0m\n"; \
|
|
||||||
printf "───────────────────────────────\n"; \
|
|
||||||
printf " \033[1;36mFilename:\033[0m %s\n" "$(1)"; \
|
|
||||||
printf " \033[1;36mSize:\033[0m %s\n" "$$size"; \
|
|
||||||
printf " \033[1;36mArch:\033[0m %s\n" "$$arches"; \
|
|
||||||
printf " \033[1;36mSDK:\033[0m %s\n" "$$sdkv"; \
|
|
||||||
printf " \033[1;36mGit:\033[0m %s\n" "$$gitHash"; \
|
|
||||||
echo ""
|
|
||||||
endef
|
|
||||||
|
|
||||||
all: $(TARGETS)
|
|
||||||
|
|
||||||
arches: $(ARCHES)
|
|
||||||
|
|
||||||
# Generic make-variable introspection. Used by the release workflow to
|
|
||||||
# read $(TARGETS) and fan out one matrix runner per target. `make -s
|
|
||||||
# print-TARGETS` echoes the list with no other output.
|
|
||||||
print-%:
|
|
||||||
@echo '$($*)'
|
|
||||||
|
|
||||||
universal: $(BASE_NAME).s9pk
|
|
||||||
$(call SUMMARY,$<)
|
|
||||||
|
|
||||||
arch/%: $(BASE_NAME)_%.s9pk
|
|
||||||
$(call SUMMARY,$<)
|
|
||||||
|
|
||||||
x86 x86_64: arch/x86_64
|
|
||||||
arm arm64 aarch64: arch/aarch64
|
|
||||||
riscv riscv64: arch/riscv64
|
|
||||||
|
|
||||||
$(BASE_NAME).s9pk: $(INGREDIENTS) $(GIT_DEPS)
|
|
||||||
@$(MAKE) --no-print-directory ingredients
|
|
||||||
@echo " Packing '$@'..."
|
|
||||||
start-cli s9pk pack -o $@
|
|
||||||
|
|
||||||
$(BASE_NAME)_%.s9pk: $(INGREDIENTS) $(GIT_DEPS)
|
|
||||||
@$(MAKE) --no-print-directory ingredients
|
|
||||||
@echo " Packing '$@'..."
|
|
||||||
start-cli s9pk pack --arch=$* -o $@
|
|
||||||
|
|
||||||
ingredients: $(INGREDIENTS)
|
|
||||||
@echo " Re-evaluating ingredients..."
|
|
||||||
|
|
||||||
install: | check-deps check-init
|
|
||||||
@HOST=$$(awk -F'/' '/^host:/ {print $$3}' ~/.startos/config.yaml); \
|
|
||||||
if [ -z "$$HOST" ]; then \
|
|
||||||
echo "Error: You must define \"host: http://server-name.local\" in ~/.startos/config.yaml"; \
|
|
||||||
exit 1; \
|
|
||||||
fi; \
|
|
||||||
S9PK=$$(ls -t *.s9pk 2>/dev/null | head -1); \
|
|
||||||
if [ -z "$$S9PK" ]; then \
|
|
||||||
echo "Error: No .s9pk file found. Run 'make' first."; \
|
|
||||||
exit 1; \
|
|
||||||
fi; \
|
|
||||||
printf "\n🚀 Installing %s to %s ...\n" "$$S9PK" "$$HOST"; \
|
|
||||||
start-cli package install -s "$$S9PK"
|
|
||||||
|
|
||||||
publish: | all
|
|
||||||
@REGISTRY=$$(awk -F'/' '/^registry:/ {print $$3}' ~/.startos/config.yaml); \
|
|
||||||
if [ -z "$$REGISTRY" ]; then \
|
|
||||||
echo "Error: You must define \"registry: https://my-registry.tld\" in ~/.startos/config.yaml"; \
|
|
||||||
exit 1; \
|
|
||||||
fi; \
|
|
||||||
S3BASE=$$(awk -F'/' '/^s9pk-s3base:/ {print $$3}' ~/.startos/config.yaml); \
|
|
||||||
if [ -z "$$S3BASE" ]; then \
|
|
||||||
echo "Error: You must define \"s3base: https://s9pks.my-s3-bucket.tld\" in ~/.startos/config.yaml"; \
|
|
||||||
exit 1; \
|
|
||||||
fi; \
|
|
||||||
command -v s3cmd >/dev/null || \
|
|
||||||
(echo "Error: s3cmd not found. It must be installed to publish using s3." && exit 1); \
|
|
||||||
printf "\n🚀 Publishing to %s; indexing on %s ...\n" "$$S3BASE" "$$REGISTRY"; \
|
|
||||||
for s9pk in *.s9pk; do \
|
|
||||||
age=$$(( $$(date +%s) - $$(stat -c %Y "$$s9pk") )); \
|
|
||||||
if [ "$$age" -gt 3600 ]; then \
|
|
||||||
printf "\033[1;33m⚠️ %s is %d minutes old. Publish anyway? [y/N] \033[0m" "$$s9pk" "$$((age / 60))"; \
|
|
||||||
read -r ans; \
|
|
||||||
case "$$ans" in [yY]*) ;; *) echo "Skipping $$s9pk"; continue ;; esac; \
|
|
||||||
fi; \
|
|
||||||
start-cli s9pk publish "$$s9pk"; \
|
|
||||||
done
|
|
||||||
|
|
||||||
check-deps:
|
|
||||||
@command -v start-cli >/dev/null || \
|
|
||||||
(echo "Error: start-cli not found. Please see https://docs.start9.com/latest/developer-guide/sdk/installing-the-sdk" && exit 1)
|
|
||||||
@command -v npm >/dev/null || \
|
|
||||||
(echo "Error: npm not found. Please install Node.js and npm." && exit 1)
|
|
||||||
|
|
||||||
check-init:
|
|
||||||
@if [ ! -f ~/.startos/developer.key.pem ]; then \
|
|
||||||
echo "Initializing StartOS developer environment..."; \
|
|
||||||
start-cli init-key; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
javascript/index.js: $(shell find startos -type f) tsconfig.json node_modules
|
|
||||||
npm run check
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
node_modules: package-lock.json
|
|
||||||
npm ci
|
|
||||||
|
|
||||||
package-lock.json: package.json
|
|
||||||
npm i
|
|
||||||
|
|
||||||
clean:
|
|
||||||
@echo "Cleaning up build artifacts..."
|
|
||||||
@rm -rf $(PACKAGE_ID).s9pk $(PACKAGE_ID)_x86_64.s9pk $(PACKAGE_ID)_aarch64.s9pk $(PACKAGE_ID)_riscv64.s9pk javascript node_modules
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -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 {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+9
-6
@@ -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,
|
||||||
@@ -54,7 +55,7 @@ export const main = sdk.setupMain(async ({ effects }) => {
|
|||||||
type: 'directory',
|
type: 'directory',
|
||||||
})
|
})
|
||||||
|
|
||||||
const sub = await sdk.SubContainer.of(
|
const sub = sdk.SubContainer.of(
|
||||||
effects,
|
effects,
|
||||||
{ imageId: 'spacesops' },
|
{ imageId: 'spacesops' },
|
||||||
mounts,
|
mounts,
|
||||||
@@ -110,12 +111,14 @@ 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
|
// The image's setup-spacesops-env.sh defaults NODE_EXTRA_CA_CERTS to
|
||||||
// exports NODE_EXTRA_CA_CERTS using ${_SCRIPT_DIR}, but _SCRIPT_DIR is set on
|
// /app/certs/startos-local-root-ca.pem, a root CA baked in at build time from
|
||||||
// a separate non-export line the entrypoint never evals — startup crashes
|
// another box. Fetch this box's StartOS root CA and point the variable at it
|
||||||
// unless NODE_EXTRA_CA_CERTS is already set. Fetch the StartOS root CA for
|
// instead, so outbound HTTPS to *.startos succeeds (SUBS_URI host when
|
||||||
// outbound *.startos HTTPS (SUBS_URI host when configured, else Spaces).
|
// configured, else Spaces).
|
||||||
const caHostname =
|
const caHostname =
|
||||||
(store.subsUrl && startOsHostnameFromUrl(store.subsUrl)) ||
|
(store.subsUrl && startOsHostnameFromUrl(store.subsUrl)) ||
|
||||||
`${SPACES_PACKAGE_ID}.startos`
|
`${SPACES_PACKAGE_ID}.startos`
|
||||||
|
|||||||
@@ -9,26 +9,14 @@ export const manifest = setupManifest({
|
|||||||
upstreamRepo: 'https://git.spacesops.com/spacesops/spacesops',
|
upstreamRepo: 'https://git.spacesops.com/spacesops/spacesops',
|
||||||
marketingUrl: 'https://spacesops.com',
|
marketingUrl: 'https://spacesops.com',
|
||||||
donationUrl: null,
|
donationUrl: null,
|
||||||
docsUrls: [],
|
|
||||||
description: { short, long },
|
description: { short, long },
|
||||||
volumes: ['main'],
|
volumes: ['main'],
|
||||||
images: {
|
images: {
|
||||||
spacesops: {
|
spacesops: {
|
||||||
source: { dockerTag: 'spacesops/spacesops:v1.0.1' },
|
source: { dockerTag: 'spacesops/spacesops:v1.0.4' },
|
||||||
arch: ['x86_64', 'aarch64'],
|
arch: ['x86_64', 'aarch64'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
alerts: {
|
|
||||||
install: {
|
|
||||||
en_US:
|
|
||||||
'SpacesOps REQUIRES the Spaces service. Install and sync Spaces first — SpacesOps auto-connects to it at spaces.startos:7225 by reading the Spaces RPC credentials, and will not start until they are available.\n\nWARNING: the admin area is protected by a FIXED, well-known credential baked into this image (username "admin", password "Whatever!") that cannot be changed without rebuilding the image. The admin area can run SQL and manage tenants. Keep this service private (Tor-only) and do NOT expose its admin routes to the public internet. See the "Show Admin Credentials" action.',
|
|
||||||
},
|
|
||||||
update: null,
|
|
||||||
uninstall: null,
|
|
||||||
restore: null,
|
|
||||||
start: null,
|
|
||||||
stop: null,
|
|
||||||
},
|
|
||||||
dependencies: {
|
dependencies: {
|
||||||
spaces: {
|
spaces: {
|
||||||
description: depSpacesDescription,
|
description: depSpacesDescription,
|
||||||
|
|||||||
+12
-8
@@ -18,18 +18,19 @@ 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
|
// The app's admin Basic Auth defaults, which it uses when
|
||||||
// cannot be changed without rebuilding the image. Surfaced (with a warning)
|
// PLATFORM_ADMIN_USERNAME / PLATFORM_ADMIN_PASSWORD are unset. This package
|
||||||
// via the "Show Admin Credentials" action.
|
// does not currently set either, so these well-known values are live —
|
||||||
|
// surfaced (with a warning) 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 this box's StartOS root CA for outbound *.startos HTTPS
|
||||||
// main.ts). Always injected as NODE_EXTRA_CA_CERTS — required on v1.0.1+ where
|
// (see main.ts). Always injected as NODE_EXTRA_CA_CERTS, which overrides the
|
||||||
// the image entrypoint's setup-spacesops-env.sh references _SCRIPT_DIR without
|
// image's own default of /app/certs/startos-local-root-ca.pem — a CA baked in
|
||||||
// defining it under set -u.
|
// at build time that does not match this box.
|
||||||
export const nodeExtraCaCertVolumeSubpath = '.startos/startos-root-ca.crt'
|
export const nodeExtraCaCertVolumeSubpath = '.startos/startos-root-ca.crt'
|
||||||
export const nodeExtraCaCertContainerPath = `${dataDir}/.startos/startos-root-ca.crt`
|
export const nodeExtraCaCertContainerPath = `${dataDir}/.startos/startos-root-ca.crt`
|
||||||
|
|
||||||
@@ -51,6 +52,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,
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
|
export const current = VersionInfo.of({
|
||||||
|
version: '1.0.4:0',
|
||||||
|
releaseNotes: {
|
||||||
|
en_US: `Updates SpacesOps to v1.0.4 and rebuilds the package on the current StartOS packaging SDK.
|
||||||
|
|
||||||
|
- Upstream image is spacesops/spacesops:v1.0.4 on x86_64 and aarch64. The application itself is updated; the container entrypoint and Node runtime are unchanged from v1.0.3.
|
||||||
|
- Rebuilt against start-sdk 2.x, so this release requires StartOS 0.4.0-beta.10 or later.
|
||||||
|
- The install-time warning about the fixed admin credentials now lives in the service instructions, since StartOS no longer shows install alerts.
|
||||||
|
|
||||||
|
Your operator keypair, session secret, and Configure Platform settings are preserved.`,
|
||||||
|
},
|
||||||
|
migrations: {
|
||||||
|
up: async ({ effects }) => {},
|
||||||
|
down: IMPOSSIBLE,
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import { VersionGraph } from '@start9labs/start-sdk'
|
import { VersionGraph } from '@start9labs/start-sdk'
|
||||||
import { v_1_0_1_0 } from './v1.0.1.0'
|
import { current } from './current'
|
||||||
import { v_1_0_1_1 } from './v1.0.1.1'
|
|
||||||
|
|
||||||
export const versionGraph = VersionGraph.of({
|
export const versionGraph = VersionGraph.of({
|
||||||
current: v_1_0_1_1,
|
current,
|
||||||
other: [v_1_0_1_0],
|
other: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
+2
-9
@@ -1,11 +1,4 @@
|
|||||||
{
|
{
|
||||||
"include": ["startos/**/*.ts", "node_modules/**/startos"],
|
"extends": "@start9labs/start-sdk/tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"include": ["startos/**/*.ts", "node_modules/**/startos"]
|
||||||
"target": "ES2018",
|
|
||||||
"module": "CommonJS",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user