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

Versions

StartOS uses Extended Versioning (ExVer) to manage package versions, allowing downstream maintainers to release updates without upstream changes.

Version Format

[#flavor:]<upstream>[-upstream-prerelease]:<downstream>[-downstream-prerelease]
ComponentDescriptionExample
flavorOptional variant for diverging forks#libre:
upstreamUpstream project version (SemVer)26.0.0
upstream-prereleaseUpstream prerelease suffix-beta.1
downstreamStartOS wrapper revision0, 1, 2
downstream-prereleaseWrapper prerelease suffix-alpha.0, -beta.0

Flavor

Flavors are for diverging forks of a project that maintain separate version histories. Example: if a project forks into “libre” and “pro” editions that diverge significantly, each would have its own flavor prefix.

Note

Do NOT use flavors for hardware variants (like GPU types) – those should be handled via build configuration.

Examples

Version StringUpstreamDownstream
26.0.0:026.0.0 (stable)0 (stable)
26.0.0:0-beta.026.0.0 (stable)0-beta.0
26.0.0-rc.1:0-alpha.026.0.0-rc.10-alpha.0
0.13.5:0-alpha.00.13.5 (stable)0-alpha.0
2.3.2:1-beta.02.3.2 (stable)1-beta.0

Version Ordering

Versions are compared by:

  1. Upstream version (most significant)
  2. Upstream prerelease (stable > rc > beta > alpha)
  3. Downstream revision
  4. Downstream prerelease (stable > rc > beta > alpha)

Example ordering (lowest to highest):

  • 1.0.0-alpha.0:0
  • 1.0.0-beta.0:0
  • 1.0.0:0-alpha.0
  • 1.0.0:0-beta.0
  • 1.0.0:0 (fully stable)
  • 1.0.0:1-alpha.0
  • 1.0.0:1
  • 1.1.0:0-alpha.0

Choosing a Version

When creating a new package:

  1. Select the latest stable upstream version – avoid prereleases (alpha, beta, rc) unless necessary.
  2. Match the Docker image tag – the version in manifest/index.ts images.*.source.dockerTag must match the upstream version.
  3. Match the git submodule – if using a submodule, check out the corresponding tag.
  4. Start downstream at 0 – increment only when making wrapper-only changes.
  5. Start downstream as alpha or beta – use -alpha.0 or -beta.0 for initial releases.

Version Consistency Checklist

Ensure these all match for upstream version X.Y.Z:

  • Version file exists: startos/versions/vX.Y.Z.0.a0.ts
  • Version string matches: version: 'X.Y.Z:0-alpha.0' in VersionInfo
  • Docker tag matches: dockerTag: 'image:X.Y.Z' in manifest/index.ts (if using pre-built image)
  • Git submodule checked out to vX.Y.Z tag (if applicable)

File Structure

startos/versions/
├── index.ts              # VersionGraph + exports current and historical versions
├── v1.0.0.0.a0.ts        # Version 1.0.0:0-alpha.0
├── v1.0.0.0.ts           # Version 1.0.0:0 (stable)
└── v1.1.0.0.a0.ts        # Version 1.1.0:0-alpha.0

Version File Naming

Convert the version string to a filename:

  • Replace . and : with .
  • Replace -alpha. with .a
  • Replace -beta. with .b
  • Prefix with v
VersionFilename
26.0.0:0-beta.0v26.0.0.0.b0.ts
0.13.5:0-alpha.0v0.13.5.0.a0.ts
2.3.2:1v2.3.2.1.ts

Version File Template

import { VersionInfo, IMPOSSIBLE } from '@start9labs/start-sdk'

export const v_X_Y_Z_0_a0 = VersionInfo.of({
  version: 'X.Y.Z:0-alpha.0',
  releaseNotes: {
    en_US: 'Initial release for StartOS',
    es_ES: 'Version inicial para StartOS',
    de_DE: 'Erstveeroffentlichung fuer StartOS',
    pl_PL: 'Pierwsze wydanie dla StartOS',
    fr_FR: 'Version initiale pour StartOS',
  },
  migrations: {
    up: async ({ effects }) => {},
    down: IMPOSSIBLE,  // Use for initial versions or breaking changes
  },
})

index.ts

import { VersionGraph } from '@start9labs/start-sdk'
import { v_X_Y_Z_0_a0 } from './vX.Y.Z.0.a0'

export const versionGraph = VersionGraph.of({
  current: v_X_Y_Z_0_a0,
  other: [],  // Add previous versions here for migrations
})

Incrementing Versions

When to Create a New Version File

A new file in startos/versions/ is only needed if either of the following is true:

  1. The new version contains an up or down migration. Create the file, then add the prior version to the other array in index.ts so the migration runs when users upgrade through it.
  2. You want the prior version’s release notes preserved in git history. Create the file with the new version string and notes. The prior version file can be deleted; it does not need to be added to other.

If neither applies, update the existing version file in place: rename it to the new version string, update its version and releaseNotes fields, leave other: []. No new file.

This keeps versions/ lean and the migration graph easy to read.

Upstream Update

When the upstream project releases a new version:

  1. Update git submodule to new tag
  2. Update dockerTag in manifest/index.ts
  3. Create new version file with new upstream version
  4. Reset downstream to 0

Wrapper-Only Changes

When making changes to the StartOS wrapper without upstream changes:

  1. Keep upstream version the same
  2. Increment downstream revision
  3. Apply the new-file rule — most wrapper-only bumps do not need a new file

Within an Alpha Stage: Rename In-Place

While iterating within the alpha stage (-alpha.0-alpha.1-alpha.2), don’t create a new file for every bump. Rename the existing version file, update the version string and export name, keep other: [], and move on. The alpha stage is a rolling pre-release — the version history you’d keep here is mostly churn.

Promoting Prereleases

To promote from alpha to beta to stable (or beta to stable):

  1. Create a new version file without the prerelease suffix (or with the next stage)
  2. Update index.ts to export the new version as current
  3. Move the old version to the other array — this is where migration history starts

Migrations

Migrations run when users update between versions:

migrations: {
  up: async ({ effects }) => {
    // Code to migrate from previous version
    // Access volumes, update configs, etc.
  },
  down: async ({ effects }) => {
    // Code to rollback (if possible)
  },
}

Use IMPOSSIBLE for the down migration when:

  • It is the initial version (nothing to roll back to)
  • The migration involves breaking changes that cannot be reversed
migrations: {
  up: async ({ effects }) => {
    // Migration logic
  },
  down: IMPOSSIBLE,
}

Warning

Migrations are only for migrating data that is not migrated by the upstream service itself.

setupOnInit

Use sdk.setupOnInit() to run setup logic during installation, restore, or container rebuild. It receives a kind parameter:

KindWhen it runs
'install'Fresh install
'restore'Restoring from backup
nullContainer rebuild (no data changes)

Bootstrapping Config Files

Generate passwords, write initial config files, and seed stores on fresh install:

// init/seedFiles.ts
export const seedFiles = sdk.setupOnInit(async (effects, kind) => {
  if (kind !== 'install') return

  const secretKey = utils.getDefaultString({ charset: 'a-z,A-Z,0-9', len: 32 })
  await storeJson.merge(effects, { secretKey })
  await configToml.merge(effects, { /* initial config */ })
})

Creating Tasks

Tasks reference actions, so they must be created in a setupOnInit that runs after actions are registered in the init sequence:

// init/initializeService.ts
export const initializeService = sdk.setupOnInit(async (effects, kind) => {
  if (kind !== 'install') return
  await sdk.action.createOwnTask(effects, toggleRegistrations, 'important', {
    reason: 'After creating your admin account, disable registrations.',
  })
})

Git Tag Conventions

Releases are published via git tags. The StartOS tag format is:

v{upstream_version}_{wrapper_revision}-{prerelease}
Package versionGit tag
2.1.0:7-beta.5v2.1.0_7-beta.5
0.13.5:0-alpha.0v0.13.5_0-alpha.0
26.0.0:0v26.0.0_0

Conventions:

  • Underscore between upstream and wrapper. The : from the version string becomes _ in the tag — tags can’t contain colons.
  • No package-name prefix. The tag is just the version, not myservice-v2.1.0_7-beta.5.
  • Keep the prerelease suffix (-alpha.N / -beta.N / -rc.N) when the version has one.
  • Push tags individually (git push origin <tag>), not with git push --tags.