@@ -39,8 +39,10 @@ through a browser-based terminal.
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Spaces image | `docker.io/horologger/spaces` |
|
| Spaces image | `docker.io/horologger/spaces` |
|
||||||
| PostgreSQL image | `docker.io/postgres:16.3` |
|
| PostgreSQL image | `docker.io/postgres:16.3` |
|
||||||
| Indexer-Go image | `docker.io/golang:1.23-alpine` |
|
| Subspaces image | `docker.io/horologger/subs:v0.1.0` |
|
||||||
| Explorer-UI image | `docker.io/node:20-alpine` |
|
| Certrelay image | `docker.io/horologger/certrelay:v0.2.3` |
|
||||||
|
| Indexer image | _**TODO** — prebuilt explorer-indexer image, not yet produced_ |
|
||||||
|
| Explorer image | _**TODO** — prebuilt explorer (SvelteKit) image_ |
|
||||||
| Architectures | `linux/amd64`, `linux/arm64` |
|
| Architectures | `linux/amd64`, `linux/arm64` |
|
||||||
| Entrypoint | StartOS-managed (image entrypoints are **not** used directly) |
|
| Entrypoint | StartOS-managed (image entrypoints are **not** used directly) |
|
||||||
|
|
||||||
@@ -48,10 +50,15 @@ The Spaces image bundles `spaced`, `space-cli`, `bitcoin-cli`, `gotty`, `node`,
|
|||||||
`npm`, `screen`, and a small shell environment. StartOS ignores the Spaces
|
`npm`, `screen`, and a small shell environment. StartOS ignores the Spaces
|
||||||
image's `docker_entrypoint.sh`; daemons are defined in `startos/main.ts`. The
|
image's `docker_entrypoint.sh`; daemons are defined in `startos/main.ts`. The
|
||||||
PostgreSQL image is the upstream `postgres:16.3`, launched via its official
|
PostgreSQL image is the upstream `postgres:16.3`, launched via its official
|
||||||
`docker-entrypoint.sh`. The Indexer-Go image is the upstream
|
`docker-entrypoint.sh`.
|
||||||
`golang:1.23-alpine`, used both to compile the indexer's `sync` binary +
|
|
||||||
`goose` migrator (one-time, at install) and to run the compiled binaries on
|
> **Note (build-from-source removed):** earlier builds compiled the Go indexer
|
||||||
each start.
|
> and the SvelteKit explorer from source inside `golang:1.23-alpine` and
|
||||||
|
> `node:20-alpine` builder images. Those images and their build chains were
|
||||||
|
> removed to shrink the `.s9pk` and eliminate the multi-minute first-enable
|
||||||
|
> compile. They will be replaced with prebuilt images (see the **TODO** rows
|
||||||
|
> above). Until then, enabling the embedded explorer starts **PostgreSQL only**
|
||||||
|
> — the indexer and explorer-UI daemons are stubbed out in `startos/main.ts`.
|
||||||
|
|
||||||
## Volume and Data Layout
|
## Volume and Data Layout
|
||||||
|
|
||||||
@@ -61,14 +68,13 @@ each start.
|
|||||||
| `/data/mainnet/.cookie` | `main` | Spaced RPC cookie (auto-generated by `spaced` at startup) |
|
| `/data/mainnet/.cookie` | `main` | Spaced RPC cookie (auto-generated by `spaced` at startup) |
|
||||||
| `/data/store.json` | `main` | StartOS-managed credentials (web-UI password, bitcoind RPC user/password, PostgreSQL user/password/database) |
|
| `/data/store.json` | `main` | StartOS-managed credentials (web-UI password, bitcoind RPC user/password, PostgreSQL user/password/database) |
|
||||||
| `/data/postgres` | `main` | PostgreSQL data directory (`PGDATA`). Owned by uid 999 (`postgres`). The whole `main` volume is backed up, so the database is included. |
|
| `/data/postgres` | `main` | PostgreSQL data directory (`PGDATA`). Owned by uid 999 (`postgres`). The whole `main` volume is backed up, so the database is included. |
|
||||||
| `/data/explorer-indexer` | `main` | Spaces-protocol explorer-indexer source (`spacesprotocol/explorer-indexer`), fetched from GitHub on first start. Contains `cmd/sync/`, `pkg/`, `sql/schema/`, `go.mod`, etc. |
|
| `/data/explorer-indexer` | `main` | **TODO** — reserved for the prebuilt indexer's runtime data. The build-from-source layout (source, `bin/sync`, `bin/goose`, marker) is gone; `reset-indexer-state` still wipes this path. |
|
||||||
| `/data/explorer-indexer/bin/sync` | `main` | Compiled indexer sync binary (`go build ./cmd/sync`). |
|
| `/data/explorer-ui` | `main` | **TODO** — reserved for the prebuilt explorer's runtime data. The build-from-source layout (source, `build/`, `node_modules`, marker) is gone; `reset-explorer-state` still wipes this path. |
|
||||||
| `/data/explorer-indexer/bin/goose` | `main` | Compiled goose migrator (`go install github.com/pressly/goose/v3/cmd/goose`). Used by `indexer-migrate` to apply `sql/schema/*.sql`. |
|
| `/data/subspaces/data` | `main` | `subs` runtime data (`SUBS_DATA_DIR`) — handles, proofs, config.db. Persists across restarts and enable/disable toggles. |
|
||||||
| `/data/explorer-indexer/.installed-sha` | `main` | Marker file recording the pinned commit + build-ID currently extracted. If the build-ID in the package code differs from the marker, the next start wipes and re-fetches. |
|
| `/data/subspaces/prover` | `main` | Working directory for the `subs-prover` daemon. |
|
||||||
| `/data/explorer-ui` | `main` | SvelteKit explorer source (`randomlogin/explorer`), fetched on first explorer-enable. Contains `src/`, `static/`, `server.js`, `package.json`, etc. |
|
| `/data/subspaces/registry` | `main` | Working directory for the `registry-server` daemon. |
|
||||||
| `/data/explorer-ui/build` | `main` | SvelteKit node-adapter build output. `node build` (i.e. `node /data/explorer-ui/build`) runs the production server. |
|
| `/data/certrelay/data` | `main` | Certrelay data directory (`CERTRELAY_DATA_DIR`) — anchors, peer state, sqlite. |
|
||||||
| `/data/explorer-ui/node_modules` | `main` | npm dependencies (~300-500 MB). Re-fetched on `Reset Explorer UI State`. |
|
| `/data/bin/fabric` | `main` | The `fabric` CLI, copied from the certrelay image on each start so the gotty terminal (which has `/data/bin` on PATH) can resolve handles. |
|
||||||
| `/data/explorer-ui/.installed-sha` | `main` | Marker file for the pinned explorer commit + build-ID. |
|
|
||||||
|
|
||||||
## Installation and First-Run Flow
|
## Installation and First-Run Flow
|
||||||
|
|
||||||
@@ -92,8 +98,16 @@ On the first install, StartOS:
|
|||||||
gotty terminal's `spaces` alias and the indexer share a single auth path.
|
gotty terminal's `spaces` alias and the indexer share a single auth path.
|
||||||
6. Seeds `store.json.enableExplorer = false`. The embedded explorer
|
6. Seeds `store.json.enableExplorer = false`. The embedded explorer
|
||||||
(PostgreSQL + Go indexer) is **opt-in**.
|
(PostgreSQL + Go indexer) is **opt-in**.
|
||||||
7. Launches `spaced` as a managed daemon (no `screen`, no shell auto-start)
|
7. Seeds `store.json` certrelay defaults: `certrelaySelfUrl =
|
||||||
|
https://certrelay.spacesops.com`, `certrelayBootstrap = false`,
|
||||||
|
`certrelayHealthcheckHandle = account-digital-useful.genesis@key`.
|
||||||
|
8. Launches `spaced` as a managed daemon (no `screen`, no shell auto-start)
|
||||||
and the `gotty` web terminal once the bashrc oneshot completes.
|
and the `gotty` web terminal once the bashrc oneshot completes.
|
||||||
|
9. Runs `certrelay-setup` (creates `/data/certrelay/data`, copies the `fabric`
|
||||||
|
CLI to `/data/bin/fabric`) and launches the **certrelay** daemon on 7778.
|
||||||
|
Certrelay is **always on**; its `CERTRELAY_SPACED_RPC_URL` uses the local
|
||||||
|
spaced credentials from `store.spacedAuth`. Configure its self URL etc. via
|
||||||
|
the **Configure Certrelay** action.
|
||||||
|
|
||||||
The user is **not** prompted to choose a chain or RPC mode — this package is
|
The user is **not** prompted to choose a chain or RPC mode — this package is
|
||||||
mainnet-only.
|
mainnet-only.
|
||||||
@@ -101,31 +115,20 @@ mainnet-only.
|
|||||||
### Enabling the embedded explorer
|
### Enabling the embedded explorer
|
||||||
|
|
||||||
Run the **Enable Embedded Explorer** action. That flips
|
Run the **Enable Embedded Explorer** action. That flips
|
||||||
`store.json.enableExplorer = true` and the service auto-restarts with the full
|
`store.json.enableExplorer = true` and the service auto-restarts:
|
||||||
daemon graph:
|
|
||||||
|
|
||||||
8. Postgres-chown oneshot creates `/data/postgres` with correct ownership.
|
8. Postgres-chown oneshot creates `/data/postgres` with correct ownership.
|
||||||
9. PostgreSQL daemon starts on loopback `127.0.0.1:5432`.
|
9. PostgreSQL daemon starts on loopback `127.0.0.1:5432`.
|
||||||
10. `indexer-fetch` downloads the pinned `spacesprotocol/explorer-indexer`
|
|
||||||
tarball into `/data/explorer-indexer` and applies two compatibility
|
> **TODO (prebuilt images pending):** the indexer and explorer-UI daemons are
|
||||||
patches for our older spaced binary (see Limitations).
|
> currently **stubbed out** in `startos/main.ts`. The build-from-source chain
|
||||||
11. `indexer-build` runs `CGO_ENABLED=0 go build -o /data/explorer-indexer/bin/sync ./cmd/sync`
|
> (`indexer-fetch` → `indexer-build` → `indexer-migrate` → `indexer`, and
|
||||||
and `GOBIN=/data/explorer-indexer/bin go install github.com/pressly/goose/v3/cmd/goose@v3.24.3`.
|
> `explorer-fetch` → `explorer-install` → `explorer-ui`) was removed to shrink
|
||||||
12. `indexer-cleanup-legacy` checks for and removes leftover TS-indexer schema
|
> the `.s9pk` and cut startup time. Until prebuilt images are wired in,
|
||||||
if present (from earlier package versions).
|
> enabling the explorer starts **PostgreSQL only**; nothing populates the DB
|
||||||
13. `indexer-migrate` runs `goose ... up` to apply `sql/schema/*.sql`.
|
> and the Explorer Web UI (port 3000) will not resolve. The `show-db-credentials`,
|
||||||
14. `indexer` daemon (`/data/explorer-indexer/bin/sync`) starts polling
|
> `reset-db-state`, `reset-indexer-state`, and `reset-explorer-state` actions
|
||||||
bitcoind + spaced and writes blocks, transactions, spaces, rollouts, etc.
|
> remain available.
|
||||||
to PostgreSQL.
|
|
||||||
15. `explorer-fetch` downloads the pinned `randomlogin/explorer` tarball into
|
|
||||||
`/data/explorer-ui` (skipped if marker already matches).
|
|
||||||
16. `explorer-install` runs `npm install` + `PUBLIC_BTC_NETWORK=mainnet npm run build`
|
|
||||||
to produce `/data/explorer-ui/build/`. Skipped if `build/index.js` already
|
|
||||||
exists. First-time install can take 1-3 minutes for the npm download.
|
|
||||||
17. `explorer-ui` daemon (`node /data/explorer-ui/build`) starts on port 3000.
|
|
||||||
The SvelteKit app reads exclusively from PostgreSQL — it does not talk to
|
|
||||||
spaced or bitcoind directly. The UI link will appear in the StartOS
|
|
||||||
dashboard alongside the gotty terminal.
|
|
||||||
|
|
||||||
Run **Disable Embedded Explorer** to turn it back off; on-disk data at
|
Run **Disable Embedded Explorer** to turn it back off; on-disk data at
|
||||||
`/data/postgres`, `/data/explorer-indexer`, and `/data/explorer-ui` is
|
`/data/postgres`, `/data/explorer-indexer`, and `/data/explorer-ui` is
|
||||||
@@ -146,7 +149,11 @@ preserved.
|
|||||||
| Interface | Port | Protocol | Exposure | Notes |
|
| Interface | Port | Protocol | Exposure | Notes |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| Space-CLI Web UI (gotty terminal) | 8080 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | Basic auth: `admin:<store.password>` |
|
| Space-CLI Web UI (gotty terminal) | 8080 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | Basic auth: `admin:<store.password>` |
|
||||||
| Explorer Web UI | 3000 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | SvelteKit explorer. No built-in auth — relies on StartOS interface exposure controls. Only useful while the embedded explorer is enabled (otherwise nothing is listening). |
|
| Explorer Web UI | 3000 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | SvelteKit explorer. **TODO** — daemon stubbed pending the prebuilt explorer image; the interface is still registered but nothing listens yet. |
|
||||||
|
| Subspaces Web UI | 7777 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `subs` UI. No built-in auth. Only useful while Subspaces is enabled. |
|
||||||
|
| Subspaces Prover | 8888 | HTTP | LAN / `.local` / Tor / clearnet (via StartOS) | `subs-prover` RISC Zero prover server (no GPU). 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. |
|
||||||
| Spaced RPC | 7225 | HTTP JSON-RPC | **Loopback only** | Internal use, static-cred-authenticated via `SPACED_RPC_USER` / `SPACED_RPC_PASSWORD` from `store.json.spacedAuth` |
|
| Spaced RPC | 7225 | HTTP JSON-RPC | **Loopback only** | Internal use, static-cred-authenticated via `SPACED_RPC_USER` / `SPACED_RPC_PASSWORD` from `store.json.spacedAuth` |
|
||||||
| PostgreSQL | 5432 | Postgres wire protocol | **Loopback only** (`listen_addresses=127.0.0.1`) | Username/password from `store.json.dbAuth`; full `DB_URL` exported into the web terminal |
|
| PostgreSQL | 5432 | Postgres wire protocol | **Loopback only** (`listen_addresses=127.0.0.1`) | Username/password from `store.json.dbAuth`; full `DB_URL` exported into the web terminal |
|
||||||
|
|
||||||
@@ -168,6 +175,10 @@ preserved.
|
|||||||
| `reset-db-state` | Reset Database State | Enabled | Any | Deletes `/data/postgres` so the next start re-initializes an empty database. Warning-gated. `store.json` is preserved. |
|
| `reset-db-state` | Reset Database State | Enabled | Any | Deletes `/data/postgres` so the next start re-initializes an empty database. Warning-gated. `store.json` is preserved. |
|
||||||
| `reset-indexer-state` | Reset Indexer State | Enabled | Any | Deletes `/data/explorer-indexer` so the next start re-fetches the indexer source and rebuilds the sync + goose binaries. Useful when bumping the pinned commit. PostgreSQL data is preserved. Warning-gated. |
|
| `reset-indexer-state` | Reset Indexer State | Enabled | Any | Deletes `/data/explorer-indexer` so the next start re-fetches the indexer source and rebuilds the sync + goose binaries. Useful when bumping the pinned commit. PostgreSQL data is preserved. Warning-gated. |
|
||||||
| `reset-explorer-state` | Reset Explorer UI State | Enabled | Any | Deletes `/data/explorer-ui` so the next start re-fetches the SvelteKit source and rebuilds the bundle. PostgreSQL data is preserved. Warning-gated. |
|
| `reset-explorer-state` | Reset Explorer UI State | Enabled | Any | Deletes `/data/explorer-ui` so the next start re-fetches the SvelteKit source and rebuilds the bundle. PostgreSQL data is preserved. Warning-gated. |
|
||||||
|
| `enable-subspaces` | Enable Subspaces | Enabled (hidden when already on) | Any | Sets `store.enableSubspaces = true` and triggers a service restart so the Subspaces chain (build → wallet → subs-prover → subs) takes effect. |
|
||||||
|
| `disable-subspaces` | Disable Subspaces | Enabled (hidden when already off) | Any | Sets `store.enableSubspaces = false` and triggers a service restart so the subs and subs-prover daemons stop. On-disk data at `/data/subspaces` is preserved. |
|
||||||
|
| `reset-subspaces-state` | Reset Subspaces State | Enabled | Any | Deletes `/data/subspaces` (runtime data only — binaries ship in the image). Use to wipe local handles/proofs. The spaces wallet on spaced is preserved. Warning-gated. |
|
||||||
|
| `configure-certrelay` | Configure Certrelay | Enabled | Any | Sets `CERTRELAY_SELF_URL` (text), `CERTRELAY_BOOTSTRAP` (toggle), and `CERTRELAY_HEALTHCHECK_HANDLE` (text) in `store.json`; prefilled with current values. Saving restarts the service so certrelay picks up the new config. |
|
||||||
|
|
||||||
## Backups and Restore
|
## Backups and Restore
|
||||||
|
|
||||||
@@ -182,11 +193,13 @@ the same idempotent init logic runs and reuses the existing credentials in
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `postgres` (daemon `ready`) | Database | 60 s | TCP listen on `127.0.0.1:5432` |
|
| `postgres` (daemon `ready`) | Database | 60 s | TCP listen on `127.0.0.1:5432` |
|
||||||
| `spaced` (daemon `ready`) | Spaced RPC | 120 s | TCP listen on `127.0.0.1:7225` |
|
| `spaced` (daemon `ready`) | Spaced RPC | 120 s | TCP listen on `127.0.0.1:7225` |
|
||||||
| `indexer` (daemon `ready`) | Indexer Process | 60 s | Daemon liveness only — health is reported by `indexer-sync` below |
|
| `subs-prover` (daemon `ready`) | Subspaces Prover | 60 s | TCP listen on `0.0.0.0:8888`. Only present when Subspaces is enabled. |
|
||||||
| `explorer-ui` (daemon `ready`) | Explorer Web UI | 60 s | TCP listen on `0.0.0.0:3000`. Only present when embedded explorer is enabled. |
|
| `subs-registry` (daemon `ready`) | Subspaces Registry | 60 s | TCP listen on `0.0.0.0:8081`. Only present when Subspaces is enabled. |
|
||||||
|
| `subs` (daemon `ready`) | Subspaces Web UI | 60 s | TCP listen on `0.0.0.0:7777`. Only present when Subspaces is enabled. |
|
||||||
|
| `certrelay` (daemon `ready`) | Certrelay | 120 s | TCP listen on `0.0.0.0:7778`. Always on. Polled every 30 s. |
|
||||||
| `web-terminal` (daemon `ready`) | Web Interface | default | TCP listen on `0.0.0.0:8080` |
|
| `web-terminal` (daemon `ready`) | Web Interface | default | TCP listen on `0.0.0.0:8080` |
|
||||||
| `sync` (standalone) | Spaced Sync | 30 s | Exec `space-cli --output-format json getserverinfo`; reports `success` when `ready=true && progress=100%`, otherwise `loading` with progress percentage |
|
| `sync` (standalone) | Spaced Sync | 30 s | Exec `space-cli --output-format json getserverinfo`; reports `success` when `ready=true && progress=100%`, otherwise `loading` with progress percentage |
|
||||||
| `indexer-sync` (standalone) | Indexer Sync | 60 s | `psql` the `blocks` table for `MAX(height) WHERE orphan = FALSE AND height >= 0`; cross-check against spaced's current tip via `space-cli getserverinfo`. `success` when within 5 blocks of the tip; otherwise `loading` with explicit lag. |
|
| `indexer` / `explorer-ui` / `indexer-sync` | — | — | **TODO** — removed with the build-from-source chain. Will return when the prebuilt indexer + explorer images are wired in. |
|
||||||
|
|
||||||
## Limitations and Differences
|
## Limitations and Differences
|
||||||
|
|
||||||
@@ -195,44 +208,52 @@ the same idempotent init logic runs and reuses the existing credentials in
|
|||||||
`spaced` inside `screen` would conflict with the StartOS-managed daemon.
|
`spaced` inside `screen` would conflict with the StartOS-managed daemon.
|
||||||
3. **Space-CLI Web UI is a terminal, not a graphical app.** All Spaces operations happen
|
3. **Space-CLI Web UI is a terminal, not a graphical app.** All Spaces operations happen
|
||||||
via `space-cli` (aliased as `spaces` inside the shell).
|
via `space-cli` (aliased as `spaces` inside the shell).
|
||||||
4. **Only port 8080 (gotty) is exposed externally.** Spaced RPC (7225) and
|
4. **Externally exposed ports** (each a StartOS interface): 8080 (gotty
|
||||||
PostgreSQL (5432) are loopback-only, and any other ports present in the
|
Space-CLI Web UI, always on), 7778 (Certrelay, always on), and — only while
|
||||||
image (e.g. 22253, 3000, 5173, 8081) are not bound.
|
their feature is enabled — 3000 (Explorer Web UI, currently stubbed), 7777
|
||||||
|
(Subspaces Web UI), 8888 (Subspaces Prover), 8081 (Subspaces Registry).
|
||||||
|
**Loopback-only:** spaced RPC (7225) and PostgreSQL (5432). The Subspaces
|
||||||
|
registry is remapped from its upstream default 8080 to 8081 to avoid
|
||||||
|
colliding with the gotty terminal.
|
||||||
5. **Bitcoin Core 31.x is the only supported dependency.** Earlier majors are
|
5. **Bitcoin Core 31.x is the only supported dependency.** Earlier majors are
|
||||||
not allowed by the manifest version range.
|
not allowed by the manifest version range.
|
||||||
6. **The web terminal is independent of spaced.** Gotty stays reachable even
|
6. **The web terminal is independent of spaced.** Gotty stays reachable even
|
||||||
when `spaced` is crash-looping, so you can always shell in to diagnose.
|
when `spaced` is crash-looping, so you can always shell in to diagnose.
|
||||||
7. **The embedded explorer is opt-in.** Fresh installs run in spaces-only
|
7. **The embedded explorer is opt-in — and currently PostgreSQL-only.** Fresh
|
||||||
mode (spaced + gotty). Run **Enable Embedded Explorer** to start
|
installs run in spaces-only mode (spaced + gotty). Run **Enable Embedded
|
||||||
PostgreSQL + the Go indexer; **Disable Embedded Explorer** stops them. The
|
Explorer** to start PostgreSQL; **Disable Embedded Explorer** stops it. The
|
||||||
`postgres` and `indexer-go` images are bundled in the .s9pk regardless so
|
indexer and explorer-UI daemons are **stubbed out** pending prebuilt images
|
||||||
toggling is instant, no re-download. Indexed data is preserved across
|
(the build-from-source chain was removed to shrink the `.s9pk` and cut
|
||||||
toggles.
|
startup time). So enabling the explorer today brings up an empty database
|
||||||
|
and nothing else. PostgreSQL data is preserved across toggles.
|
||||||
8. **PostgreSQL is embedded, not a separate service.** There is no
|
8. **PostgreSQL is embedded, not a separate service.** There is no
|
||||||
cross-service dependency on a postgres .s9pk; the database is local to this
|
cross-service dependency on a postgres .s9pk; the database is local to this
|
||||||
package, lives at `/data/postgres`, and is intended for the embedded
|
package, lives at `/data/postgres`, and is intended for the (pending)
|
||||||
indexer (and any future explorer UI). Connect to it from inside the gotty
|
prebuilt indexer + explorer UI. Connect to it from inside the gotty
|
||||||
terminal with `psql $DB_URL` (only meaningful while the explorer is
|
terminal with `psql $DB_URL`.
|
||||||
enabled).
|
9. **Subspaces pointers data note.** When the prebuilt indexer is wired back
|
||||||
9. **Enabling the explorer requires internet on its first start.** The pinned
|
in, be aware our spaced binary predates the spaces pointers feature; the
|
||||||
commit of `spacesprotocol/explorer-indexer` is downloaded from GitHub into
|
prior build-from-source indexer was patched to treat `ptrs_root` as optional
|
||||||
`/data/explorer-indexer`, and `go build` + `go install github.com/pressly/goose/v3/cmd/goose`
|
and to skip the `getptrblockmeta` RPC. A future rebuild of
|
||||||
fetch Go modules. After that the indexer runs offline; the only network it
|
`horologger/spaces` with a newer spaced will let us drop those patches.
|
||||||
needs is bitcoind (already a StartOS dependency) and the local spaced RPC.
|
10. **Subspaces is opt-in.** Fresh installs do not run Subspaces. Run **Enable
|
||||||
10. **Explorer first-enable latency is ~2-5 minutes.** The Go indexer needs to
|
Subspaces** to start it. It ships as the prebuilt `horologger/subs:v0.1.0`
|
||||||
download modules and compile from scratch the first time you turn it on.
|
image (no compile step), so enabling is fast — the three daemons start as
|
||||||
Subsequent restarts (and toggle off→on) skip `indexer-fetch` +
|
soon as the image is on the box. Enabling runs three daemons, each with its
|
||||||
`indexer-build` because the binaries already exist on the volume. Use the
|
own StartOS interface: **subs** (Web UI, 7777), **subs-prover** (8888, no
|
||||||
**Reset Indexer State** action to force a rebuild.
|
GPU), **registry-server** (8081). subs loads the existing `default` spaces
|
||||||
11. **Subspaces pointers data is not indexed.** Our spaced binary predates the
|
wallet at startup and does **not** create it — create it first from the
|
||||||
pointers feature. The indexer source is patched during `indexer-fetch` to
|
Space-CLI Web UI with `spaces createwallet` if you haven't. Subspaces is
|
||||||
treat `ptrs_root` as optional and to skip the `getptrblockmeta` RPC call.
|
independent of the Embedded Explorer toggle — you can run any combination
|
||||||
All other spaces protocol data — blocks, transactions, spaces, rollouts,
|
of {explorer, subspaces}.
|
||||||
root anchors with empty pointers root — indexes normally. A future
|
11. **Certrelay is always on.** It ships as the prebuilt
|
||||||
rebuild of `horologger/spaces` with a newer spaced will let us drop these
|
`horologger/certrelay:v0.2.3` image (static musl binaries) and runs on
|
||||||
patches.
|
7778 from service start. It talks to the local spaced over loopback using
|
||||||
|
the `store.spacedAuth` credentials. The bundled `fabric` CLI is copied to
|
||||||
## What Is Unchanged from Upstream
|
`/data/bin/fabric` so it's runnable from the Space-CLI Web UI. **You must
|
||||||
|
set `CERTRELAY_SELF_URL`** (via **Configure Certrelay**) to the publicly
|
||||||
|
visible URL StartOS exposes for the Certrelay interface, or peers/clients
|
||||||
|
won't be able to reach this relay at the advertised address.
|
||||||
|
|
||||||
- `space-cli` subcommands and flags work exactly as documented upstream.
|
- `space-cli` subcommands and flags work exactly as documented upstream.
|
||||||
- `spaced` honours all `SPACED_*` environment variables not otherwise set by
|
- `spaced` honours all `SPACED_*` environment variables not otherwise set by
|
||||||
@@ -256,8 +277,8 @@ Examples:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
spaces getserverinfo
|
spaces getserverinfo
|
||||||
spaces walletcreate default
|
spaces createwallet # creates the `default` wallet (--wallet defaults to "default")
|
||||||
spaces walletbalance default
|
spaces balance
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Reference for AI Consumers
|
## Quick Reference for AI Consumers
|
||||||
@@ -268,14 +289,20 @@ upstream_version: subspacesplus
|
|||||||
images:
|
images:
|
||||||
spaces: docker.io/horologger/spaces:v0.0.9s
|
spaces: docker.io/horologger/spaces:v0.0.9s
|
||||||
postgres: docker.io/postgres:16.3
|
postgres: docker.io/postgres:16.3
|
||||||
indexer-go: docker.io/golang:1.23-alpine
|
subspaces: docker.io/horologger/subs:v0.1.0
|
||||||
explorer-ui: docker.io/node:20-alpine
|
certrelay: docker.io/horologger/certrelay:v0.2.3
|
||||||
|
# TODO: prebuilt indexer image (was golang:1.23-alpine builder, removed)
|
||||||
|
# TODO: prebuilt explorer image (was node:20-alpine builder, removed)
|
||||||
architectures: [x86_64, aarch64]
|
architectures: [x86_64, aarch64]
|
||||||
volumes:
|
volumes:
|
||||||
main: /data
|
main: /data
|
||||||
ports:
|
ports:
|
||||||
ui: 8080
|
ui: 8080
|
||||||
explorer_ui: 3000 # external, only useful while explorer enabled
|
explorer_ui: 3000 # interface registered; daemon stubbed (TODO prebuilt image)
|
||||||
|
subspaces_ui: 7777 # external, only useful while subspaces enabled
|
||||||
|
subspaces_prover: 8888 # external (its own interface), subspaces only
|
||||||
|
subspaces_registry: 8081 # external (its own interface), subspaces only; remapped from upstream 8080
|
||||||
|
certrelay: 7778 # external (its own interface), always on
|
||||||
spaced_rpc: 7225 # loopback only
|
spaced_rpc: 7225 # loopback only
|
||||||
postgres: 5432 # loopback only
|
postgres: 5432 # loopback only
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -307,27 +334,45 @@ postgres_env_vars:
|
|||||||
explorer:
|
explorer:
|
||||||
default_enabled: false # opt-in via Enable Embedded Explorer action
|
default_enabled: false # opt-in via Enable Embedded Explorer action
|
||||||
store_field: enableExplorer
|
store_field: enableExplorer
|
||||||
bundled_when_disabled: true # images stay in .s9pk; data on /data preserved
|
status: postgres_only # indexer + explorer-ui daemons stubbed; see TODOs
|
||||||
|
reset_actions: [reset-db-state, reset-indexer-state, reset-explorer-state]
|
||||||
indexer:
|
indexer:
|
||||||
source_repo: spacesprotocol/explorer-indexer
|
status: TODO # build-from-source (Go) removed; prebuilt image pending
|
||||||
pinned_commit: 00ae1e548734d93f1a8bb9f48d2290f459e12b35
|
prior_source_repo: spacesprotocol/explorer-indexer
|
||||||
install_dir: /data/explorer-indexer
|
data_dir: /data/explorer-indexer # reset-indexer-state still wipes this
|
||||||
bin_dir: /data/explorer-indexer/bin
|
|
||||||
schema_dir: /data/explorer-indexer/sql/schema
|
|
||||||
language: Go
|
|
||||||
build_image: golang:1.23-alpine
|
|
||||||
migrations_tool: goose
|
|
||||||
goose_version: v3.24.3
|
|
||||||
explorer_ui:
|
explorer_ui:
|
||||||
source_repo: randomlogin/explorer
|
status: TODO # build-from-source (SvelteKit) removed; prebuilt image pending
|
||||||
pinned_commit: c827da1754c3cba5c5507d2c29f21b8fa231344d
|
prior_source_repo: randomlogin/explorer
|
||||||
install_dir: /data/explorer-ui
|
data_dir: /data/explorer-ui # reset-explorer-state still wipes this
|
||||||
build_dir: /data/explorer-ui/build
|
port: 3000 # interface registered; daemon stubbed
|
||||||
language: TypeScript (SvelteKit)
|
subspaces:
|
||||||
build_image: node:20-alpine
|
default_enabled: false # opt-in via Enable Subspaces action
|
||||||
build_command: PUBLIC_BTC_NETWORK=mainnet npm run build
|
store_field: enableSubspaces
|
||||||
daemon_command: node /data/explorer-ui/build
|
image: horologger/subs:v0.1.0 # prebuilt; no compile step
|
||||||
port: 3000
|
data_dir: /data/subspaces/data
|
||||||
|
wallet: default # loaded by subs, NOT created (create via space-cli first)
|
||||||
|
daemons: # three separate daemons, each its own interface
|
||||||
|
subs: { bin: /usr/local/bin/subs, port: 7777 }
|
||||||
|
subs-prover: { bin: /usr/local/bin/subs-prover, port: 8888, gpu: none }
|
||||||
|
registry-server: { bin: /usr/local/bin/registry-server, port: 8081 } # remapped from upstream 8080
|
||||||
|
certrelay:
|
||||||
|
always_on: true
|
||||||
|
image: horologger/certrelay:v0.2.3 # prebuilt; static musl binaries
|
||||||
|
server_bin: /usr/local/bin/certrelay
|
||||||
|
fabric_bin: /usr/local/bin/fabric # copied to /data/bin/fabric for the CLI
|
||||||
|
data_dir: /data/certrelay/data
|
||||||
|
port: 7778
|
||||||
|
spaced_rpc_creds: from store.spacedAuth (local spaced)
|
||||||
|
configurable_via_action: configure-certrelay
|
||||||
|
env:
|
||||||
|
CERTRELAY_CHAIN: mainnet
|
||||||
|
CERTRELAY_BIND: 0.0.0.0
|
||||||
|
CERTRELAY_PORT: '7778'
|
||||||
|
CERTRELAY_REMOTE_IP_HEADER: x-forwarded-for
|
||||||
|
CERTRELAY_ANCHOR_REFRESH: '300'
|
||||||
|
CERTRELAY_SELF_URL: https://certrelay.spacesops.com # default; set to your public URL
|
||||||
|
CERTRELAY_BOOTSTRAP: 'false' # toggle via action
|
||||||
|
CERTRELAY_HEALTHCHECK_HANDLE: account-digital-useful.genesis@key # via action
|
||||||
managed_env_vars:
|
managed_env_vars:
|
||||||
- POSTGRES_URI
|
- POSTGRES_URI
|
||||||
- BITCOIN_NODE_URI
|
- BITCOIN_NODE_URI
|
||||||
@@ -360,4 +405,8 @@ actions:
|
|||||||
- reset-db-state
|
- reset-db-state
|
||||||
- reset-indexer-state
|
- reset-indexer-state
|
||||||
- reset-explorer-state
|
- reset-explorer-state
|
||||||
|
- enable-subspaces
|
||||||
|
- disable-subspaces
|
||||||
|
- reset-subspaces-state
|
||||||
|
- configure-certrelay
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { storeJson } from '../fileModels/storeJson'
|
||||||
|
import { i18n } from '../i18n'
|
||||||
|
import { sdk } from '../sdk'
|
||||||
|
import {
|
||||||
|
CERTRELAY_DEFAULT_BOOTSTRAP,
|
||||||
|
CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE,
|
||||||
|
CERTRELAY_DEFAULT_SELF_URL,
|
||||||
|
} from '../utils'
|
||||||
|
|
||||||
|
const { InputSpec, Value } = sdk
|
||||||
|
|
||||||
|
const inputSpec = InputSpec.of({
|
||||||
|
certrelaySelfUrl: Value.text({
|
||||||
|
name: i18n('Certrelay Self URL'),
|
||||||
|
description: i18n(
|
||||||
|
'The publicly visible URL clients use to reach this certrelay (CERTRELAY_SELF_URL). Set this to match the external address StartOS exposes for the Certrelay interface (e.g. your .onion or clearnet domain).',
|
||||||
|
),
|
||||||
|
warning: null,
|
||||||
|
footnote: null,
|
||||||
|
default: CERTRELAY_DEFAULT_SELF_URL,
|
||||||
|
required: true,
|
||||||
|
masked: false,
|
||||||
|
placeholder: 'https://certrelay.example.com',
|
||||||
|
minLength: 1,
|
||||||
|
maxLength: null,
|
||||||
|
}),
|
||||||
|
certrelayBootstrap: Value.toggle({
|
||||||
|
name: i18n('Certrelay Bootstrap'),
|
||||||
|
description: i18n(
|
||||||
|
'Whether this relay bootstraps from peer relays on startup (CERTRELAY_BOOTSTRAP).',
|
||||||
|
),
|
||||||
|
warning: null,
|
||||||
|
footnote: null,
|
||||||
|
default: CERTRELAY_DEFAULT_BOOTSTRAP,
|
||||||
|
}),
|
||||||
|
certrelayHealthcheckHandle: Value.text({
|
||||||
|
name: i18n('Certrelay Healthcheck Handle'),
|
||||||
|
description: i18n(
|
||||||
|
'A handle the bundled fabric CLI resolves against this relay to verify end-to-end health (CERTRELAY_HEALTHCHECK_HANDLE).',
|
||||||
|
),
|
||||||
|
warning: null,
|
||||||
|
footnote: null,
|
||||||
|
default: CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE,
|
||||||
|
required: true,
|
||||||
|
masked: false,
|
||||||
|
placeholder: 'account-digital-useful.genesis@key',
|
||||||
|
minLength: 1,
|
||||||
|
maxLength: null,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const configureCertrelay = sdk.Action.withInput(
|
||||||
|
// id
|
||||||
|
'configure-certrelay',
|
||||||
|
|
||||||
|
// metadata
|
||||||
|
async ({ effects }) => ({
|
||||||
|
name: i18n('Configure Certrelay'),
|
||||||
|
description: i18n(
|
||||||
|
'Set the certrelay self URL, bootstrap toggle, and healthcheck handle. Saving restarts the service so certrelay picks up the new values.',
|
||||||
|
),
|
||||||
|
warning: null,
|
||||||
|
allowedStatuses: 'any',
|
||||||
|
group: null,
|
||||||
|
visibility: 'enabled',
|
||||||
|
}),
|
||||||
|
|
||||||
|
// input
|
||||||
|
inputSpec,
|
||||||
|
|
||||||
|
// prefill — load current values from store
|
||||||
|
async ({ effects }) => {
|
||||||
|
const store = await storeJson.read().once()
|
||||||
|
return {
|
||||||
|
certrelaySelfUrl: store?.certrelaySelfUrl ?? CERTRELAY_DEFAULT_SELF_URL,
|
||||||
|
certrelayBootstrap:
|
||||||
|
store?.certrelayBootstrap ?? CERTRELAY_DEFAULT_BOOTSTRAP,
|
||||||
|
certrelayHealthcheckHandle:
|
||||||
|
store?.certrelayHealthcheckHandle ??
|
||||||
|
CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// run
|
||||||
|
async ({ effects, input }) => {
|
||||||
|
await storeJson.merge(effects, {
|
||||||
|
certrelaySelfUrl: input.certrelaySelfUrl,
|
||||||
|
certrelayBootstrap: input.certrelayBootstrap,
|
||||||
|
certrelayHealthcheckHandle: input.certrelayHealthcheckHandle,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: '1',
|
||||||
|
title: i18n('Success'),
|
||||||
|
message: i18n(
|
||||||
|
'Certrelay configuration saved. The service is restarting to apply the new settings.',
|
||||||
|
),
|
||||||
|
result: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { storeJson } from '../fileModels/storeJson'
|
||||||
|
import { i18n } from '../i18n'
|
||||||
|
import { sdk } from '../sdk'
|
||||||
|
|
||||||
|
export const disableSubspaces = sdk.Action.withoutInput(
|
||||||
|
// id
|
||||||
|
'disable-subspaces',
|
||||||
|
|
||||||
|
// metadata
|
||||||
|
async ({ effects }) => {
|
||||||
|
const current = await storeJson.read((s) => s.enableSubspaces).once()
|
||||||
|
return {
|
||||||
|
name: i18n('Disable Subspaces'),
|
||||||
|
description: i18n(
|
||||||
|
'Stop the embedded Subspaces service. The compiled binaries and data at /data/subspaces are preserved and will be reused if Subspaces is re-enabled. Service restarts automatically.',
|
||||||
|
),
|
||||||
|
warning: null,
|
||||||
|
allowedStatuses: 'any',
|
||||||
|
group: null,
|
||||||
|
visibility: current === true ? 'enabled' : 'hidden',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// run
|
||||||
|
async ({ effects }) => {
|
||||||
|
await storeJson.merge(effects, { enableSubspaces: false })
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: '1',
|
||||||
|
title: i18n('Success'),
|
||||||
|
message: i18n(
|
||||||
|
'Subspaces disabled. The service is restarting; the Subspaces Web UI is no longer listening. Run "Enable Subspaces" to turn it back on.',
|
||||||
|
),
|
||||||
|
result: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { storeJson } from '../fileModels/storeJson'
|
||||||
|
import { i18n } from '../i18n'
|
||||||
|
import { sdk } from '../sdk'
|
||||||
|
|
||||||
|
export const enableSubspaces = sdk.Action.withoutInput(
|
||||||
|
// id
|
||||||
|
'enable-subspaces',
|
||||||
|
|
||||||
|
// metadata
|
||||||
|
async ({ effects }) => {
|
||||||
|
const current = await storeJson.read((s) => s.enableSubspaces).once()
|
||||||
|
return {
|
||||||
|
name: i18n('Enable Subspaces'),
|
||||||
|
description: i18n(
|
||||||
|
'Turn on the embedded Subspaces service (off-chain Bitcoin handles). Starts three prebuilt daemons — subs (Web UI), subs-prover, and registry-server — each on its own interface. Service restarts automatically. subs loads the existing `default` spaces wallet; create it first via the Space-CLI Web UI (`spaces createwallet`) if you have not already.',
|
||||||
|
),
|
||||||
|
warning: null,
|
||||||
|
allowedStatuses: 'any',
|
||||||
|
group: null,
|
||||||
|
visibility: current === true ? 'hidden' : 'enabled',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// run
|
||||||
|
async ({ effects }) => {
|
||||||
|
await storeJson.merge(effects, { enableSubspaces: true })
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: '1',
|
||||||
|
title: i18n('Success'),
|
||||||
|
message: i18n(
|
||||||
|
'Subspaces enabled. The service is restarting; the Subspaces Web UI (7777), Prover (8888), and Registry (8081) interfaces come up once their daemons are healthy.',
|
||||||
|
),
|
||||||
|
result: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import { sdk } from '../sdk'
|
import { sdk } from '../sdk'
|
||||||
|
import { configureCertrelay } from './configureCertrelay'
|
||||||
import { disableExplorer } from './disableExplorer'
|
import { disableExplorer } from './disableExplorer'
|
||||||
|
import { disableSubspaces } from './disableSubspaces'
|
||||||
import { enableExplorer } from './enableExplorer'
|
import { enableExplorer } from './enableExplorer'
|
||||||
|
import { enableSubspaces } from './enableSubspaces'
|
||||||
import { exportWallet } from './exportWallet'
|
import { exportWallet } from './exportWallet'
|
||||||
import { importWallet } from './importWallet'
|
import { importWallet } from './importWallet'
|
||||||
import { resetDbState } from './resetDbState'
|
import { resetDbState } from './resetDbState'
|
||||||
@@ -8,6 +11,7 @@ import { resetExplorerState } from './resetExplorerState'
|
|||||||
import { resetIndexerState } from './resetIndexerState'
|
import { resetIndexerState } from './resetIndexerState'
|
||||||
import { resetPassword } from './resetPassword'
|
import { resetPassword } from './resetPassword'
|
||||||
import { resetSpacedState } from './resetSpacedState'
|
import { resetSpacedState } from './resetSpacedState'
|
||||||
|
import { resetSubspacesState } from './resetSubspacesState'
|
||||||
import { setBitcoinRpc } from './setBitcoinRpc'
|
import { setBitcoinRpc } from './setBitcoinRpc'
|
||||||
import { showCredentials } from './showCredentials'
|
import { showCredentials } from './showCredentials'
|
||||||
import { showDbCredentials } from './showDbCredentials'
|
import { showDbCredentials } from './showDbCredentials'
|
||||||
@@ -29,3 +33,7 @@ export const actions = sdk.Actions.of()
|
|||||||
.addAction(resetDbState)
|
.addAction(resetDbState)
|
||||||
.addAction(resetIndexerState)
|
.addAction(resetIndexerState)
|
||||||
.addAction(resetExplorerState)
|
.addAction(resetExplorerState)
|
||||||
|
.addAction(enableSubspaces)
|
||||||
|
.addAction(disableSubspaces)
|
||||||
|
.addAction(resetSubspacesState)
|
||||||
|
.addAction(configureCertrelay)
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ export const resetExplorerState = sdk.Action.withoutInput(
|
|||||||
async ({ effects }) => {
|
async ({ effects }) => {
|
||||||
const res = await sdk.SubContainer.withTemp(
|
const res = await sdk.SubContainer.withTemp(
|
||||||
effects,
|
effects,
|
||||||
{ imageId: 'explorer-ui' },
|
// TODO(prebuilt-explorer): switch back to the explorer image once it
|
||||||
|
// exists. Any image with `rm` works for the wipe; `spaces` is always present.
|
||||||
|
{ imageId: 'spaces' },
|
||||||
sdk.Mounts.of().mountVolume({
|
sdk.Mounts.of().mountVolume({
|
||||||
volumeId: 'main',
|
volumeId: 'main',
|
||||||
subpath: null,
|
subpath: null,
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { i18n } from '../i18n'
|
||||||
|
import { sdk } from '../sdk'
|
||||||
|
import { dataDir, SUBSPACES_DIR } from '../utils'
|
||||||
|
|
||||||
|
export const resetSubspacesState = sdk.Action.withoutInput(
|
||||||
|
// id
|
||||||
|
'reset-subspaces-state',
|
||||||
|
|
||||||
|
// metadata
|
||||||
|
async ({ effects }) => ({
|
||||||
|
name: i18n('Reset Subspaces State'),
|
||||||
|
description: i18n(
|
||||||
|
'Wipe /data/subspaces so the next start re-creates empty Subspaces data, prover, and registry directories.',
|
||||||
|
),
|
||||||
|
warning: i18n(
|
||||||
|
'This deletes all local Subspaces state — any handles/proofs stored on disk will be lost. The binaries ship in the image, so nothing needs to be re-downloaded. The spaces wallet on spaced is preserved.',
|
||||||
|
),
|
||||||
|
allowedStatuses: 'any',
|
||||||
|
group: null,
|
||||||
|
visibility: 'enabled',
|
||||||
|
}),
|
||||||
|
|
||||||
|
// run
|
||||||
|
async ({ effects }) => {
|
||||||
|
const res = await sdk.SubContainer.withTemp(
|
||||||
|
effects,
|
||||||
|
{ imageId: 'subspaces' },
|
||||||
|
sdk.Mounts.of().mountVolume({
|
||||||
|
volumeId: 'main',
|
||||||
|
subpath: null,
|
||||||
|
mountpoint: dataDir,
|
||||||
|
readonly: false,
|
||||||
|
}),
|
||||||
|
'spaces-reset-subspaces',
|
||||||
|
(subc) => subc.exec(['rm', '-rf', SUBSPACES_DIR], { user: 'root' }),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (res.exitCode !== 0) {
|
||||||
|
return {
|
||||||
|
version: '1',
|
||||||
|
title: i18n('Failure'),
|
||||||
|
message: i18n('Could not wipe Subspaces state: ${error}', {
|
||||||
|
error: (res.stderr ?? '').toString() || `exit ${res.exitCode}`,
|
||||||
|
}),
|
||||||
|
result: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: '1',
|
||||||
|
title: i18n('Success'),
|
||||||
|
message: i18n(
|
||||||
|
'Subspaces cache and data have been wiped. Start (or restart) the service to re-fetch and rebuild from GitHub.',
|
||||||
|
),
|
||||||
|
result: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -26,6 +26,10 @@ const shape = z.object({
|
|||||||
.nullable()
|
.nullable()
|
||||||
.catch(null),
|
.catch(null),
|
||||||
enableExplorer: z.boolean().nullable().catch(null),
|
enableExplorer: z.boolean().nullable().catch(null),
|
||||||
|
enableSubspaces: z.boolean().nullable().catch(null),
|
||||||
|
certrelaySelfUrl: z.string().nullable().catch(null),
|
||||||
|
certrelayBootstrap: z.boolean().nullable().catch(null),
|
||||||
|
certrelayHealthcheckHandle: z.string().nullable().catch(null),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const storeJson = FileHelper.json(
|
export const storeJson = FileHelper.json(
|
||||||
|
|||||||
@@ -76,6 +76,58 @@ const dict = {
|
|||||||
'Explorer cache has been wiped. Start (or restart) the service to re-fetch and rebuild from GitHub.':
|
'Explorer cache has been wiped. Start (or restart) the service to re-fetch and rebuild from GitHub.':
|
||||||
97,
|
97,
|
||||||
'Could not wipe explorer state: ${error}': 98,
|
'Could not wipe explorer state: ${error}': 98,
|
||||||
|
'Subspaces Prover': 99,
|
||||||
|
'subs-prover is ready': 100,
|
||||||
|
'subs-prover is not ready': 101,
|
||||||
|
'Subspaces Web UI': 102,
|
||||||
|
'subspaces UI is ready': 103,
|
||||||
|
'subspaces UI is not ready': 104,
|
||||||
|
'Off-chain Bitcoin handles via the Spaces protocol (spacesops/subs). Talks to the local spaced over loopback RPC. Only useful while Subspaces is enabled.':
|
||||||
|
105,
|
||||||
|
'Subspaces Registry': 117,
|
||||||
|
'registry-server is ready': 118,
|
||||||
|
'registry-server is not ready': 119,
|
||||||
|
'RISC Zero prover server for Subspaces (no GPU). Generates the cryptographic proofs the Subspaces UI requests. Only useful while Subspaces is enabled.':
|
||||||
|
120,
|
||||||
|
'Registry server for publishing and resolving Subspaces handles. Only useful while Subspaces is enabled.':
|
||||||
|
121,
|
||||||
|
'Enable Subspaces': 106,
|
||||||
|
'Turn on the embedded Subspaces service (off-chain Bitcoin handles). Starts three prebuilt daemons — subs (Web UI), subs-prover, and registry-server — each on its own interface. Service restarts automatically. subs loads the existing `default` spaces wallet; create it first via the Space-CLI Web UI (`spaces createwallet`) if you have not already.':
|
||||||
|
107,
|
||||||
|
'Subspaces enabled. The service is restarting; the Subspaces Web UI (7777), Prover (8888), and Registry (8081) interfaces come up once their daemons are healthy.':
|
||||||
|
108,
|
||||||
|
'Disable Subspaces': 109,
|
||||||
|
'Stop the embedded Subspaces service. The compiled binaries and data at /data/subspaces are preserved and will be reused if Subspaces is re-enabled. Service restarts automatically.':
|
||||||
|
110,
|
||||||
|
'Subspaces disabled. The service is restarting; the Subspaces Web UI is no longer listening. Run "Enable Subspaces" to turn it back on.':
|
||||||
|
111,
|
||||||
|
'Reset Subspaces State': 112,
|
||||||
|
'Wipe /data/subspaces so the next start re-creates empty Subspaces data, prover, and registry directories.':
|
||||||
|
113,
|
||||||
|
'This deletes all local Subspaces state — any handles/proofs stored on disk will be lost. The binaries ship in the image, so nothing needs to be re-downloaded. The spaces wallet on spaced is preserved.':
|
||||||
|
114,
|
||||||
|
'Could not wipe Subspaces state: ${error}': 115,
|
||||||
|
'Subspaces cache and data have been wiped. Start (or restart) the service to re-fetch and rebuild from GitHub.':
|
||||||
|
116,
|
||||||
|
Certrelay: 122,
|
||||||
|
'certrelay is ready': 123,
|
||||||
|
'certrelay is not ready': 124,
|
||||||
|
'Certrelay serves cryptographic proofs binding Bitcoin-anchored handles to owner keys. SETUP NOTE: run the "Configure Certrelay" action and set CERTRELAY_SELF_URL to the publicly visible URL StartOS exposes for this interface (your clearnet domain or .onion address) — peers and clients reach this relay at that URL.':
|
||||||
|
125,
|
||||||
|
'Configure Certrelay': 126,
|
||||||
|
'Set the certrelay self URL, bootstrap toggle, and healthcheck handle. Saving restarts the service so certrelay picks up the new values.':
|
||||||
|
127,
|
||||||
|
'Certrelay Self URL': 128,
|
||||||
|
'The publicly visible URL clients use to reach this certrelay (CERTRELAY_SELF_URL). Set this to match the external address StartOS exposes for the Certrelay interface (e.g. your .onion or clearnet domain).':
|
||||||
|
129,
|
||||||
|
'Certrelay Bootstrap': 130,
|
||||||
|
'Whether this relay bootstraps from peer relays on startup (CERTRELAY_BOOTSTRAP).':
|
||||||
|
131,
|
||||||
|
'Certrelay Healthcheck Handle': 132,
|
||||||
|
'A handle the bundled fabric CLI resolves against this relay to verify end-to-end health (CERTRELAY_HEALTHCHECK_HANDLE).':
|
||||||
|
133,
|
||||||
|
'Certrelay configuration saved. The service is restarting to apply the new settings.':
|
||||||
|
134,
|
||||||
|
|
||||||
// interfaces.ts
|
// interfaces.ts
|
||||||
'Space-CLI Web UI': 12,
|
'Space-CLI Web UI': 12,
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { setInterfaces } from '../interfaces'
|
|||||||
import { sdk } from '../sdk'
|
import { sdk } from '../sdk'
|
||||||
import { versionGraph } from '../versions'
|
import { versionGraph } from '../versions'
|
||||||
import { taskBtcAuth } from './taskBtcAuth'
|
import { taskBtcAuth } from './taskBtcAuth'
|
||||||
|
import { taskSeedCertrelay } from './taskSeedCertrelay'
|
||||||
import { taskSeedDb } from './taskSeedDb'
|
import { taskSeedDb } from './taskSeedDb'
|
||||||
import { taskSeedEnableExplorer } from './taskSeedEnableExplorer'
|
import { taskSeedEnableExplorer } from './taskSeedEnableExplorer'
|
||||||
|
import { taskSeedEnableSubspaces } from './taskSeedEnableSubspaces'
|
||||||
import { taskSeedSpacedAuth } from './taskSeedSpacedAuth'
|
import { taskSeedSpacedAuth } from './taskSeedSpacedAuth'
|
||||||
import { taskSetPassword } from './taskSetPassword'
|
import { taskSetPassword } from './taskSetPassword'
|
||||||
|
|
||||||
@@ -20,6 +22,8 @@ export const init = sdk.setupInit(
|
|||||||
taskSeedDb,
|
taskSeedDb,
|
||||||
taskSeedSpacedAuth,
|
taskSeedSpacedAuth,
|
||||||
taskSeedEnableExplorer,
|
taskSeedEnableExplorer,
|
||||||
|
taskSeedEnableSubspaces,
|
||||||
|
taskSeedCertrelay,
|
||||||
taskSetPassword,
|
taskSetPassword,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { storeJson } from '../fileModels/storeJson'
|
||||||
|
import { sdk } from '../sdk'
|
||||||
|
import {
|
||||||
|
CERTRELAY_DEFAULT_BOOTSTRAP,
|
||||||
|
CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE,
|
||||||
|
CERTRELAY_DEFAULT_SELF_URL,
|
||||||
|
} from '../utils'
|
||||||
|
|
||||||
|
export const taskSeedCertrelay = sdk.setupOnInit(async (effects) => {
|
||||||
|
const store = await storeJson.read().once()
|
||||||
|
|
||||||
|
const patch: {
|
||||||
|
certrelaySelfUrl?: string
|
||||||
|
certrelayBootstrap?: boolean
|
||||||
|
certrelayHealthcheckHandle?: string
|
||||||
|
} = {}
|
||||||
|
|
||||||
|
if (store?.certrelaySelfUrl === null || store?.certrelaySelfUrl === undefined)
|
||||||
|
patch.certrelaySelfUrl = CERTRELAY_DEFAULT_SELF_URL
|
||||||
|
if (
|
||||||
|
store?.certrelayBootstrap === null ||
|
||||||
|
store?.certrelayBootstrap === undefined
|
||||||
|
)
|
||||||
|
patch.certrelayBootstrap = CERTRELAY_DEFAULT_BOOTSTRAP
|
||||||
|
if (
|
||||||
|
store?.certrelayHealthcheckHandle === null ||
|
||||||
|
store?.certrelayHealthcheckHandle === undefined
|
||||||
|
)
|
||||||
|
patch.certrelayHealthcheckHandle = CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE
|
||||||
|
|
||||||
|
if (Object.keys(patch).length > 0) {
|
||||||
|
await storeJson.merge(effects, patch, { allowWriteAfterConst: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { storeJson } from '../fileModels/storeJson'
|
||||||
|
import { sdk } from '../sdk'
|
||||||
|
|
||||||
|
export const taskSeedEnableSubspaces = sdk.setupOnInit(async (effects) => {
|
||||||
|
const existing = await storeJson.read((s) => s.enableSubspaces).once()
|
||||||
|
if (existing !== null && existing !== undefined) return
|
||||||
|
|
||||||
|
await storeJson.merge(
|
||||||
|
effects,
|
||||||
|
{ enableSubspaces: false },
|
||||||
|
{ allowWriteAfterConst: true },
|
||||||
|
)
|
||||||
|
})
|
||||||
+97
-2
@@ -1,6 +1,13 @@
|
|||||||
import { i18n } from './i18n'
|
import { i18n } from './i18n'
|
||||||
import { sdk } from './sdk'
|
import { sdk } from './sdk'
|
||||||
import { EXPLORER_PORT, uiPort } from './utils'
|
import {
|
||||||
|
CERTRELAY_PORT,
|
||||||
|
EXPLORER_PORT,
|
||||||
|
SUBSPACES_PROVER_PORT,
|
||||||
|
SUBSPACES_REGISTRY_PORT,
|
||||||
|
SUBSPACES_UI_PORT,
|
||||||
|
uiPort,
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
|
export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
|
||||||
const uiMulti = sdk.MultiHost.of(effects, 'ui-multi')
|
const uiMulti = sdk.MultiHost.of(effects, 'ui-multi')
|
||||||
@@ -43,5 +50,93 @@ export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
|
|||||||
|
|
||||||
const explorerReceipt = await explorerMultiOrigin.export([explorer])
|
const explorerReceipt = await explorerMultiOrigin.export([explorer])
|
||||||
|
|
||||||
return [uiReceipt, explorerReceipt]
|
const subspacesMulti = sdk.MultiHost.of(effects, 'subspaces-multi')
|
||||||
|
const subspacesMultiOrigin = await subspacesMulti.bindPort(SUBSPACES_UI_PORT, {
|
||||||
|
protocol: 'http',
|
||||||
|
})
|
||||||
|
const subspaces = sdk.createInterface(effects, {
|
||||||
|
name: i18n('Subspaces Web UI'),
|
||||||
|
id: 'subspaces',
|
||||||
|
description: i18n(
|
||||||
|
'Off-chain Bitcoin handles via the Spaces protocol (spacesops/subs). Talks to the local spaced over loopback RPC. Only useful while Subspaces is enabled.',
|
||||||
|
),
|
||||||
|
type: 'ui',
|
||||||
|
masked: false,
|
||||||
|
schemeOverride: null,
|
||||||
|
username: null,
|
||||||
|
path: '',
|
||||||
|
query: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
const subspacesReceipt = await subspacesMultiOrigin.export([subspaces])
|
||||||
|
|
||||||
|
const proverMulti = sdk.MultiHost.of(effects, 'subspaces-prover-multi')
|
||||||
|
const proverMultiOrigin = await proverMulti.bindPort(SUBSPACES_PROVER_PORT, {
|
||||||
|
protocol: 'http',
|
||||||
|
})
|
||||||
|
const prover = sdk.createInterface(effects, {
|
||||||
|
name: i18n('Subspaces Prover'),
|
||||||
|
id: 'subspaces-prover',
|
||||||
|
description: i18n(
|
||||||
|
'RISC Zero prover server for Subspaces (no GPU). Generates the cryptographic proofs the Subspaces UI requests. Only useful while Subspaces is enabled.',
|
||||||
|
),
|
||||||
|
type: 'api',
|
||||||
|
masked: false,
|
||||||
|
schemeOverride: null,
|
||||||
|
username: null,
|
||||||
|
path: '',
|
||||||
|
query: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
const proverReceipt = await proverMultiOrigin.export([prover])
|
||||||
|
|
||||||
|
const registryMulti = sdk.MultiHost.of(effects, 'subspaces-registry-multi')
|
||||||
|
const registryMultiOrigin = await registryMulti.bindPort(
|
||||||
|
SUBSPACES_REGISTRY_PORT,
|
||||||
|
{ protocol: 'http' },
|
||||||
|
)
|
||||||
|
const registry = sdk.createInterface(effects, {
|
||||||
|
name: i18n('Subspaces Registry'),
|
||||||
|
id: 'subspaces-registry',
|
||||||
|
description: i18n(
|
||||||
|
'Registry server for publishing and resolving Subspaces handles. Only useful while Subspaces is enabled.',
|
||||||
|
),
|
||||||
|
type: 'api',
|
||||||
|
masked: false,
|
||||||
|
schemeOverride: null,
|
||||||
|
username: null,
|
||||||
|
path: '',
|
||||||
|
query: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
const registryReceipt = await registryMultiOrigin.export([registry])
|
||||||
|
|
||||||
|
const certrelayMulti = sdk.MultiHost.of(effects, 'certrelay-multi')
|
||||||
|
const certrelayMultiOrigin = await certrelayMulti.bindPort(CERTRELAY_PORT, {
|
||||||
|
protocol: 'http',
|
||||||
|
})
|
||||||
|
const certrelay = sdk.createInterface(effects, {
|
||||||
|
name: i18n('Certrelay'),
|
||||||
|
id: 'certrelay',
|
||||||
|
description: i18n(
|
||||||
|
'Certrelay serves cryptographic proofs binding Bitcoin-anchored handles to owner keys. SETUP NOTE: run the "Configure Certrelay" action and set CERTRELAY_SELF_URL to the publicly visible URL StartOS exposes for this interface (your clearnet domain or .onion address) — peers and clients reach this relay at that URL.',
|
||||||
|
),
|
||||||
|
type: 'api',
|
||||||
|
masked: false,
|
||||||
|
schemeOverride: null,
|
||||||
|
username: null,
|
||||||
|
path: '',
|
||||||
|
query: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
const certrelayReceipt = await certrelayMultiOrigin.export([certrelay])
|
||||||
|
|
||||||
|
return [
|
||||||
|
uiReceipt,
|
||||||
|
explorerReceipt,
|
||||||
|
subspacesReceipt,
|
||||||
|
proverReceipt,
|
||||||
|
registryReceipt,
|
||||||
|
certrelayReceipt,
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
+263
-388
@@ -5,32 +5,35 @@ import {
|
|||||||
APP_USER,
|
APP_USER,
|
||||||
BITCOIND_RPC_HOSTNAME,
|
BITCOIND_RPC_HOSTNAME,
|
||||||
BITCOIND_RPC_PORT,
|
BITCOIND_RPC_PORT,
|
||||||
|
CERTRELAY_ANCHOR_REFRESH,
|
||||||
|
CERTRELAY_BIN,
|
||||||
|
CERTRELAY_BIND,
|
||||||
|
CERTRELAY_CHAIN,
|
||||||
|
CERTRELAY_DATA_DIR,
|
||||||
|
CERTRELAY_DEFAULT_BOOTSTRAP,
|
||||||
|
CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE,
|
||||||
|
CERTRELAY_DEFAULT_SELF_URL,
|
||||||
|
CERTRELAY_DIR,
|
||||||
|
CERTRELAY_FABRIC_BIN,
|
||||||
|
CERTRELAY_FABRIC_DEST,
|
||||||
|
CERTRELAY_PORT,
|
||||||
|
CERTRELAY_REMOTE_IP_HEADER,
|
||||||
dataDir,
|
dataDir,
|
||||||
EXPLORER_BUILD_DIR,
|
|
||||||
EXPLORER_BUILD_ID,
|
|
||||||
EXPLORER_DIR,
|
|
||||||
EXPLORER_MARKER,
|
|
||||||
EXPLORER_NETWORK,
|
|
||||||
EXPLORER_PORT,
|
|
||||||
EXPLORER_TARBALL_URL,
|
|
||||||
INDEXER_ACTIVATION_HEIGHT,
|
|
||||||
INDEXER_BIN_DIR,
|
|
||||||
INDEXER_BUILD_ID,
|
|
||||||
INDEXER_DIR,
|
|
||||||
INDEXER_FAST_SYNC_HEIGHT,
|
|
||||||
INDEXER_GOOSE_BIN,
|
|
||||||
INDEXER_MARKER,
|
|
||||||
INDEXER_MEMPOOL_CHUNK_SIZE,
|
|
||||||
INDEXER_SCHEMA_DIR,
|
|
||||||
INDEXER_SYNC_BIN,
|
|
||||||
INDEXER_TARBALL_URL,
|
|
||||||
INDEXER_UPDATE_INTERVAL,
|
|
||||||
pgDataDir,
|
pgDataDir,
|
||||||
POSTGRES_DB,
|
|
||||||
POSTGRES_PORT,
|
POSTGRES_PORT,
|
||||||
POSTGRES_USER,
|
|
||||||
SPACED_CHAIN,
|
SPACED_CHAIN,
|
||||||
spacedRpcPort,
|
spacedRpcPort,
|
||||||
|
SUBSPACES_DATA_DIR,
|
||||||
|
SUBSPACES_DIR,
|
||||||
|
SUBSPACES_PROVER_BIN,
|
||||||
|
SUBSPACES_PROVER_DIR,
|
||||||
|
SUBSPACES_PROVER_PORT,
|
||||||
|
SUBSPACES_REGISTRY_BIN,
|
||||||
|
SUBSPACES_REGISTRY_DIR,
|
||||||
|
SUBSPACES_REGISTRY_PORT,
|
||||||
|
SUBSPACES_SUBS_BIN,
|
||||||
|
SUBSPACES_UI_PORT,
|
||||||
|
SUBSPACES_WALLET,
|
||||||
uiPort,
|
uiPort,
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
@@ -52,6 +55,7 @@ export const main = sdk.setupMain(async ({ effects }) => {
|
|||||||
|
|
||||||
const { password: APP_PASSWORD, btcAuth, dbAuth, spacedAuth } = store
|
const { password: APP_PASSWORD, btcAuth, dbAuth, spacedAuth } = store
|
||||||
const enableExplorer = store.enableExplorer === true
|
const enableExplorer = store.enableExplorer === true
|
||||||
|
const enableSubspaces = store.enableSubspaces === true
|
||||||
|
|
||||||
const spacedEnv = {
|
const spacedEnv = {
|
||||||
SPACED_CHAIN,
|
SPACED_CHAIN,
|
||||||
@@ -75,6 +79,15 @@ export const main = sdk.setupMain(async ({ effects }) => {
|
|||||||
DB_URL: `postgres://${dbAuth.username}:${dbAuth.password}@127.0.0.1:${POSTGRES_PORT}/${dbAuth.database}`,
|
DB_URL: `postgres://${dbAuth.username}:${dbAuth.password}@127.0.0.1:${POSTGRES_PORT}/${dbAuth.database}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Certrelay config — user-tunable bits live in store.json (set via the
|
||||||
|
// "Configure Certrelay" action); the rest are fixed package defaults.
|
||||||
|
const certrelaySelfUrl =
|
||||||
|
store.certrelaySelfUrl ?? CERTRELAY_DEFAULT_SELF_URL
|
||||||
|
const certrelayBootstrap =
|
||||||
|
store.certrelayBootstrap ?? CERTRELAY_DEFAULT_BOOTSTRAP
|
||||||
|
const certrelayHealthcheckHandle =
|
||||||
|
store.certrelayHealthcheckHandle ?? CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE
|
||||||
|
|
||||||
const mounts = sdk.Mounts.of().mountVolume({
|
const mounts = sdk.Mounts.of().mountVolume({
|
||||||
volumeId: 'main',
|
volumeId: 'main',
|
||||||
subpath: null,
|
subpath: null,
|
||||||
@@ -110,45 +123,77 @@ export const main = sdk.setupMain(async ({ effects }) => {
|
|||||||
PGDATA: pgDataDir,
|
PGDATA: pgDataDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexerSub = await sdk.SubContainer.of(
|
// TODO(prebuilt-indexer / prebuilt-explorer): the Go indexer and SvelteKit
|
||||||
|
// explorer used to be built from source in dedicated builder images
|
||||||
|
// (golang:1.23-alpine, node:20-alpine). Those images + their build chains
|
||||||
|
// were removed to shrink the .s9pk and cut first-enable latency. When the
|
||||||
|
// prebuilt images exist, re-add their SubContainers + env here, e.g.:
|
||||||
|
// const indexerSub = await sdk.SubContainer.of(
|
||||||
|
// effects, { imageId: '<prebuilt-indexer-image>' }, mounts, 'indexer-sub')
|
||||||
|
// const explorerSub = await sdk.SubContainer.of(
|
||||||
|
// effects, { imageId: '<prebuilt-explorer-image>' }, mounts, 'explorer-sub')
|
||||||
|
// const indexerEnv = { POSTGRES_URI, BITCOIN_NODE_URI/USER/PASSWORD,
|
||||||
|
// SPACES_NODE_URI, RPC_USER/PASSWORD, ACTIVATION_BLOCK_HEIGHT,
|
||||||
|
// FAST_SYNC_BLOCK_HEIGHT, UPDATE_DB_INTERVAL, MEMPOOL_CHUNK_SIZE }
|
||||||
|
// const explorerEnv = { DB_URL (?sslmode=disable), PUBLIC_BTC_NETWORK, PORT }
|
||||||
|
// Also add the image ids to startos/manifest/index.ts and re-add the daemon
|
||||||
|
// definitions in the explorer branch below. See git history for prior shapes.
|
||||||
|
|
||||||
|
const subspacesSub = await sdk.SubContainer.of(
|
||||||
effects,
|
effects,
|
||||||
{ imageId: 'indexer-go' },
|
{ imageId: 'subspaces' },
|
||||||
mounts,
|
mounts,
|
||||||
'indexer-sub',
|
'subspaces-sub',
|
||||||
)
|
)
|
||||||
|
|
||||||
const explorerSub = await sdk.SubContainer.of(
|
// subs reads all of these from the environment (see subs/src/main.rs #[arg(env=...)]).
|
||||||
effects,
|
const subsEnv = {
|
||||||
{ imageId: 'explorer-ui' },
|
SUBS_DATA_DIR: SUBSPACES_DATA_DIR,
|
||||||
mounts,
|
SUBS_PORT: String(SUBSPACES_UI_PORT),
|
||||||
'explorer-sub',
|
SUBS_WALLET: SUBSPACES_WALLET,
|
||||||
)
|
SUBS_SPACED_RPC_URL: `http://127.0.0.1:${spacedRpcPort}`,
|
||||||
|
SUBS_SPACED_RPC_USER: spacedAuth.username,
|
||||||
const explorerEnv = {
|
SUBS_SPACED_RPC_PASSWORD: spacedAuth.password,
|
||||||
DB_URL: `postgres://${dbAuth.username}:${dbAuth.password}@127.0.0.1:${POSTGRES_PORT}/${dbAuth.database}?sslmode=disable`,
|
SUBS_PROVER_ENDPOINT: `http://127.0.0.1:${SUBSPACES_PROVER_PORT}`,
|
||||||
PUBLIC_BTC_NETWORK: EXPLORER_NETWORK,
|
SUBS_REGISTRY_ENDPOINT: `http://127.0.0.1:${SUBSPACES_REGISTRY_PORT}`,
|
||||||
PORT: String(EXPLORER_PORT),
|
HOME: SUBSPACES_DATA_DIR,
|
||||||
HOME: '/root',
|
RUST_LOG: 'subs=info,subs_prover=info,registry_server=info',
|
||||||
}
|
}
|
||||||
|
|
||||||
const postgresUri = `postgres://${dbAuth.username}:${dbAuth.password}@127.0.0.1:${POSTGRES_PORT}/${dbAuth.database}?sslmode=disable`
|
const proverEnv = {
|
||||||
|
SUBS_PROVER_SERVER: '1',
|
||||||
|
SUBS_PROVER_PORT: String(SUBSPACES_PROVER_PORT),
|
||||||
|
HOME: SUBSPACES_PROVER_DIR,
|
||||||
|
RUST_LOG: 'subs_prover=info',
|
||||||
|
}
|
||||||
|
|
||||||
const indexerEnv = {
|
const registryEnv = {
|
||||||
POSTGRES_URI: postgresUri,
|
REGISTRY_SERVER_PORT: String(SUBSPACES_REGISTRY_PORT),
|
||||||
BITCOIN_NODE_URI: `http://${BITCOIND_RPC_HOSTNAME}:${BITCOIND_RPC_PORT}`,
|
HOME: SUBSPACES_REGISTRY_DIR,
|
||||||
BITCOIN_NODE_USER: btcAuth.username,
|
RUST_LOG: 'registry_server=info',
|
||||||
BITCOIN_NODE_PASSWORD: btcAuth.password,
|
}
|
||||||
SPACES_NODE_URI: `http://127.0.0.1:${spacedRpcPort}`,
|
|
||||||
RPC_USER: spacedAuth.username,
|
const certrelaySub = await sdk.SubContainer.of(
|
||||||
RPC_PASSWORD: spacedAuth.password,
|
effects,
|
||||||
ACTIVATION_BLOCK_HEIGHT: INDEXER_ACTIVATION_HEIGHT,
|
{ imageId: 'certrelay' },
|
||||||
FAST_SYNC_BLOCK_HEIGHT: INDEXER_FAST_SYNC_HEIGHT,
|
mounts,
|
||||||
UPDATE_DB_INTERVAL: INDEXER_UPDATE_INTERVAL,
|
'certrelay-sub',
|
||||||
MEMPOOL_CHUNK_SIZE: INDEXER_MEMPOOL_CHUNK_SIZE,
|
)
|
||||||
PATH: `${INDEXER_BIN_DIR}:/usr/local/go/bin:/usr/local/bin:/usr/bin:/bin`,
|
|
||||||
HOME: '/root',
|
const certrelayEnv = {
|
||||||
GOPATH: '/root/go',
|
CERTRELAY_CHAIN,
|
||||||
GOBIN: INDEXER_BIN_DIR,
|
CERTRELAY_DATA_DIR,
|
||||||
|
CERTRELAY_BIND,
|
||||||
|
CERTRELAY_PORT: String(CERTRELAY_PORT),
|
||||||
|
CERTRELAY_SELF_URL: certrelaySelfUrl,
|
||||||
|
// Credentials come from the local spaced server (store.spacedAuth).
|
||||||
|
CERTRELAY_SPACED_RPC_URL: `http://${spacedAuth.username}:${spacedAuth.password}@127.0.0.1:${spacedRpcPort}`,
|
||||||
|
CERTRELAY_REMOTE_IP_HEADER,
|
||||||
|
CERTRELAY_BOOTSTRAP: certrelayBootstrap ? 'true' : 'false',
|
||||||
|
CERTRELAY_ANCHOR_REFRESH,
|
||||||
|
CERTRELAY_HEALTHCHECK_HANDLE: certrelayHealthcheckHandle,
|
||||||
|
HOME: CERTRELAY_DATA_DIR,
|
||||||
|
RUST_LOG: 'info',
|
||||||
}
|
}
|
||||||
|
|
||||||
const bashrc = [
|
const bashrc = [
|
||||||
@@ -161,18 +206,155 @@ export const main = sdk.setupMain(async ({ effects }) => {
|
|||||||
'│ spaced is managed by StartOS — do not run it manually. │',
|
'│ spaced is managed by StartOS — do not run it manually. │',
|
||||||
'│ Use the `spaces` alias to call space-cli, e.g. │',
|
'│ Use the `spaces` alias to call space-cli, e.g. │',
|
||||||
'│ spaces getserverinfo │',
|
'│ spaces getserverinfo │',
|
||||||
|
'│ `fabric` resolves handles against the local certrelay. │',
|
||||||
'│ Docs: https://docs.spacesprotocol.org/ │',
|
'│ Docs: https://docs.spacesprotocol.org/ │',
|
||||||
'└──────────────────────────────────────────────────────────┘',
|
'└──────────────────────────────────────────────────────────┘',
|
||||||
'',
|
'',
|
||||||
'EOF',
|
'EOF',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
|
// Appends the always-on certrelay chain (setup oneshot + certrelay daemon) to
|
||||||
|
// any existing chain. certrelay serves cryptographic proofs binding handles to
|
||||||
|
// owner keys anchored to Bitcoin. The `fabric` CLI is copied onto /data/bin so
|
||||||
|
// the gotty terminal (which has /data/bin on PATH) can resolve handles.
|
||||||
|
const withCertrelay = (chain: any): any =>
|
||||||
|
chain
|
||||||
|
.addOneshot('certrelay-setup', {
|
||||||
|
subcontainer: certrelaySub,
|
||||||
|
exec: {
|
||||||
|
command: [
|
||||||
|
'sh',
|
||||||
|
'-c',
|
||||||
|
`set -eu; \
|
||||||
|
mkdir -p ${CERTRELAY_DATA_DIR} /data/bin; \
|
||||||
|
echo "certrelay-setup: installing fabric CLI to ${CERTRELAY_FABRIC_DEST}..."; \
|
||||||
|
cp -f ${CERTRELAY_FABRIC_BIN} ${CERTRELAY_FABRIC_DEST}; \
|
||||||
|
chmod +x ${CERTRELAY_FABRIC_DEST}; \
|
||||||
|
echo "certrelay-setup: done."`,
|
||||||
|
],
|
||||||
|
user: 'root',
|
||||||
|
},
|
||||||
|
requires: [],
|
||||||
|
})
|
||||||
|
.addDaemon('certrelay', {
|
||||||
|
subcontainer: certrelaySub,
|
||||||
|
exec: {
|
||||||
|
command: [CERTRELAY_BIN],
|
||||||
|
env: certrelayEnv,
|
||||||
|
// /data is root-owned; the image's USER certrelay can't write it.
|
||||||
|
user: 'root',
|
||||||
|
},
|
||||||
|
ready: {
|
||||||
|
display: i18n('Certrelay'),
|
||||||
|
fn: () =>
|
||||||
|
sdk.healthCheck.checkPortListening(effects, CERTRELAY_PORT, {
|
||||||
|
successMessage: i18n('certrelay is ready'),
|
||||||
|
errorMessage: i18n('certrelay is not ready'),
|
||||||
|
}),
|
||||||
|
// Match the image's 120s start-period: anchor-refresh/checkpoint work
|
||||||
|
// on first start can delay the listener.
|
||||||
|
gracePeriod: 120_000,
|
||||||
|
trigger: sdk.trigger.cooldownTrigger(30_000),
|
||||||
|
},
|
||||||
|
requires: ['certrelay-setup', 'spaced'],
|
||||||
|
})
|
||||||
|
|
||||||
|
// Appends the Subspaces chain (fetch → build → wallet ensure → subs-prover →
|
||||||
|
// subs daemon) to any existing chain. `as any` because the chain's TS type
|
||||||
|
// depends on prior IDs and we can't easily express the union here.
|
||||||
|
const withSubspaces = (chain: any): any =>
|
||||||
|
chain
|
||||||
|
.addOneshot('subspaces-dirs', {
|
||||||
|
subcontainer: subspacesSub,
|
||||||
|
exec: {
|
||||||
|
command: [
|
||||||
|
'sh',
|
||||||
|
'-c',
|
||||||
|
`set -eu; \
|
||||||
|
mkdir -p ${SUBSPACES_DATA_DIR} ${SUBSPACES_PROVER_DIR} ${SUBSPACES_REGISTRY_DIR}; \
|
||||||
|
echo "subspaces-dirs: ensured data/prover/registry dirs under ${SUBSPACES_DIR}."`,
|
||||||
|
],
|
||||||
|
user: 'root',
|
||||||
|
},
|
||||||
|
requires: [],
|
||||||
|
})
|
||||||
|
.addDaemon('subs-prover', {
|
||||||
|
subcontainer: subspacesSub,
|
||||||
|
exec: {
|
||||||
|
command: [
|
||||||
|
'sh',
|
||||||
|
'-c',
|
||||||
|
`cd ${SUBSPACES_PROVER_DIR} && exec ${SUBSPACES_PROVER_BIN} --server --server-port ${SUBSPACES_PROVER_PORT}`,
|
||||||
|
],
|
||||||
|
env: proverEnv,
|
||||||
|
// Run as root: the horologger/subs image declares USER subs, but our
|
||||||
|
// /data volume + subdirs are root-owned, so the non-root user can't
|
||||||
|
// traverse/write them. Root sidesteps the ownership mismatch.
|
||||||
|
user: 'root',
|
||||||
|
},
|
||||||
|
ready: {
|
||||||
|
display: i18n('Subspaces Prover'),
|
||||||
|
fn: () =>
|
||||||
|
sdk.healthCheck.checkPortListening(effects, SUBSPACES_PROVER_PORT, {
|
||||||
|
successMessage: i18n('subs-prover is ready'),
|
||||||
|
errorMessage: i18n('subs-prover is not ready'),
|
||||||
|
}),
|
||||||
|
gracePeriod: 60_000,
|
||||||
|
trigger: sdk.trigger.cooldownTrigger(30_000),
|
||||||
|
},
|
||||||
|
requires: ['subspaces-dirs'],
|
||||||
|
})
|
||||||
|
.addDaemon('subs-registry', {
|
||||||
|
subcontainer: subspacesSub,
|
||||||
|
exec: {
|
||||||
|
command: [
|
||||||
|
'sh',
|
||||||
|
'-c',
|
||||||
|
`cd ${SUBSPACES_REGISTRY_DIR} && exec ${SUBSPACES_REGISTRY_BIN} --port ${SUBSPACES_REGISTRY_PORT}`,
|
||||||
|
],
|
||||||
|
env: registryEnv,
|
||||||
|
user: 'root',
|
||||||
|
},
|
||||||
|
ready: {
|
||||||
|
display: i18n('Subspaces Registry'),
|
||||||
|
fn: () =>
|
||||||
|
sdk.healthCheck.checkPortListening(effects, SUBSPACES_REGISTRY_PORT, {
|
||||||
|
successMessage: i18n('registry-server is ready'),
|
||||||
|
errorMessage: i18n('registry-server is not ready'),
|
||||||
|
}),
|
||||||
|
gracePeriod: 60_000,
|
||||||
|
},
|
||||||
|
requires: ['subspaces-dirs'],
|
||||||
|
})
|
||||||
|
.addDaemon('subs', {
|
||||||
|
subcontainer: subspacesSub,
|
||||||
|
exec: {
|
||||||
|
command: [SUBSPACES_SUBS_BIN],
|
||||||
|
env: subsEnv,
|
||||||
|
user: 'root',
|
||||||
|
},
|
||||||
|
ready: {
|
||||||
|
display: i18n('Subspaces Web UI'),
|
||||||
|
fn: () =>
|
||||||
|
sdk.healthCheck.checkPortListening(effects, SUBSPACES_UI_PORT, {
|
||||||
|
successMessage: i18n('subspaces UI is ready'),
|
||||||
|
errorMessage: i18n('subspaces UI is not ready'),
|
||||||
|
}),
|
||||||
|
gracePeriod: 60_000,
|
||||||
|
},
|
||||||
|
// Intentionally NOT contingent on subs-prover: the RISC Zero prover can
|
||||||
|
// take a while to become ready, and subs only needs it on-demand (proof
|
||||||
|
// generation), not at startup. subs reaches the prover via
|
||||||
|
// SUBS_PROVER_ENDPOINT once the prover is up.
|
||||||
|
requires: ['subspaces-dirs', 'spaced', 'subs-registry'],
|
||||||
|
})
|
||||||
|
|
||||||
if (!enableExplorer) {
|
if (!enableExplorer) {
|
||||||
// Spaces-only mode: spaced + gotty terminal. No PostgreSQL, no indexer.
|
// Spaces-only mode: spaced + gotty terminal. No PostgreSQL, no indexer.
|
||||||
// User can flip the toggle via the Enable Embedded Explorer action;
|
// User can flip the toggle via the Enable Embedded Explorer action;
|
||||||
// store.json.enableExplorer is read via .const() so the merge triggers
|
// store.json.enableExplorer is read via .const() so the merge triggers
|
||||||
// an automatic service restart and the full chain takes over.
|
// an automatic service restart and the full chain takes over.
|
||||||
return sdk.Daemons.of(effects)
|
let chain: any = sdk.Daemons.of(effects)
|
||||||
.addOneshot('bashrc', {
|
.addOneshot('bashrc', {
|
||||||
subcontainer: termSub,
|
subcontainer: termSub,
|
||||||
exec: {
|
exec: {
|
||||||
@@ -316,11 +498,14 @@ SPACES_BASHRC_EOF`],
|
|||||||
},
|
},
|
||||||
requires: ['spaced'],
|
requires: ['spaced'],
|
||||||
})
|
})
|
||||||
|
chain = withCertrelay(chain)
|
||||||
|
if (enableSubspaces) chain = withSubspaces(chain)
|
||||||
|
return chain
|
||||||
}
|
}
|
||||||
|
|
||||||
// enableExplorer = true: full chain — spaced + terminal + embedded
|
// enableExplorer = true: full chain — spaced + terminal + embedded
|
||||||
// PostgreSQL + Go indexer + indexer-sync health check.
|
// PostgreSQL + Go indexer + indexer-sync health check.
|
||||||
return sdk.Daemons.of(effects)
|
let chain: any = sdk.Daemons.of(effects)
|
||||||
.addOneshot('bashrc', {
|
.addOneshot('bashrc', {
|
||||||
subcontainer: termSub,
|
subcontainer: termSub,
|
||||||
exec: {
|
exec: {
|
||||||
@@ -382,215 +567,22 @@ SPACES_BASHRC_EOF`],
|
|||||||
},
|
},
|
||||||
requires: [],
|
requires: [],
|
||||||
})
|
})
|
||||||
.addOneshot('indexer-fetch', {
|
// TODO(prebuilt-indexer): the Go indexer build-from-source chain
|
||||||
subcontainer: indexerSub,
|
// (indexer-fetch + indexer-build + indexer-cleanup-legacy +
|
||||||
exec: {
|
// indexer-migrate + `indexer` daemon) was removed to shrink the .s9pk
|
||||||
command: [
|
// and cut first-enable latency. Re-add an `indexer` daemon here that runs
|
||||||
'sh',
|
// the prebuilt `sync` binary from the future indexer image (image id +
|
||||||
'-c',
|
// binary path TBD) against PostgreSQL + spaced. Env it needs is the old
|
||||||
`set -eu; \
|
// indexerEnv (POSTGRES_URI, BITCOIN_NODE_*, SPACES_NODE_URI, RPC_USER/
|
||||||
echo "indexer-fetch: image diagnostics..."; \
|
// PASSWORD, ACTIVATION_BLOCK_HEIGHT, FAST_SYNC_BLOCK_HEIGHT,
|
||||||
cat /etc/os-release 2>/dev/null | head -3 || echo "no /etc/os-release"; \
|
// UPDATE_DB_INTERVAL, MEMPOOL_CHUNK_SIZE). A goose migration step must
|
||||||
echo " go: $(go version 2>/dev/null || echo MISSING)"; \
|
// run before it. See git history (build-from-source) for the prior shape.
|
||||||
echo " git: $(git --version 2>/dev/null || echo MISSING)"; \
|
//
|
||||||
echo " curl: $(curl --version 2>/dev/null | head -1 || echo MISSING)"; \
|
// TODO(prebuilt-explorer): the SvelteKit explorer build chain
|
||||||
echo " wget: $(wget --version 2>/dev/null | head -1 || echo MISSING)"; \
|
// (explorer-fetch + explorer-install + `explorer-ui` daemon) was removed
|
||||||
echo " tar: $(tar --version 2>/dev/null | head -1 || echo MISSING)"; \
|
// for the same reason. Re-add an `explorer-ui` daemon that runs the
|
||||||
mkdir -p ${INDEXER_DIR} ${INDEXER_BIN_DIR}; \
|
// prebuilt explorer server from the provided explorer image (image id +
|
||||||
if [ -f ${INDEXER_MARKER} ] && [ "$(cat ${INDEXER_MARKER})" = "${INDEXER_BUILD_ID}" ] && [ -x ${INDEXER_SYNC_BIN} ] && [ -x ${INDEXER_GOOSE_BIN} ]; then \
|
// command TBD) on port 3000, reading DB_URL + PUBLIC_BTC_NETWORK.
|
||||||
echo "indexer-fetch: ${INDEXER_BUILD_ID} already present, skipping."; \
|
|
||||||
exit 0; \
|
|
||||||
fi; \
|
|
||||||
echo "indexer-fetch: downloading ${INDEXER_BUILD_ID}..."; \
|
|
||||||
rm -rf ${INDEXER_DIR}; \
|
|
||||||
mkdir -p ${INDEXER_DIR} ${INDEXER_BIN_DIR}; \
|
|
||||||
if command -v curl >/dev/null 2>&1; then \
|
|
||||||
curl -fsSL '${INDEXER_TARBALL_URL}' -o /tmp/explorer-indexer.tar.gz; \
|
|
||||||
elif command -v wget >/dev/null 2>&1; then \
|
|
||||||
wget -q -O /tmp/explorer-indexer.tar.gz '${INDEXER_TARBALL_URL}'; \
|
|
||||||
else \
|
|
||||||
echo "indexer-fetch: ERROR no curl or wget available"; exit 1; \
|
|
||||||
fi; \
|
|
||||||
tar -xzf /tmp/explorer-indexer.tar.gz --strip-components=1 -C ${INDEXER_DIR}; \
|
|
||||||
rm -f /tmp/explorer-indexer.tar.gz; \
|
|
||||||
echo "indexer-fetch: patching types.go to make ptrs_root optional (older spaced compatibility)..."; \
|
|
||||||
awk 'BEGIN{patched=0} /if aux.PointersRoot == nil \\{/ {skip=2; patched++; next} skip>0 {skip--; next} /ra\\.PointersRoot = \\*aux\\.PointersRoot/ {print "\\tif aux.PointersRoot != nil { ra.PointersRoot = *aux.PointersRoot }"; next} {print} END{if(patched<2){print "PATCH-FAIL: expected 2 ptrs_root blocks, got " patched > "/dev/stderr"; exit 1}}' ${INDEXER_DIR}/pkg/node/types.go > ${INDEXER_DIR}/pkg/node/types.go.new; \
|
|
||||||
mv ${INDEXER_DIR}/pkg/node/types.go.new ${INDEXER_DIR}/pkg/node/types.go; \
|
|
||||||
if grep -q 'missing required field: ptrs_root' ${INDEXER_DIR}/pkg/node/types.go; then \
|
|
||||||
echo "indexer-fetch: ERROR ptrs_root check still present after patch"; exit 1; \
|
|
||||||
fi; \
|
|
||||||
echo "indexer-fetch: patching store.go to skip getptrblockmeta (older spaced has no pointer-block RPC)..."; \
|
|
||||||
awk 'BEGIN{patched=0} /spacesPtrBlock, err := sc.GetPtrBlockMeta/ {skip=8; patched++; next} skip>0 {skip--; next} {print} END{if(patched<1){print "PATCH-FAIL: expected GetPtrBlockMeta block, got " patched > "/dev/stderr"; exit 1}}' ${INDEXER_DIR}/pkg/store/store.go > ${INDEXER_DIR}/pkg/store/store.go.new; \
|
|
||||||
mv ${INDEXER_DIR}/pkg/store/store.go.new ${INDEXER_DIR}/pkg/store/store.go; \
|
|
||||||
if grep -q 'GetPtrBlockMeta' ${INDEXER_DIR}/pkg/store/store.go; then \
|
|
||||||
echo "indexer-fetch: ERROR GetPtrBlockMeta call still present after patch"; exit 1; \
|
|
||||||
fi; \
|
|
||||||
echo "${INDEXER_BUILD_ID}" > ${INDEXER_MARKER}; \
|
|
||||||
echo "indexer-fetch: done."`,
|
|
||||||
],
|
|
||||||
user: 'root',
|
|
||||||
},
|
|
||||||
requires: [],
|
|
||||||
})
|
|
||||||
.addOneshot('indexer-build', {
|
|
||||||
subcontainer: indexerSub,
|
|
||||||
exec: {
|
|
||||||
command: [
|
|
||||||
'sh',
|
|
||||||
'-c',
|
|
||||||
`set -eu; \
|
|
||||||
if [ -x ${INDEXER_SYNC_BIN} ] && [ -x ${INDEXER_GOOSE_BIN} ]; then \
|
|
||||||
echo "indexer-build: sync + goose already built, skipping."; \
|
|
||||||
exit 0; \
|
|
||||||
fi; \
|
|
||||||
cd ${INDEXER_DIR}; \
|
|
||||||
mkdir -p ${INDEXER_BIN_DIR}; \
|
|
||||||
echo "indexer-build: building sync binary..."; \
|
|
||||||
CGO_ENABLED=0 go build -o ${INDEXER_SYNC_BIN} ./cmd/sync; \
|
|
||||||
echo "indexer-build: installing goose..."; \
|
|
||||||
CGO_ENABLED=0 GOBIN=${INDEXER_BIN_DIR} go install github.com/pressly/goose/v3/cmd/goose@v3.24.3; \
|
|
||||||
ls -l ${INDEXER_BIN_DIR}; \
|
|
||||||
echo "indexer-build: done."`,
|
|
||||||
],
|
|
||||||
env: indexerEnv,
|
|
||||||
user: 'root',
|
|
||||||
},
|
|
||||||
requires: ['indexer-fetch'],
|
|
||||||
})
|
|
||||||
.addOneshot('indexer-cleanup-legacy', {
|
|
||||||
subcontainer: postgresSub,
|
|
||||||
exec: {
|
|
||||||
command: [
|
|
||||||
'sh',
|
|
||||||
'-c',
|
|
||||||
`set -eu; \
|
|
||||||
PSQL="psql -U $POSTGRES_USER -h 127.0.0.1 -p 5432 -d $POSTGRES_DB -tA"; \
|
|
||||||
HAS_LEGACY=$($PSQL -c "SELECT (to_regclass('public.block_stats') IS NOT NULL) OR (to_regclass('public.spaces_history') IS NOT NULL)" 2>/dev/null || echo f); \
|
|
||||||
HAS_GOOSE=$($PSQL -c "SELECT to_regclass('public.goose_db_version') IS NOT NULL" 2>/dev/null || echo f); \
|
|
||||||
if [ "$HAS_GOOSE" != "t" ] && [ "$HAS_LEGACY" = "t" ]; then \
|
|
||||||
echo "indexer-cleanup-legacy: dropping leftover TS indexer schema..."; \
|
|
||||||
$PSQL -c "DROP TABLE IF EXISTS block_stats, syncs, spaces_history, spaces, transactions, blocks CASCADE; DROP EXTENSION IF EXISTS pg_trgm;"; \
|
|
||||||
echo "indexer-cleanup-legacy: done."; \
|
|
||||||
else \
|
|
||||||
echo "indexer-cleanup-legacy: nothing to clean (HAS_GOOSE=$HAS_GOOSE HAS_LEGACY=$HAS_LEGACY)."; \
|
|
||||||
fi`,
|
|
||||||
],
|
|
||||||
env: {
|
|
||||||
...postgresEnv,
|
|
||||||
PGPASSWORD: dbAuth.password,
|
|
||||||
},
|
|
||||||
user: 'postgres',
|
|
||||||
},
|
|
||||||
requires: ['postgres'],
|
|
||||||
})
|
|
||||||
.addOneshot('indexer-migrate', {
|
|
||||||
subcontainer: indexerSub,
|
|
||||||
exec: {
|
|
||||||
command: [
|
|
||||||
'sh',
|
|
||||||
'-c',
|
|
||||||
`set -eu; \
|
|
||||||
echo "indexer-migrate: applying goose migrations..."; \
|
|
||||||
${INDEXER_GOOSE_BIN} -dir ${INDEXER_SCHEMA_DIR} postgres "$POSTGRES_URI" up; \
|
|
||||||
echo "indexer-migrate: done."`,
|
|
||||||
],
|
|
||||||
env: indexerEnv,
|
|
||||||
user: 'root',
|
|
||||||
},
|
|
||||||
requires: ['indexer-build', 'postgres', 'indexer-cleanup-legacy'],
|
|
||||||
})
|
|
||||||
.addDaemon('indexer', {
|
|
||||||
subcontainer: indexerSub,
|
|
||||||
exec: {
|
|
||||||
command: [INDEXER_SYNC_BIN],
|
|
||||||
env: indexerEnv,
|
|
||||||
},
|
|
||||||
ready: {
|
|
||||||
display: i18n('Indexer Process'),
|
|
||||||
fn: () => ({
|
|
||||||
result: 'success',
|
|
||||||
message: i18n('indexer process is running'),
|
|
||||||
}),
|
|
||||||
gracePeriod: 60_000,
|
|
||||||
},
|
|
||||||
requires: ['indexer-migrate', 'postgres', 'spaced'],
|
|
||||||
})
|
|
||||||
.addOneshot('explorer-fetch', {
|
|
||||||
subcontainer: explorerSub,
|
|
||||||
exec: {
|
|
||||||
command: [
|
|
||||||
'sh',
|
|
||||||
'-c',
|
|
||||||
`set -eu; \
|
|
||||||
mkdir -p ${EXPLORER_DIR}; \
|
|
||||||
if [ -f ${EXPLORER_MARKER} ] && [ "$(cat ${EXPLORER_MARKER})" = "${EXPLORER_BUILD_ID}" ] && [ -d ${EXPLORER_BUILD_DIR} ]; then \
|
|
||||||
echo "explorer-fetch: ${EXPLORER_BUILD_ID} already present, skipping."; \
|
|
||||||
exit 0; \
|
|
||||||
fi; \
|
|
||||||
echo "explorer-fetch: downloading ${EXPLORER_BUILD_ID}..."; \
|
|
||||||
rm -rf ${EXPLORER_DIR}; \
|
|
||||||
mkdir -p ${EXPLORER_DIR}; \
|
|
||||||
if command -v curl >/dev/null 2>&1; then \
|
|
||||||
curl -fsSL '${EXPLORER_TARBALL_URL}' -o /tmp/explorer-ui.tar.gz; \
|
|
||||||
elif command -v wget >/dev/null 2>&1; then \
|
|
||||||
wget -q -O /tmp/explorer-ui.tar.gz '${EXPLORER_TARBALL_URL}'; \
|
|
||||||
else \
|
|
||||||
echo "explorer-fetch: ERROR no curl or wget"; exit 1; \
|
|
||||||
fi; \
|
|
||||||
tar -xzf /tmp/explorer-ui.tar.gz --strip-components=1 -C ${EXPLORER_DIR}; \
|
|
||||||
rm -f /tmp/explorer-ui.tar.gz; \
|
|
||||||
echo "${EXPLORER_BUILD_ID}" > ${EXPLORER_MARKER}; \
|
|
||||||
echo "explorer-fetch: done."`,
|
|
||||||
],
|
|
||||||
user: 'root',
|
|
||||||
},
|
|
||||||
requires: [],
|
|
||||||
})
|
|
||||||
.addOneshot('explorer-install', {
|
|
||||||
subcontainer: explorerSub,
|
|
||||||
exec: {
|
|
||||||
command: [
|
|
||||||
'sh',
|
|
||||||
'-c',
|
|
||||||
`set -eu; \
|
|
||||||
cd ${EXPLORER_DIR}; \
|
|
||||||
if [ -d node_modules ] && [ -d build ] && [ -f build/index.js ]; then \
|
|
||||||
echo "explorer-install: already built, skipping."; \
|
|
||||||
exit 0; \
|
|
||||||
fi; \
|
|
||||||
echo "explorer-install: npm install (this may take a few minutes)..."; \
|
|
||||||
npm install --no-audit --no-fund; \
|
|
||||||
echo "explorer-install: building (PUBLIC_BTC_NETWORK=${EXPLORER_NETWORK})..."; \
|
|
||||||
PUBLIC_BTC_NETWORK=${EXPLORER_NETWORK} npm run build; \
|
|
||||||
if [ ! -f ${EXPLORER_BUILD_DIR}/index.js ]; then \
|
|
||||||
echo "explorer-install: ERROR build/index.js not produced"; \
|
|
||||||
ls -la ${EXPLORER_BUILD_DIR} || true; \
|
|
||||||
exit 1; \
|
|
||||||
fi; \
|
|
||||||
echo "explorer-install: done."`,
|
|
||||||
],
|
|
||||||
env: explorerEnv,
|
|
||||||
user: 'root',
|
|
||||||
},
|
|
||||||
requires: ['explorer-fetch'],
|
|
||||||
})
|
|
||||||
.addDaemon('explorer-ui', {
|
|
||||||
subcontainer: explorerSub,
|
|
||||||
exec: {
|
|
||||||
command: ['node', EXPLORER_BUILD_DIR],
|
|
||||||
env: explorerEnv,
|
|
||||||
},
|
|
||||||
ready: {
|
|
||||||
display: i18n('Explorer Web UI'),
|
|
||||||
fn: () =>
|
|
||||||
sdk.healthCheck.checkPortListening(effects, EXPLORER_PORT, {
|
|
||||||
successMessage: i18n('explorer UI is ready'),
|
|
||||||
errorMessage: i18n('explorer UI is not ready'),
|
|
||||||
}),
|
|
||||||
gracePeriod: 60_000,
|
|
||||||
},
|
|
||||||
requires: ['explorer-install', 'postgres'],
|
|
||||||
})
|
|
||||||
.addDaemon('web-terminal', {
|
.addDaemon('web-terminal', {
|
||||||
subcontainer: termSub,
|
subcontainer: termSub,
|
||||||
exec: {
|
exec: {
|
||||||
@@ -714,128 +706,11 @@ SPACES_BASHRC_EOF`],
|
|||||||
},
|
},
|
||||||
requires: ['spaced'],
|
requires: ['spaced'],
|
||||||
})
|
})
|
||||||
.addHealthCheck('indexer-sync', {
|
// TODO(prebuilt-indexer): re-add the `indexer-sync` standalone health
|
||||||
ready: {
|
// check once the prebuilt indexer daemon is wired back in. It queried
|
||||||
display: i18n('Indexer Sync'),
|
// MAX(blocks.height) (non-orphan) via psql and compared against spaced's
|
||||||
fn: async () => {
|
// tip from `space-cli getserverinfo`. See git history for the prior body.
|
||||||
try {
|
chain = withCertrelay(chain)
|
||||||
// Go indexer has no syncs table; track progress by max(blocks.height)
|
if (enableSubspaces) chain = withSubspaces(chain)
|
||||||
// of non-orphan rows.
|
return chain
|
||||||
const psql = await postgresSub.exec(
|
|
||||||
[
|
|
||||||
'psql',
|
|
||||||
'-U',
|
|
||||||
POSTGRES_USER,
|
|
||||||
'-d',
|
|
||||||
POSTGRES_DB,
|
|
||||||
'-h',
|
|
||||||
'127.0.0.1',
|
|
||||||
'-p',
|
|
||||||
String(POSTGRES_PORT),
|
|
||||||
'-t',
|
|
||||||
'-A',
|
|
||||||
'-F',
|
|
||||||
'|',
|
|
||||||
'-c',
|
|
||||||
"SELECT COALESCE(MAX(height), 0) FROM blocks WHERE orphan = FALSE AND height >= 0;",
|
|
||||||
],
|
|
||||||
{ env: { PGPASSWORD: dbAuth.password } as Record<string, string> },
|
|
||||||
)
|
|
||||||
|
|
||||||
if (psql.exitCode !== 0) {
|
|
||||||
return {
|
|
||||||
result: 'failure',
|
|
||||||
message: i18n('indexer psql exited ${code}: ${error}', {
|
|
||||||
code: String(psql.exitCode),
|
|
||||||
error: ((psql.stderr ?? '') as string).toString().slice(0, 200) ||
|
|
||||||
'<no output>',
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const out = (psql.stdout ?? '').toString().trim()
|
|
||||||
if (!out) {
|
|
||||||
return {
|
|
||||||
result: 'loading',
|
|
||||||
message: i18n('indexer has not run a sync cycle yet.'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const parts = out.split('|')
|
|
||||||
const endBlockHeight = parseInt(parts[0] ?? '0', 10) || 0
|
|
||||||
const ageSec = -1 // Go indexer has no per-sync timestamp surface
|
|
||||||
|
|
||||||
const probe = await spacedSub.exec(
|
|
||||||
[
|
|
||||||
'/root/.cargo/bin/space-cli',
|
|
||||||
'--chain',
|
|
||||||
SPACED_CHAIN,
|
|
||||||
'--rpc-user',
|
|
||||||
spacedAuth.username,
|
|
||||||
'--rpc-password',
|
|
||||||
spacedAuth.password,
|
|
||||||
'--output-format',
|
|
||||||
'json',
|
|
||||||
'getserverinfo',
|
|
||||||
],
|
|
||||||
{},
|
|
||||||
)
|
|
||||||
let spacedBlocks = 0
|
|
||||||
if (probe.exitCode === 0) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse((probe.stdout ?? '').toString())
|
|
||||||
spacedBlocks = parsed?.chain?.blocks ?? 0
|
|
||||||
} catch {
|
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endBlockHeight === 0) {
|
|
||||||
return {
|
|
||||||
result: 'loading',
|
|
||||||
message: i18n(
|
|
||||||
'indexer has not yet committed any blocks (spaced tip ${tip}).',
|
|
||||||
{ tip: String(spacedBlocks) },
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const lag = Math.max(0, spacedBlocks - endBlockHeight)
|
|
||||||
if (spacedBlocks > 0 && lag <= 5) {
|
|
||||||
return {
|
|
||||||
result: 'success',
|
|
||||||
message: i18n(
|
|
||||||
'indexer caught up at block ${end} (spaced tip ${tip}).',
|
|
||||||
{
|
|
||||||
end: String(endBlockHeight),
|
|
||||||
tip: String(spacedBlocks),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
result: 'loading',
|
|
||||||
message: i18n(
|
|
||||||
'indexer at block ${end}, ${lag} behind spaced tip ${tip}.',
|
|
||||||
{
|
|
||||||
end: String(endBlockHeight),
|
|
||||||
lag: String(lag),
|
|
||||||
tip: String(spacedBlocks),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
result: 'failure',
|
|
||||||
message: i18n('Indexer Sync health check crashed: ${error}', {
|
|
||||||
error: (e as Error)?.message ?? String(e),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
gracePeriod: 60_000,
|
|
||||||
trigger: sdk.trigger.cooldownTrigger(30_000),
|
|
||||||
},
|
|
||||||
requires: ['indexer', 'postgres'],
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,12 +21,21 @@ export const manifest = setupManifest({
|
|||||||
source: { dockerTag: 'postgres:16.3' },
|
source: { dockerTag: 'postgres:16.3' },
|
||||||
arch: ['x86_64', 'aarch64'],
|
arch: ['x86_64', 'aarch64'],
|
||||||
},
|
},
|
||||||
'indexer-go': {
|
// TODO(prebuilt-indexer): add the prebuilt explorer-indexer image here once
|
||||||
source: { dockerTag: 'golang:1.23-alpine' },
|
// it's produced, e.g. 'indexer-go': { source: { dockerTag:
|
||||||
|
// 'horologger/explorer-indexer:<tag>' }, arch: ['x86_64','aarch64'] }.
|
||||||
|
// The golang:1.23-alpine builder image + build-from-source chain were
|
||||||
|
// removed to shrink the .s9pk and cut first-enable latency.
|
||||||
|
//
|
||||||
|
// TODO(prebuilt-explorer): add the prebuilt explorer (SvelteKit) image here,
|
||||||
|
// replacing the node:20-alpine builder, e.g. 'explorer-ui': { source: {
|
||||||
|
// dockerTag: 'horologger/explorer:<tag>' }, arch: ['x86_64','aarch64'] }.
|
||||||
|
subspaces: {
|
||||||
|
source: { dockerTag: 'horologger/subs:v0.1.0' },
|
||||||
arch: ['x86_64', 'aarch64'],
|
arch: ['x86_64', 'aarch64'],
|
||||||
},
|
},
|
||||||
'explorer-ui': {
|
certrelay: {
|
||||||
source: { dockerTag: 'node:20-alpine' },
|
source: { dockerTag: 'horologger/certrelay:v0.2.3' },
|
||||||
arch: ['x86_64', 'aarch64'],
|
arch: ['x86_64', 'aarch64'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -52,6 +52,42 @@ export const EXPLORER_MARKER = '/data/explorer-ui/.installed-sha'
|
|||||||
export const EXPLORER_PORT = 3000
|
export const EXPLORER_PORT = 3000
|
||||||
export const EXPLORER_NETWORK = 'mainnet'
|
export const EXPLORER_NETWORK = 'mainnet'
|
||||||
|
|
||||||
|
// Subspaces ships as the prebuilt horologger/subs image; binaries live at
|
||||||
|
// these paths inside it. No build-from-source step.
|
||||||
|
export const SUBSPACES_SUBS_BIN = '/usr/local/bin/subs'
|
||||||
|
export const SUBSPACES_PROVER_BIN = '/usr/local/bin/subs-prover'
|
||||||
|
export const SUBSPACES_REGISTRY_BIN = '/usr/local/bin/registry-server'
|
||||||
|
|
||||||
|
export const SUBSPACES_DIR = '/data/subspaces'
|
||||||
|
export const SUBSPACES_DATA_DIR = '/data/subspaces/data'
|
||||||
|
export const SUBSPACES_PROVER_DIR = '/data/subspaces/prover'
|
||||||
|
export const SUBSPACES_REGISTRY_DIR = '/data/subspaces/registry'
|
||||||
|
|
||||||
|
export const SUBSPACES_UI_PORT = 7777
|
||||||
|
export const SUBSPACES_PROVER_PORT = 8888
|
||||||
|
// 8081 (not the upstream default 8080) to avoid colliding with the gotty
|
||||||
|
// Space-CLI Web UI on 8080.
|
||||||
|
export const SUBSPACES_REGISTRY_PORT = 8081
|
||||||
|
export const SUBSPACES_WALLET = 'default'
|
||||||
|
|
||||||
|
// Certrelay ships as the prebuilt horologger/certrelay image; both binaries
|
||||||
|
// are static musl, so `fabric` runs anywhere once copied onto the volume.
|
||||||
|
export const CERTRELAY_BIN = '/usr/local/bin/certrelay'
|
||||||
|
export const CERTRELAY_FABRIC_BIN = '/usr/local/bin/fabric'
|
||||||
|
// Copied here so the gotty terminal (which has /data/bin on PATH) can run it.
|
||||||
|
export const CERTRELAY_FABRIC_DEST = '/data/bin/fabric'
|
||||||
|
export const CERTRELAY_DIR = '/data/certrelay'
|
||||||
|
export const CERTRELAY_DATA_DIR = '/data/certrelay/data'
|
||||||
|
export const CERTRELAY_PORT = 7778
|
||||||
|
export const CERTRELAY_CHAIN = 'mainnet'
|
||||||
|
export const CERTRELAY_BIND = '0.0.0.0'
|
||||||
|
export const CERTRELAY_REMOTE_IP_HEADER = 'x-forwarded-for'
|
||||||
|
export const CERTRELAY_ANCHOR_REFRESH = '300'
|
||||||
|
export const CERTRELAY_DEFAULT_SELF_URL = 'https://certrelay.spacesops.com'
|
||||||
|
export const CERTRELAY_DEFAULT_HEALTHCHECK_HANDLE =
|
||||||
|
'account-digital-useful.genesis@key'
|
||||||
|
export const CERTRELAY_DEFAULT_BOOTSTRAP = false
|
||||||
|
|
||||||
export function randomPassword() {
|
export function randomPassword() {
|
||||||
// bitcoind's generate-rpc-dependent action validates the password against
|
// bitcoind's generate-rpc-dependent action validates the password against
|
||||||
// /^[A-Za-z0-9_-]+$/, so the charset must stay in that set.
|
// /^[A-Za-z0-9_-]+$/, so the charset must stay in that set.
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk'
|
|||||||
export const v_0_0_9_1 = VersionInfo.of({
|
export const v_0_0_9_1 = VersionInfo.of({
|
||||||
version: '0.0.9:1',
|
version: '0.0.9:1',
|
||||||
releaseNotes: {
|
releaseNotes: {
|
||||||
en_US: `- Embedded explorer (PostgreSQL + Go indexer + SvelteKit web UI) is now **opt-in**. Fresh installs run in spaces-only mode (spaced + gotty terminal). New "Enable Embedded Explorer" action turns the whole bundle on; "Disable Embedded Explorer" turns it back off. Both auto-restart the service so the new daemon graph takes effect. Indexed data on disk is preserved across toggles.
|
en_US: `- Embedded Certrelay (prebuilt \`horologger/certrelay:v0.2.3\` image, static musl binaries). Always on — runs the \`certrelay\` server on port 7778 from service start, as its own StartOS interface. It serves cryptographic proofs binding Bitcoin-anchored handles to owner keys, talking to the local spaced over loopback using the store.spacedAuth credentials. The bundled \`fabric\` CLI is copied to /data/bin/fabric so it's runnable from the Space-CLI Web UI. New "Configure Certrelay" action sets CERTRELAY_SELF_URL, CERTRELAY_BOOTSTRAP, and CERTRELAY_HEALTHCHECK_HANDLE (stored in store.json; saving restarts the service). Set CERTRELAY_SELF_URL to the publicly visible URL StartOS exposes for the Certrelay interface.
|
||||||
|
- Removed the build-from-source chains for the Go indexer and the SvelteKit explorer, and dropped their builder images (\`golang:1.23-alpine\`, \`node:20-alpine\`). This shrinks the .s9pk and removes the multi-minute first-enable compile. The indexer + explorer-UI daemons are now stubbed with TODOs in startos/main.ts pending prebuilt images (indexer image not yet produced; explorer image to be provided). Enabling the embedded explorer currently starts PostgreSQL only. All explorer-indexer actions (show-db-credentials, reset-db-state, reset-indexer-state, reset-explorer-state) are retained.
|
||||||
|
- Embedded Subspaces support (prebuilt \`horologger/subs:v0.1.0\` image). Opt-in via the new "Enable Subspaces" action; "Disable Subspaces" stops it. No compile step — enabling starts three prebuilt daemons, each on its own StartOS interface: **subs** (Web UI, 7777), **subs-prover** (RISC Zero prover, no GPU, 8888), and **registry-server** (handle registry, 8081). subs loads the existing \`default\` spaces wallet at startup (it does not create one). Runtime data persists at /data/subspaces/data across restarts and toggles. New "Reset Subspaces State" action wipes local data. Adds a fifth manifest image (\`horologger/subs:v0.1.0\`, ~84 MB) — switched from building \`spacesops/subs\` from source with \`rust:1-slim\` to shrink the .s9pk and eliminate the multi-minute first-enable compile.
|
||||||
|
- Five distinct interfaces now appear in the dashboard: **Space-CLI Web UI** (gotty terminal, 8080), **Explorer Web UI** (3000, opt-in), **Subspaces Web UI** (7777, opt-in), **Subspaces Prover** (8888, opt-in), **Subspaces Registry** (8081, opt-in). Internal "Web UI" references renamed to **Space-CLI Web UI** for clarity.
|
||||||
|
- Embedded explorer (PostgreSQL + Go indexer + SvelteKit web UI) is now **opt-in**. Fresh installs run in spaces-only mode (spaced + gotty terminal). New "Enable Embedded Explorer" action turns the whole bundle on; "Disable Embedded Explorer" turns it back off. Both auto-restart the service so the new daemon graph takes effect. Indexed data on disk is preserved across toggles.
|
||||||
- Embedded SvelteKit explorer web UI (randomlogin/explorer @ c827da175). Fetched into /data/explorer-ui on first enable, \`npm install\` + \`npm run build\` produce a SvelteKit node-adapter bundle, and \`node build\` runs as a managed daemon on port 3000. Exposed as a StartOS interface so it appears alongside the gotty terminal in the dashboard. Reads exclusively from the embedded PostgreSQL; does not talk to spaced or bitcoind directly. New "Reset Explorer UI State" action wipes the cache for a clean rebuild. Adds a fourth manifest image \`node:20-alpine\`.
|
- Embedded SvelteKit explorer web UI (randomlogin/explorer @ c827da175). Fetched into /data/explorer-ui on first enable, \`npm install\` + \`npm run build\` produce a SvelteKit node-adapter bundle, and \`node build\` runs as a managed daemon on port 3000. Exposed as a StartOS interface so it appears alongside the gotty terminal in the dashboard. Reads exclusively from the embedded PostgreSQL; does not talk to spaced or bitcoind directly. New "Reset Explorer UI State" action wipes the cache for a clean rebuild. Adds a fourth manifest image \`node:20-alpine\`.
|
||||||
- Embedded spaces-protocol explorer indexer (Go, spacesprotocol/explorer-indexer @ 00ae1e548). When enabled, on first start its source tarball is fetched into /data/explorer-indexer, the \`sync\` binary and \`goose\` migrator are built into /data/explorer-indexer/bin, the goose-managed SQL schema is applied to the embedded PostgreSQL, and the sync binary runs as a managed daemon that polls bitcoind + spaced and writes to the database.
|
- Embedded spaces-protocol explorer indexer (Go, spacesprotocol/explorer-indexer @ 00ae1e548). When enabled, on first start its source tarball is fetched into /data/explorer-indexer, the \`sync\` binary and \`goose\` migrator are built into /data/explorer-indexer/bin, the goose-managed SQL schema is applied to the embedded PostgreSQL, and the sync binary runs as a managed daemon that polls bitcoind + spaced and writes to the database.
|
||||||
- Two patches applied to the indexer source during \`indexer-fetch\` to handle our older spaced binary: \`pkg/node/types.go\` makes \`ptrs_root\` optional in RootAnchor, and \`pkg/store/store.go\` skips the \`getptrblockmeta\` RPC call. Subspaces pointer data won't be indexed; everything else (blocks, transactions, spaces, rollouts, root anchors) does.
|
- Two patches applied to the indexer source during \`indexer-fetch\` to handle our older spaced binary: \`pkg/node/types.go\` makes \`ptrs_root\` optional in RootAnchor, and \`pkg/store/store.go\` skips the \`getptrblockmeta\` RPC call. Subspaces pointer data won't be indexed; everything else (blocks, transactions, spaces, rollouts, root anchors) does.
|
||||||
|
|||||||
Reference in New Issue
Block a user