Updating
Applies to SynapseOS builds from 2026-10-09 (20261009-141152 and later). See What changed.
One update, the whole system, and the old one stays on disk.
Where updates come from
A unit follows a channel, named in /etc/synapseos/update.conf:
UPDATE_BASE_URL=<provided by Gen0Sec, specific to your platform>
UPDATE_CHANNEL=stable
stable is the default. A unit delivered with network updates has UPDATE_BASE_URL set
already; with it unset the unit has no channel and takes updates from external media only.
Each platform build has its own channels, so the URL you were given applies to units of
that platform and no other.
Setting up network updates
A unit takes updates over the network once UPDATE_BASE_URL points at its platform's
channels and carries an update key. Updates never run on their own: the unit contacts the
update host only when you run check or update, and you choose when to reboot.
What you need from Gen0Sec:
- the platform name of your units, which is part of the URL;
- an update key. A key issued for the package repositories does not open the update channels; ask for one for SynapseOS updates.
Write the URL, with the key, into the unit's configuration:
UPDATE_BASE_URL=https://<organization ID>:<update key>@releases.gen0sec.com/synapseos/<platform>
UPDATE_CHANNEL=stable
The key travels as the password of the URL, over HTTPS. The user name before the colon is not checked; your organization ID keeps it recognisable. The file holds a credential, so make it readable by root only:
chmod 600 /etc/synapseos/update.conf
The file is on the unit's persistent configuration, not on the system an update replaces, so it survives every update and rollback. Behind an HTTP proxy, add the proxy line from Network requirements.
Prove the path:
synapseos-ab-update check
| Result | Meaning |
|---|---|
=> up to date or => an update is available | The URL, the key and the channel signature are all good |
401 in the error | No key in the URL, or a key that does not open the update channels |
404 in the error | The key is accepted, but the platform or the channel in the URL does not exist |
| A signature error | The answer did not come from Gen0Sec unaltered; do not override it, report it |
synapseos-ab-update status prints the channel's URL in full, key included, and an error
from a failed download can too. Before pasting either into a ticket, remove the key:
synapseos-ab-update status 2>&1 | sed -E 's#//[^@/]*@#//***@#'
Choosing a channel. stable is what a production unit follows. Where Gen0Sec has
published a canary channel for your platform, it receives a release first; point a test
unit at it, or take one update from it without changing the file:
synapseos-ab-update update --channel=canary
Units on builds before 2026-10-12 need one correction after update and before the
reboot; see Units on builds before 2026-10-12.
Checking
synapseos-ab-update check
It prints the version you are running and the version the channel names, then either
=> up to date or => an update is available. It changes nothing. In a script, note that
it exits non-zero when the unit is up to date.
synapseos-ab-update status
prints, on a unit that has taken one update and confirmed it:
disk : /dev/sda
active slot : B (verified)
slot A : 20260917155226-6.18.48 serial 20260917155226 kernel 6.18.48-synapseos
slot B : 20260918191637-6.18.48 serial 20260918191637 kernel 6.18.48-synapseos <- running
channel : stable @ <your UPDATE_BASE_URL>
boot entries : synapseos_a.efi synapseos_b.efi
loader defaul: synapseos_b.efi
| Line | Meaning |
|---|---|
active slot | Which of the two slots is running. (verified) means its system passed the integrity check for this boot; it does not say whether the slot has been confirmed good |
slot A, slot B | The version, serial and kernel each slot holds; <- running marks the active one. A slot never written says empty (never installed) |
channel | The channel followed and the base it is read from; unset means offline updates only |
boot entries | The files the bootloader can boot. A slot still on trial carries a counter in its name, synapseos_b+3.efi; a confirmed one does not. That counter, not (verified), is what says a slot was confirmed |
loader defaul | Which entry boots next |
Installing over the network
synapseos-ab-update update
systemctl reboot
synapseos-ab-update status
update downloads, verifies and writes the update into the inactive slot, then points the
bootloader at it. The reboot is yours to schedule: nothing changes until then. Take a
backup of /etc before it, as a rule; the update does not touch /etc,
but a configuration you cannot restore is the one thing a rollback does not give back.
After the reboot, confirm the unit is doing its job, not only that it booted:
synapseos-ab-update status # the new version is the running slot, the previous one beside it
synapseos-synapse status # every service runs the Synapse version you expect
systemctl is-active synapse-agent # active
journalctl -u synapse-agent -b -n 20 # "Started Synapse", and nothing after it that says failed
On a pair, update the standby first; see High availability.
The update refuses, before writing anything:
- an update that is not newer than the running system, the same version included;
- an update of a different variant, which would silently change the unit's security posture;
- anything whose signature does not verify.
--allow-downgrade overrides the first check only: reinstalling an older or equal version
is a decision, and the flag records it. The variant check cannot be overridden, although
the refusal message suggests the same flag; a unit changes variant by being re-imaged. The
signature check is never overridden.
Installing offline, from external media
A unit with no network path to updates installs the same update from external media. It
needs no UPDATE_BASE_URL and no update key; the update carries its own signature, and the
unit checks it exactly as it does for a network update.
What you receive from Gen0Sec: one archive per release and platform,
synapseos-<variant>-<version>.bundle.tar.zst, and its checksum file,
synapseos-<variant>-<version>.bundle.tar.zst.sha256. The variant (hardened, for
example) must match the unit's, which grep -o '"variant": *"[^"]*"' /var/lib/synapseos/slots.json prints; install refuses any other. synapseos-ab-update status shows the running version.
Preparing the media
On a Linux machine, with a USB drive of at least 8 GB. Formatting erases the drive, so
check the device name with lsblk first:
f=synapseos-<variant>-<version>.bundle.tar.zst
echo "$(cut -d' ' -f1 "$f.sha256") $f" | sha256sum -c # "<file>: OK"
mkfs.ext4 -L SYNAPSEOS-UPD /dev/sdX1 # the drive's partition, not the machine's disk
mount /dev/sdX1 /mnt/usb
mkdir /mnt/usb/update
tar --zstd -xf synapseos-<variant>-<version>.bundle.tar.zst -C /mnt/usb/update
ls /mnt/usb/update # manifest.json manifest.json.asc roothash uki.efi usr.hash.img usr.img
umount /mnt/usb
Use ext4. The system image, usr.img, is a single 5 GiB file, which FAT32 cannot hold.
Unpack on the media, or on the machine preparing it, never on the unit: the
appliance's root filesystem is small, and the installer streams the update from the media
straight into the inactive slot. The archive unpacks its files into the directory you name,
so give it one of its own.
Installing
synapseos-ab-update status # note the running version
mkdir -p /mnt/media
mount -o ro /dev/disk/by-label/SYNAPSEOS-UPD /mnt/media
synapseos-ab-update install /mnt/media/update
umount /mnt/media
systemctl reboot
install takes the directory that holds manifest.json. Read-only media is fine; nothing
is written to it. Before writing anything it checks the signature on the manifest, that
every file matches the manifest, and that the kernel image is signed. It refuses an update
that is not newer, or of a different variant, just as update does
(Installing over the network).
On a unit running a build from before 2026-10-12, correct the bootloader entry after
install and before the reboot; see
Units on builds before 2026-10-12.
After the reboot, run the same checks as after a network update. The new slot boots on trial and falls back by itself if it does not come up healthy; see below. Remove the media once the unit is confirmed.
Units on builds before 2026-10-12
The updater on these builds marks the new slot as the bootloader's default. The
bootloader does not count trial boots of a default entry, so if the new slot fails, the
unit does not fall back to the old one by itself. After update or install, and before
the reboot, mark the entry preferred instead. Do it only on a bootloader that
understands preferred, which is systemd-boot 260 or later:
bootctl status | grep 'Product: systemd-boot' # needs 260 or later
grep -E '^(default|preferred) ' /efi/loader/loader.conf
sed -i -E 's/^default (synapseos_[ab])(\.efi)?$/preferred \1.efi/' /efi/loader/loader.conf
grep -E '^(default|preferred) ' /efi/loader/loader.conf # preferred synapseos_<a or b>.efi
If the version is below 260, leave default in place and contact
support@gen0sec.com: on an older bootloader, an entry marked
preferred is ignored, and the unit would boot whichever slot sorts first.
After the change, synapseos-ab-update status on these builds shows an empty
loader defaul: line until the reboot. That is expected: the old updater reads only
default lines, so trust the grep above instead. The 2026-10-12 build you update to
writes preferred itself from then on.
What happens on the first boot after an update
The new slot boots as a counted entry. If the kernel does not come up, or the unit boots but Synapse (and the jailer, on hardened units) does not reach a running state, the bootloader stops trying after three attempts and returns to the previous slot by itself. Only once the appliance's services are up is the new slot confirmed good: the counter disappears from its boot entry, and from then on it boots like any other slot. A boot that fails for any other reason later, once a slot is confirmed, is not caught by this: the safety net exists for the trial boots after an update.
Going back
If the new version runs but behaves worse than the old one:
synapseos-ab-update rollback
systemctl reboot
The previous slot is still on disk, exactly as it was. Rolling back the OS does not change
anything in /etc.
What an update is
A SynapseOS update is not a set of packages. It is a complete new system (/usr, with
every binary, library and kernel module) together with the kernel that matches it, signed
as one. The unit holds two copies of the system, called slots A and B. An update is written
into the slot that is not running, verified in place, and only then made the one to boot
next. The running slot is never touched, which is what makes going back possible.
Two signatures are checked before anything is written, and a third on every boot:
| Signature | Covers | Checked by |
|---|---|---|
| The update's manifest | Its version and the digest of every file in it | The updater, before writing |
| The channel pointer | Which update the channel currently names | The updater, on every check |
| The kernel image | Kernel, initrd and command line, which carries the hash of the new system | The firmware, at every boot |
Hosting adds nothing to this: an update served from a mirror you do not trust can be withheld, but not forged.
Synapse versions and OS updates
An update brings new Synapse versions with it, but does not change which one runs. A unit
with a version pinned keeps it, as long as the new image still carries that version; one
with no pin runs the newest. If a pinned version is no longer shipped, the newest runs
instead, and synapseos-synapse status is where that shows. After the update:
synapseos-synapse list
synapseos-synapse use <version>
Switching Synapse versions is a service restart, not an OS update, and it does not use the other slot, which stays free for rolling back the OS itself. See Synapse versions on the appliance.