OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / startos-0.4 / startos / main.ts

79 lines · typescript ref:
import { sdk } from './sdk'
import { uiPort } from './utils'

// StartOS has no APP_SEED equivalent (Umbrel derives one from the device
// seed), so — exactly as the 0.3.x docker_entrypoint.sh did — we persist our
// own 32-byte seed on the data volume: first boot generates it, every later
// boot reuses it. That seed derives the dashboard's proxy-auth token AND keeps
// the relay identity stable across reinstalls (as long as /data survives).
//
// We run this inline via `sh -c` instead of a baked-in entrypoint script,
// because the image is the unmodified published GHCR image (referenced by
// dockerTag in the manifest) — we never rebuild it. `exec node …` becomes the
// container's PID 1 (runAsInit) so SIGTERM reaches the relay for clean
// shutdown. Byte-for-byte the same seed derivation as the 0.3.x package.
const SEED_AND_START =
  'if [ ! -f /data/.app-seed ]; then ' +
  'head -c 32 /dev/urandom | od -An -tx1 | tr -d \' \\n\' > /data/.app-seed; ' +
  'chmod 600 /data/.app-seed; ' +
  'fi; ' +
  'export APP_SEED="$(cat /data/.app-seed)"; ' +
  'exec node /app/packages/core/cli/index.js start'

export const main = sdk.setupMain(async ({ effects }) => {
  return sdk.Daemons.of(effects).addDaemon('primary', {
    subcontainer: sdk.SubContainer.of(
      effects,
      { imageId: 'blindspark' },
      sdk.Mounts.of().mountVolume({
        volumeId: 'main',
        subpath: null,
        mountpoint: '/data',
        readonly: false,
      }),
      'blindspark-sub',
    ),
    exec: {
      command: ['/bin/sh', '-c', SEED_AND_START],
      runAsInit: true,
      // Large nodes can take a while to flush on shutdown; give the relay room.
      sigtermTimeout: 60000,
      env: {
        HOME: '/data',
        HIVERELAY_API_HOST: '0.0.0.0',
        HIVERELAY_API_PORT: String(uiPort),
        // StartOS fronts the UI through its Tor/LAN proxy — same as Umbrel's
        // app_proxy — so the dashboard embeds a seed-derived bearer token in
        // served HTML to authenticate itself.
        HIVERELAY_UI_EXPOSE_TOKEN: '1',
        // Single-page appliance dashboard (no operator tabs, no Docs/GitHub).
        HIVERELAY_UI_SIMPLE: '1',
        // Sovereignty default: review every incoming seed request until the
        // operator explicitly switches modes from the dashboard.
        HIVERELAY_ACCEPT_MODE: 'review',
        // Conservative home-server default; the CLI only applies this before a
        // saved operator config exists, so dashboard changes win later.
        HIVERELAY_MAX_STORAGE: '10GB',
        HIVERELAY_LOG_LEVEL: 'info',
      },
    },
    ready: {
      display: 'Web Interface',
      // Matches the 0.3.x check: the dashboard actually answers on /health,
      // not merely that the port is open.
      fn: () =>
        sdk.healthCheck.checkWebUrl(
          effects,
          `http://localhost:${uiPort}/health`,
          {
            timeout: 10000,
            successMessage: 'The Blindspark dashboard is reachable',
            errorMessage:
              'The dashboard is not responding yet — large nodes can take a couple of minutes after a restart.',
          },
        ),
    },
    requires: [],
  })
})