Update to @start9labs/start-sdk 2.0.9

Targets StartOS 0.4.0-beta.10 (was beta.9). Four breaking changes applied:

- Service interfaces moved onto their binding: sdk.serviceInterface.getOwn is
  gone, so nacho's EXPO_PUBLIC_API_BASE_URL now walks the subs-api interface off
  its host with sdk.host.getOwn, using the map selector so .const() re-runs only
  when that interface's address changes.
- SubContainer.of is lazy and synchronous — dropped the awaits.
- Task input split into accept/set; the Bitcoin RPC task accepts the credentials
  it sets, matching its previous behavior.
- The manifest alerts field was removed (ours was all null).

Also adopts the build plumbing the SDK now ships instead of vendoring it: the
Makefile includes node_modules/@start9labs/start-sdk/s9pk.mk, tsconfig extends
the SDK's base (ES2022, bundler resolution), and TypeScript moves to 6.0.

Verified by installing on a StartOS box: all daemons come up, nacho logs a
derived API base URL rather than the loopback fallback, and the sync health
check's space-cli exec runs on cadence against the shared subcontainer.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-30 11:48:09 -04:00
co-authored by Cursor
parent 196abdb860
commit b1d4890320
10 changed files with 1350 additions and 181 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
ARCHES := x86 ARCHES := x86
# 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
+1 -1
View File
@@ -125,7 +125,7 @@ mainnet-only.
| Subspaces Prover | 8888 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `subs-prover` RISC Zero prover server (no GPU). Its own StartOS interface. Optional HTTP basic auth — off by default, gated by **Enable Subspaces Prover Auth**. When on, `SUBS_PROVER_BASIC_AUTH_USER`/`SUBS_PROVER_BASIC_AUTH_PASSWORD` are enforced. Only useful while Subspaces (and the prover toggle) is enabled. | | Subspaces Prover | 8888 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `subs-prover` RISC Zero prover server (no GPU). Its own StartOS interface. Optional HTTP basic auth — off by default, gated by **Enable Subspaces Prover Auth**. When on, `SUBS_PROVER_BASIC_AUTH_USER`/`SUBS_PROVER_BASIC_AUTH_PASSWORD` are enforced. Only useful while Subspaces (and the prover toggle) is enabled. |
| Subspaces Registry | 8081 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `registry-server` for publishing/resolving handles (remapped from its upstream default 8080 to avoid colliding with the gotty terminal on 8080). Its own StartOS interface. Only useful while Subspaces is enabled. | | Subspaces Registry | 8081 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `registry-server` for publishing/resolving handles (remapped from its upstream default 8080 to avoid colliding with the gotty terminal on 8080). Its own StartOS interface. Only useful while Subspaces is enabled. |
| Certrelay | 7778 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `certrelay` server (always on). Serves cryptographic proofs binding handles to owner keys. **Setup:** run **Configure Certrelay** and set `CERTRELAY_SELF_URL` to the publicly visible URL StartOS exposes for this interface. | | Certrelay | 7778 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `certrelay` server (always on). Serves cryptographic proofs binding handles to owner keys. **Setup:** run **Configure Certrelay** and set `CERTRELAY_SELF_URL` to the publicly visible URL StartOS exposes for this interface. |
| Nacho | 8082 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | nacho Expo dev server (always on). The ignore list is read at runtime from `/data/nacho/ignore_names.txt` (managed by **Configure Nacho**). `EXPO_PUBLIC_API_BASE_URL` is derived dynamically from the Subs API StartOS interface (`sdk.serviceInterface.getOwn('subs-api').const()`), preferring the `.local` URL. Falls back to `http://127.0.0.1:7777` only if no address info is available. | | Nacho | 8082 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | nacho Expo dev server (always on). The ignore list is read at runtime from `/data/nacho/ignore_names.txt` (managed by **Configure Nacho**). `EXPO_PUBLIC_API_BASE_URL` is derived dynamically from the Subs API StartOS interface (walked off its host: `sdk.host.getOwn(effects, 'subspaces-multi', h => h?.bindings[7777]?.interfaces['subs-api']).const()`), preferring the `.local` URL. Falls back to `http://127.0.0.1:7777` only if no address info is available. |
| Spaces API (spaced RPC) | 7225 | HTTP JSON-RPC | LAN / `.local` / Tor / clearnet (via StartOS) | spaced JSON-RPC, bound to `0.0.0.0` and exposed as the **Spaces API** interface. Static-cred-authenticated via `SPACED_RPC_USER` / `SPACED_RPC_PASSWORD` from `store.json.spacedAuth`. Internal clients still connect over loopback. | | Spaces API (spaced RPC) | 7225 | HTTP JSON-RPC | LAN / `.local` / Tor / clearnet (via StartOS) | spaced JSON-RPC, bound to `0.0.0.0` and exposed as the **Spaces API** interface. Static-cred-authenticated via `SPACED_RPC_USER` / `SPACED_RPC_PASSWORD` from `store.json.spacedAuth`. Internal clients still connect over loopback. |
## Actions ## Actions
+14 -4
View File
@@ -1,4 +1,4 @@
# TODO — bring Spaces from beta.9 to # TODO — bring Spaces from beta.9 to beta.10
Consult the packaging guide as you go Consult the packaging guide as you go
(`start-technologies/projects/start-sdk/docs/src/recipes.md` is the intent index). Remove items as you finish (`start-technologies/projects/start-sdk/docs/src/recipes.md` is the intent index). Remove items as you finish
@@ -6,7 +6,9 @@ them, and add items when you defer work.
## The service ## The service
- [ ] Update @start9labs/start-sdk to the latest version. - [ ] Rename `startos/versions/v0.2.1.0.ts` to `startos/versions/current.ts` (the
guide keeps the latest version there; a dated file is only for a released
version that carries a migration).
## Docs ## Docs
@@ -16,8 +18,16 @@ them, and add items when you defer work.
## Build, test, ship ## Build, test, ship
- [ ] First test build: `make` (or `start-cli s9pk pack`); fix any `tsc` / pack errors. - [ ] Build for arm (`ARCHES := x86 arm` in the `Makefile`). All four images declare
`aarch64` in the manifest and every tag publishes an arm64 variant, so the
package currently advertises support it never ships. Needs an arm box to verify.
- [x] First test build: `make` (or `start-cli s9pk pack`); fix any `tsc` / pack errors.
- [ ] Install on a StartOS box and verify the service runs (and is reachable, once it - [ ] Install on a StartOS box and verify the service runs (and is reachable, once it
exposes an interface). exposes an interface). Under SDK 2.0 pay particular attention to nacho's
`EXPO_PUBLIC_API_BASE_URL` (now resolved by walking the subs-api host) and to
the Bitcoin RPC task raised on first install (`accept`/`set` input).
- [ ] `npx prettier --write startos` — 9 files predate the current prettier config and
fail `--check` (untouched by the SDK bump, so left out of that diff).
- [ ] Backup / restore sanity check. - [ ] Backup / restore sanity check.
- [ ] Review the README and instructions one more time against actual behavior. - [ ] Review the README and instructions one more time against actual behavior.
+1309 -13
View File
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -6,13 +6,16 @@
"check": "tsc --noEmit" "check": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@start9labs/start-sdk": "1.5.1" "@start9labs/start-sdk": "2.0.9"
},
"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",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"typescript": "^5.9.3" "typescript": "^6.0.3"
}, },
"prettier": { "prettier": {
"trailingComma": "all", "trailingComma": "all",
-132
View File
@@ -1,132 +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 ?= 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)
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
+2 -1
View File
@@ -22,7 +22,8 @@ export const taskBtcAuth = sdk.setupOnInit(async (effects) => {
reason: i18n('Spaces needs RPC credentials in Bitcoin'), reason: i18n('Spaces needs RPC credentials in Bitcoin'),
input: { input: {
kind: 'partial', kind: 'partial',
value: { username, password }, accept: [{ username, password }],
set: { username, password },
}, },
}) })
+16 -10
View File
@@ -105,21 +105,21 @@ export const main = sdk.setupMain(async ({ effects }) => {
readonly: false, readonly: false,
}) })
const spacedSub = await sdk.SubContainer.of( const spacedSub = sdk.SubContainer.of(
effects, effects,
{ imageId: 'spaces' }, { imageId: 'spaces' },
mounts, mounts,
'spaced-sub', 'spaced-sub',
) )
const termSub = await sdk.SubContainer.of( const termSub = sdk.SubContainer.of(
effects, effects,
{ imageId: 'spaces' }, { imageId: 'spaces' },
mounts, mounts,
'terminal-sub', 'terminal-sub',
) )
const subspacesSub = await sdk.SubContainer.of( const subspacesSub = sdk.SubContainer.of(
effects, effects,
{ imageId: 'subspaces' }, { imageId: 'subspaces' },
mounts, mounts,
@@ -183,7 +183,7 @@ export const main = sdk.setupMain(async ({ effects }) => {
RUST_LOG: 'registry_server=info', RUST_LOG: 'registry_server=info',
} }
const certrelaySub = await sdk.SubContainer.of( const certrelaySub = sdk.SubContainer.of(
effects, effects,
{ imageId: 'certrelay' }, { imageId: 'certrelay' },
mounts, mounts,
@@ -206,7 +206,7 @@ export const main = sdk.setupMain(async ({ effects }) => {
RUST_LOG: 'info', RUST_LOG: 'info',
} }
const nachoSub = await sdk.SubContainer.of( const nachoSub = sdk.SubContainer.of(
effects, effects,
{ imageId: 'nacho' }, { imageId: 'nacho' },
mounts, mounts,
@@ -217,11 +217,17 @@ export const main = sdk.setupMain(async ({ effects }) => {
// tracks the actual host:port StartOS exposes (instead of a hardcoded // tracks the actual host:port StartOS exposes (instead of a hardcoded
// domain). Prefer the mdns/.local URL — matches how a LAN browser reaches // domain). Prefer the mdns/.local URL — matches how a LAN browser reaches
// the device — then fall back to any non-local URL, then loopback. // the device — then fall back to any non-local URL, then loopback.
// `.const(effects)` makes this reactive: if the interface address changes // The interface is reached by walking its host ('subspaces-multi', the
// (clearnet enabled, Tor added, etc.), the service restarts and nacho // MultiHost that bound 7777 in interfaces.ts). `.const()` makes this
// picks up the new URL. // reactive, and the `map` selector narrows the watch to this one interface:
const subsApiIf = await sdk.serviceInterface // if its address changes (clearnet enabled, Tor added, etc.), the service
.getOwn(effects, 'subs-api') // restarts and nacho picks up the new URL.
const subsApiIf = await sdk.host
.getOwn(
effects,
'subspaces-multi',
(host) => host?.bindings[SUBSPACES_UI_PORT]?.interfaces['subs-api'],
)
.const() .const()
const subsApiCandidateUrls = subsApiIf?.addressInfo const subsApiCandidateUrls = subsApiIf?.addressInfo
? [ ? [
-8
View File
@@ -30,14 +30,6 @@ export const manifest = setupManifest({
arch: ['x86_64', 'aarch64'], arch: ['x86_64', 'aarch64'],
}, },
}, },
alerts: {
install: null,
update: null,
uninstall: null,
restore: null,
start: null,
stop: null,
},
dependencies: { dependencies: {
bitcoind: { bitcoind: {
description: depBitcoindDescription, description: depBitcoindDescription,
+2 -9
View File
@@ -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
}
} }