---
paths:
  - "licensing-service-startos/startos/**"
  - "licensing-service-startos/Makefile"
  - "licensing-service-startos/s9pk.mk"
  - "licensing-service-startos/Dockerfile"
  - "licensing-service-startos/package.json"
---

# StartOS packaging, build & release

Platform is **StartOS 0.4.0.x** — LXC under the hood, so commands/paths reflect
that, not Docker. Wrapper is TypeScript on `@start9labs/start-sdk ^1.3.2`, bundled
with `@vercel/ncc`, Node 22.

## Build / install (from `licensing-service-startos/`)

```
make x86            # build keysat_x86_64.s9pk   (npm check+build → start-cli s9pk pack)
make arm            # build keysat_aarch64.s9pk  (verified to build; ~1.5 min Rust cross-compile)
make universal      # single multi-arch package (both arches)
make install        # install newest *.s9pk to the host in ~/.startos/config.yaml
make clean          # wipe artifacts + node_modules (auto-reinstalls on next build)
npm run check       # tsc --noEmit on the wrapper
npm run prettier    # prettier --write startos  (NOT enforced; see testing.md)
```

Auth for `make install` is the developer key at `~/.startos/developer.key.pem`
(private — never commit/share).

`make install` prints only "🚀 Installing …" and is otherwise silent on success.
Verify it landed with `start-cli package installed-version keysat` (returns the
box's `0.2.0:N` ExVer revision) plus `GET /healthz` on the public URL. The
daemon's `/info` `version` field reports `CARGO_PKG_VERSION` (`0.1.0`), **not** the
s9pk revision, so it can't tell you which `:N` is running.

## Clean-box build bootstrap (`prepare.sh`)

`prepare.sh` (in `licensing-service-startos/`) installs every HOST prerequisite a
fresh Debian/Ubuntu box needs before `make`: apt prereqs (build-essential, jq, git,
squashfs-tools(-ng)), Node 22 (NodeSource), Docker (+ QEMU binfmt for cross-arch),
and **start-cli** via the official installer —
`curl -fsSL https://start9.com/start-cli/install.sh | sh` (drops the binary in
`~/.local/bin`). Rust is **not** installed on the host; the daemon compiles inside
the Dockerfile (`FROM rust:1.88`). Idempotent; apt-based only. Caveat: `prepare.sh`
is a 0.3.5.x submission convention — the 0.4.x docs never mention it, so Start9's
0.4.x build flow may not actually invoke it (confirm before relying on it).

## ALWAYS: bump the version before building

Edit `startos/versions/v0.2.0.ts` — increment `version: '0.2.0:N'` and prepend a
`ROUTINE_NOTES[0]` entry — **before** `make x86` or `publish.sh`. Start9 0.4.x
silently no-ops an install whose version equals what's already installed. Run
`cargo check` from `licensing-service/` first so the build doesn't fail downstream.

## Release (operator-local scripts, in `~/.keysat/`, gitignored)

```
~/.keysat/publish.sh                       # version-gate → make universal → FileBrowser upload → registry register → GitHub mirror → make install (deploy to live host, best-effort)
~/.keysat/deploy-sites.sh                   # DEFAULT: deploy ALL public sites (landing + docs). Re-uploads every site so the live HTML always matches its repo, even unmodified ones — preferred over a single-site deploy so nothing drifts.
~/.keysat/deploy-sites.sh docs              # or name one/more explicitly: landing | docs
```

Prefer the no-arg form (all sites) whenever pushing any site change, so every
public page is at its repo's latest. The site repos are independent of the s9pk
release, so deploying them is separate from `publish.sh`.

Credentials: `~/.keysat/filebrowser.env` (`chmod 600`); env `KEYSAT_FB_USER`,
`KEYSAT_FB_PASS`. Daemon runtime env: `KEYSAT_ADMIN_API_KEY`, `KEYSAT_LICENSE`,
`KEYSAT_OPERATOR_NAME`, `KEYSAT_PUBLIC_URL`, `BTCPAY_URL`, `BTCPAY_BROWSER_URL`,
`BTCPAY_PUBLIC_URL`.

## Arch & manifest

- Both `x86_64` and `aarch64` build cleanly (arm verified 2026-06-12). `publish.sh`
  now builds a single universal `keysat.s9pk` via `make universal` (both arches in
  one package — the registry holds one s9pk per version, so a per-arch split won't
  work), matching the manifest's `['x86_64', 'aarch64']` claim. **Pending a
  verification build**: the combined universal pack hasn't been run end-to-end yet;
  confirm the registry index lists both arches on the first publish. (`riscv` target
  exists, unverified; not in the manifest, so `make universal` excludes it.)
- Manifest `license` is `LicenseRef-Keysat-1.0`, matching the package-root
  `LICENSE` SPDX id.

## Known issue: registry icon

`start-cli registry info set-icon` stores the icon fine (round-trips at 96×96 and
256×256 PNG), but the StartOS marketplace header may still show the storefront
fallback. The operator may have to paste the data URL into the local "Configure
Registry" modal manually. Confirm visually before claiming "done."
