What runs on the board comes from two repositories (satlink-sdr for the bitstream and the
daemon, stellar-fw for the firmware) and three build flows. A release ties them together:
one bundle, one identifier, and a refusal whenever the pieces do not match.
The release identifier#
r<YYYYMMDD>-<first 12 hex of sha256(commits + diffs of both repos + md5 of the .bit)>[-dirty]- It is derived from the inputs (both repositories' commits and uncommitted diffs, and the
bitstream's md5), not from the outputs. That is what allows it to be burnt into
satlinkd, which is itself one of the outputs. - It is deterministic: the same sources give the same identifier, so rebuilding a deployed release produces a bundle that compares equal.
-dirtymarks a bundle built from trees with uncommitted changes.
The running board reports it:
curl -s http://192.168.2.1:8080/api/v1/version{"firmware": "0.1.0", "api": "v1", "fpga": "0xDEADBEEF", "release": "r20260911-829886a51ba2-dirty"}| Field | Meaning |
|---|---|
release | The identifier burnt into the running satlinkd; "unreleased" for a build that did not go through the release script |
fpga | The PL's BUILD_ID register, read live. Today every build carries the default 0xDEADBEEF, so it tells a loaded bitstream from none, not one bitstream from another |
The bundle#
A bundle lives in release/<id>/:
| File | Content |
|---|---|
system_top.bin | The bitstream, converted by bootgen |
satlink.xsa | The hardware description exported with it |
BOOT.bin, u-boot.img, uEnv.txt | Boot loader and environment |
uImage, devicetree.dtb | Kernel and device tree |
uramdisk.image.gz | Root filesystem, including satlinkd and the DSL files |
satlinkd | The daemon binary, for reference and checks |
manifest.json, MANIFEST.txt | md5 of every file, provenance of both trees |
Building a release#
The bitstream must already exist in pl/top/build/ (see
Building the Bitstream).
tools/make-release.sh # 1. computes the id, builds satlinkd with it,
# then REFUSES: the ramdisk does not carry it yet
(cd ~/Code/stellar-fw && ./build.sh build fishball7020) # 2. firmware build, burns satlinkd into the ramdisk
tools/make-release.sh # 3. same id, coherent bundlemake-release.sh options: --force (build from trees with uncommitted changes),
--no-satlinkd (skip the cross-compilation; such a bundle cannot be deployed).
The checks, each of which refuses:
| Check | Catches |
|---|---|
Modified tree without --force | A bundle nobody could rebuild |
satlinkd does not contain its id | A recycled build artefact reporting a previous id |
uramdisk.image.gz does not contain the id | A new daemon beside an old ramdisk: both files exist and look fine, but the board would run the old daemon |
| Bundle differs from its manifest | A file changed after the build |
| Incomplete bundle | Half a release |
| The target is not the real SD card | The board's own USB storage has the same label SATLINK; writing to it succeeds and changes nothing |
Deploying#
tools/deploy-release.sh # newest bundle under release/, to a mounted SD card
tools/deploy-release.sh release/r2026... # a specific bundle
tools/deploy-release.sh --over-ssh # to the SD card inside the running boardWith --over-ssh, the bundle is written to the card while it stays in the board (the FAT
partition is mounted on the board), md5 sums are computed on the board, and files are written
under a temporary name and renamed at the end, so an interrupted transfer never leaves a truncated
system_top.bin. The board is found with pl/top/bringup/resolve-board.sh.
Then reboot and verify:
tools/deploy-release.sh --verify <id>--verify asks the running board for its release id and compares. It is the only check that
proves a deployment took: writing files and running them are two different things.
Quick iterations#
For a daemon change, rebuilding the firmware is slow. The RAM-disk root filesystem is writable,
so a new satlinkd can be copied in for the current boot:
fw/make-satlinkd.sh # cross-build into fw/out/ (wraps stellar-fw's script)
scp -O fw/out/satlinkd root@192.168.2.1:/tmp/
ssh root@192.168.2.1 '/etc/init.d/S99satlinkd stop && mv /tmp/satlinkd /usr/bin/satlinkd && /etc/init.d/S99satlinkd start'- A running executable cannot be overwritten: copy to
/tmp, stop, move, start. - Burn a marker into the id (
SATLINK_RELEASE_ID=<id>+scp-<sha>) so/api/v1/versionsays the binary was hand-deployed. - It disappears at the next reboot, by construction. Never leave a campaign running across a reboot on a hand-deployed binary: it will measure the SD card's daemon without saying so.
For a bitstream-only iteration, pl/top/deploy-sd.sh converts the .bit to system_top.bin
and copies it to the SD card with md5 verification. It leaves the daemon, kernel and device tree
as they were, which is right for a PL experiment and wrong for a release.
Known limits#
- The manifest vouches for itself. It lists the md5 of every file except itself; editing it by hand goes unnoticed. It protects against drift, not tampering.
- The PL's
BUILD_IDdoes not identify builds. Nothing at runtime can tell two bitstreams apart; the release id is the only link between a board and the sources of its bitstream.