Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dependencies

Cross-service dependencies allow your service to interact with other StartOS services. Use them when your service needs to:

  • Enforce configuration on a dependency (e.g., enable a feature)
  • Register with a dependency (e.g., appservice registration)
  • Read a dependency’s interface URL at runtime

Declaring Dependencies

Dependencies are declared in manifest/index.ts. Each dependency requires either metadata or s9pk to provide display info (title and icon). Both approaches achieve the same result – they are two ways of providing the metadata:

dependencies: {
  // Provide metadata directly
  synapse: {
    description: 'Needed for Matrix homeserver',
    optional: false,
    metadata: {
      title: 'Synapse',
      icon: '../synapse-wrapper/icon.png',
    },
  },

  // Extract metadata from an s9pk file
  electrs: {
    description: 'Provides an index for address lookups',
    optional: true,
    s9pk: 'https://github.com/org/repo/releases/download/v1.0/electrs.s9pk',
  },

  // s9pk: null when no s9pk URL is available
  'other-service': {
    description: 'Optional integration',
    optional: true,
    s9pk: null,
  },
}

Creating Cross-Service Tasks

Use sdk.action.createTask() in dependencies.ts to trigger an action on a dependency. The action must be exported from the dependency’s package.

import { i18n } from './i18n'
import { sdk } from './sdk'
import { someAction } from 'dependency-package/startos/actions/someAction'

export const setDependencies = sdk.setupDependencies(async ({ effects }) => {
  await sdk.action.createTask(effects, 'dependency-id', someAction, 'critical', {
    input: {
      kind: 'partial',
      value: { /* fields matching the action's input spec */ },
    },
    when: { condition: 'input-not-matches', once: false },
    reason: i18n('Human-readable reason shown to user'),
  })

  return {
    'dependency-id': {
      kind: 'running',
      versionRange: '>=1.0.0:0',
      healthChecks: [],
    },
  }
})

API Signature

sdk.action.createTask(
  effects,
  packageId: string,         // dependency service ID
  action: ActionDefinition,  // imported from the dependency package
  severity: 'critical' | 'high' | 'medium' | 'low',
  options?: {
    input?: { kind: 'partial', value: Partial<InputSpec> },
    when?: { condition: 'input-not-matches', once: boolean },
    reason: string,
    replayId?: string,       // prevents duplicate task execution
  }
)

Note

  • Import the action object from the dependency’s published package.
  • The dependency must be listed in your package.json (e.g., "synapse-startos": "file:../synapse-wrapper").
  • when: { condition: 'input-not-matches', once: false } re-triggers until the action’s input matches.
  • replayId prevents duplicate tasks across restarts.

Reading Dependency Interfaces

Use sdk.serviceInterface.get() in main.ts to read a dependency’s interface at runtime:

const url = await sdk.serviceInterface
  .get(
    effects,
    { id: 'interface-id', packageId: 'dependency-id' },
    (i) => {
      const urls = i?.addressInfo?.format('urlstring')
      if (!urls || urls.length === 0) return null
      return urls[0]
    },
  )
  .const()  // re-runs setupMain if the interface changes

Alternatively, services are reachable directly by hostname at http://<package-id>.startos:<port>:

const url = 'http://bitcoind.startos:8332'

Mounting Dependency Volumes

Mount a dependency’s volume for direct file access in main.ts:

const mounts = sdk.Mounts.of()
  .mountVolume({ volumeId: 'main', subpath: null, mountpoint: '/data', readonly: false })
  .mountDependency({
    dependencyId: 'bitcoind',
    volumeId: 'main',
    subpath: null,
    mountpoint: '/mnt/bitcoind',
    readonly: true,
  })

Init Order

Dependencies are resolved during initialization in this order:

restoreInit -> versionGraph -> setInterfaces -> setDependencies -> actions -> setup

setInterfaces runs before setDependencies, so your service’s interfaces are available when creating cross-service tasks.