Skip to content

Self-hosted installer troubleshooting (alpha)

Last Updated: 2026-06-02

Runbook for the Substratum Installer Express install (apps/installer-gui): home server on your machine, Substratum desktop app, and edge on port 35480. For Compose dev OAuth origins, see OAuth and PDS origins. For HTTPS production topology, see Production deployment.

Install layout

PlatformInstall root
macOS~/.substratum/
Linux~/.substratum/
WindowsC:\ProgramData\Substratum\

Express install copies binaries under {install_root}/bin/, static UI under {install_root}/web/, Caddy config under {install_root}/edge/, Postgres data under {install_root}/postgres/, and blockstore data under {install_root}/data/blocks/. See Catalog vs blockstore storage for how catalog size compares to file bytes.

Express install also writes manifests in the install root:

FilePurpose
substratum-gateway.jsonGateway operational config (DB, bind 38080, public_base_url, CORS, OAuth/PDS URLs, rust_log)
substratum-installer-profile.jsonEdge hostname/port, pds_provider, Postgres paths
substratum-triangle.jsonPersonal device triangle (mesh)

Default ports (ADR 24):

ServicePort
Edge (Caddy — UI + proxied /api)35480
Gateway (internal)38080
Postgres35432

Default edge URL: http://substratum.localhost:35480 (.localhost resolves to loopback on modern browsers). http://127.0.0.1:35480 also works when CORS includes it (installer adds both).

Service logs

After a successful install, the installer message includes Service logs: {install_root}/logs.

Installer app log

The Substratum Installer (Tauri) appends structured logs to:

PathContent
{install_root}/logs/installer.logBundled resource resolution, Express install steps, gateway repair, desktop app install, PDS changes

Before the first install, logs go to ~/.substratum/logs/installer.log (macOS/Linux) or C:\ProgramData\Substratum\logs\installer.log (Windows).

Filter verbosity with RUST_LOG (default info,substratum_installer_gui=debug). In debug builds, logs also print to stderr; set SUBSTRATUM_INSTALLER_LOG_STDERR=1 in release to mirror file output to the terminal.

bash
tail -f ~/.substratum/logs/installer.log

macOS (LaunchAgents)

Servicestdoutstderr (check first)
Gateway{install_root}/logs/gateway.loggateway.err.log
Edge (Caddy){install_root}/logs/edge.logedge.err.log
Postgres{install_root}/logs/postgres.log(same file)

Example:

bash
tail -f ~/.substratum/logs/gateway.err.log

Gateway runs with working directory = install root so it loads ./substratum-gateway.json (substratum-gateway-application-v1).

Restart gateway after config edits:

bash
launchctl kickstart -k gui/$(id -u)/cloud.substratum.gateway

LaunchAgent labels: cloud.substratum.postgres, cloud.substratum.gateway, cloud.substratum.edge.

Linux (user systemd)

Units: substratum-postgres.service, substratum-gateway.service, substratum-edge.service.

bash
journalctl --user -u substratum-gateway.service -f

Postgres may also append to {install_root}/logs/postgres.log.

Windows (services)

Services: SubstratumGateway, SubstratumEdge, Postgres registration under the install root. Check Event Viewer and {install_root}\logs\ when present.

Enable verbose gateway logging

Edit rust_log in substratum-gateway.json, then restart the gateway service:

json
"rust_log": "debug,substratum_auth=debug,substratum_ingress=debug"

Reproduce the issue and read gateway.err.log. Release gateway binaries honor the manifest when env does not override (gateway manifest reference).

Health checks

bash
# Edge → static UI + API proxy
curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:35480/api/v1/health

# Gateway direct (bypass Caddy)
curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:38080/api/v1/health
HTTP resultLikely issue
Edge 502Gateway not up or Postgres not ready — see gateway.err.log, postgres.log
Edge 200, gateway direct failsCaddy OK but gateway crashed — gateway.err.log
Both failServices not started — installer Step 0 plan / LaunchAgents (macOS)

Substratum desktop app

The installer installs Substratum.app to ~/Applications (macOS). The app webview loads http://127.0.0.1:{edge_public_port} from substratum-installer-profile.json, not the .localhost hostname.

Staging / rebuild:

bash
cd apps/installer-gui && pnpm stage-explorer

Debug installer sessions read live apps/installer-gui/src-tauri/resources/bundled/ after staging (restart pnpm dev:tauri). Release installer .app embeds resources at build time — run pnpm build:installer-tauri after staging.

OAuth sign-in failures

Sign-in calls POST /api/v1/oauth/start. A 400 with "OAuth authorize failed: …" comes from the gateway (OAuthStartError); details are usually in gateway.err.log if rust_log is info or higher.

1. Exercise OAuth from the shell

bash
curl -sS -X POST http://127.0.0.1:35480/api/v1/oauth/start \
  -H 'Content-Type: application/json' \
  -d '{"handle":"YOUR_HANDLE.bsky.social"}'

Compare the JSON error with the UI message.

2. public_base_url is loopback (installer default)

Express install sets public_base_url to http://127.0.0.1:35480 (not the edge hostname) so AT Protocol OAuth uses loopback redirect_uri values accepted by Bluesky and other PDSes. cors_allowed_origins still includes both 127.0.0.1 and your edge hostname (e.g. http://substratum.localhost:35480).

Opening the Substratum desktop app at 127.0.0.1 therefore matches OAuth metadata. You can still browse via substratum.localhost when CORS lists it.

Upgrade: Opening installer Step 0 runs repair_gateway_oauth_manifest, which rewrites an older manifest that used substratum.localhost as public_base_url. Restart the gateway after repair (launchctl kickstart -k gui/$(id -u)/cloud.substratum.gateway on macOS).

See OAuth and PDS origins — same-origin rule.

3. PDS provider vs handle

Step 0 Sign-in (AT Protocol PDS) writes OAuth fields into substratum-gateway.json:

ProviderUse for
Bluesky (default)Existing *.bsky.social handles (public AppView)
Substratum*.substratum.cloud style accounts
LocalLoopback PDS on this machine (alice.test, etc.)
CustomOperator-supplied PDS base URL

Check pds_provider in substratum-installer-profile.json and atproto_appview_url / pds_* in substratum-gateway.json. Opening installer settings runs repair_gateway_oauth_manifest if AppView was incorrectly equal to pds_url.

4. Remote PDS rejects authorize

For federated handles, the user’s home PDS fetches client_id metadata from your edge and validates redirect_uri. Failures often show as OAuth authorize failed: http status: 400 without a local stack trace. Confirm:

  • Edge URL is reachable from the machine (for local alpha, same Mac is enough).
  • GET http://<your-edge>/.well-known/oauth-client-metadata.json returns JSON.
bash
curl -sS "http://substratum.localhost:35480/.well-known/oauth-client-metadata.json" | head

Alpha support checklist

When reporting issues, attach:

  1. installer.log and gateway.err.log (last ~100 lines after reproducing sign-in)
  2. substratum-gateway.json and substratum-installer-profile.json (redact passwords)
  3. Handle used and PDS provider from Step 0
  4. URL in the Substratum window (127.0.0.1 vs substratum.localhost)
  5. Installer build type (dev tauri dev vs release .app)

Updates (interim)

Long-term design: ADR 31 — Personal kit updates and in-app update checks. Until that ships, refresh components via the installer (Open Substratum for the desktop app; Express install for gateway/UI/edge).