Skip to content

0.2.0

Choose a tag to compare

@github-actions github-actions released this 09 Jul 17:39

[0.2.0] — 2026-07-09

Hardware verification + hardening + boot-time tuning pass. All 0.1.0
functionality still applies; this release focuses on making the appliance
faster to boot, safer to compromise, and more visible in the Devices tab.

Added

  • Dump a device to a disk image (new). Devices tab gets a Dump…
    button; pick a compression (xz / gz / none) and the browser streams
    the raw block device down as a downloadable image with a generated
    filename (airlock-<label>-<date>.img[.xz|.gz]). Read-only op —
    sync() first, then os.Open("/dev/<parent>"), pipe through the
    compressor (xz -c -T 0 for parallel or stdlib compress/gzip),
    write straight to the response body. No unmount, so SMB sessions
    aren't disturbed. Endpoint: GET /api/devices/{parent}/dump?compression=….
  • fsck per partition (new). Devices tab gets a Check button on
    every partition with a supported filesystem. Modal picks Check
    (read-only) or Repair (auto-fix, -p / -a / -y per tool),
    unmounts, runs e2fsck / fsck.vfat / fsck.exfat / ntfsfix /
    fsck.hfsplus, streams stdout+stderr live via SSE into a scrollable
    log in the modal, remounts. Endpoint:
    POST /api/partitions/{name}/fsck?mode=check|repair.
  • stage-airlock/06-fast-boot: pi-gen sub-stage that bakes the
    same service disables + dtoverlay=disable-bt we ship in
    AIRLOCK_FAST_BOOT=1 directly into the image. Fresh flashes boot in
    ~14 s without needing the installer flag.
  • Release workflow now builds .img.xz: tagging x.y.z now runs
    a second CI job (~45 min) that clones pi-gen inside GitHub Actions,
    syncs stage-airlock/, produces the flashable image, and attaches
    airlock-<tag>-linux-arm64.img.xz + sha256 to the same GitHub
    Release alongside the binary tarball + install bundle.
  • Flash OS images to a drive (new). Devices tab gets a Flash…
    button; the modal takes a .img, .img.xz, or .img.gz file, streams
    the upload through the appropriate decompressor (xz -dc or stdlib
    compress/gzip) straight onto /dev/<parent> in 4 MB blocks with
    per-500 ms SSE progress events. Zero intermediate storage — a 3 GB Pi
    OS image never lands on the airlock SD, only on the target. Adds
    xz-utils to the installer's package list and the pi-gen stage.
    Endpoint: POST /api/devices/{parent}/flash?compression=none|xz|gz
    (raw body, text/event-stream response). Safety-gated to USB
    devices, uses the same quarantine mechanism as format so the daemon
    doesn't auto-mount the fresh partition table mid-write.
  • Mount button on the Devices tab for USB partitions that have a
    supported filesystem but aren't currently mounted by airlock. Backed
    by POST /api/partitions/{name}/mount — safety-gated to USB-attached
    devices, fires udevadm trigger --action=add and the daemon's normal
    event handler picks up the mount.
  • AIRLOCK_FAST_BOOT=1 installer flag. Disables services not needed
    on a headless appliance (bluetooth, hciuart, triggerhappy, ModemManager,
    unused Samba flavours nmbd/winbind/samba-ad-dc, apt-daily
    timers, e2scrub, dphys-swapfile, NetworkManager-wait-online,
    rpi-eeprom-update); masks keyboard-setup and console-setup;
    disables cloud-init post-first-boot; adds dtoverlay=disable-bt to
    /boot/firmware/config.txt to skip Bluetooth radio init.
  • AIRLOCK_DISABLE_WIFI=1 installer flag. Adds dtoverlay=disable-wifi
    after checking that eth0 is up — refuses to run if not, so it can't
    accidentally orphan a Wi-Fi-only Pi.
  • AIRLOCK_HARDEN_USB=1 installer flag plus scripts/modprobe-airlock.conf.
    Blocks HID (keyboards / mice) and CDC-* (USB Ethernet / serial) driver
    binding at attach time while leaving USB mass storage alone —
    neutralizes "BadUSB" / "USB Rubber Ducky" attacks against the console.
  • systemd seccomp + prctl sandbox on airlockd.service:
    NoNewPrivileges, LockPersonality, RestrictSUIDSGID,
    RestrictRealtime, RestrictNamespaces,
    RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK.
    Mount-namespace-inducing directives are deliberately excluded (see
    Fixed below).
  • New docs sections in docs/install.md: Optional: faster boot
    (with per-service explanation, measured impact, and a manual recipe),
    Additional hardening (systemd sandbox + optional USB blocklist),
    and Flashing OS images to a drive.
  • "Why the name" section in the README explaining the security
    posture — Airlock as the isolation chamber between untrusted media
    and your primary machine (nosuid,nodev,noexec, no autorun, BadUSB
    neutralization on a headless box, kernel FS-parser CVEs land on the
    Pi instead of your laptop). Explicit note that endpoint hygiene
    (AV, sandbox) is still the user's job.
  • Devices tab payload now includes can_mount, can_relabel, and
    can_fsck flags per partition, so the UI shows only the actions
    that make sense for each filesystem.

Fixed

  • Duplicate mDNS advertisement removed. Samba's built-in mDNS
    publisher was shipping a competing _device-info._tcp (and
    _smb._tcp) record under the NetBIOS name AIRLOCK with
    model=MacSamba, which shadowed the record our Avahi service
    publishes. Suppressed via multicast dns register = no in
    smb.conf. The Avahi service still advertises
    _device-info._tcp with model=MacPro7,1@ECOLOR=226,226,224; the
    actual icon Finder draws depends on macOS's undocumented icon
    table, and reliably matching a specific "network storage"
    appearance (e.g. TrueNAS's) requires macOS-side vendor recognition
    we can't advertise.
  • SMB shares appeared empty when the systemd sandbox created a private
    mount namespace.
    ProtectSystem=strict + ReadWritePaths bind-mounted
    /mnt/airlock into the service's own namespace as slave, so mounts
    airlockd made were invisible to smbd running in the host namespace.
    MountFlags=shared didn't fix it (a slave mount only propagates
    host→service, not back). Sandbox reduced to seccomp/prctl only —
    no private mount namespace, mounts propagate correctly.
  • Format operation "device or resource busy" during wipefs — added a
    device quarantine so the daemon ignores auto-mount events for the
    target device between unmount and mkfs.
  • udevadm trigger --subsystem-match=block no longer accidentally
    matches the Pi's own boot media (mmcblk*) — the udev rule requires
    ID_BUS=usb, and every mount pass additionally cross-checks
    /proc/self/mountinfo before touching a device.
  • Windows-friendly Copy button on the Mounts tab now sends
    \\host\share on Windows and smb://host/share everywhere else.

Boot-time measurements (Pi 4 / Trixie reference hardware)

Configuration multi-user.target reached
Stock Pi OS Lite Trixie 24.0 s
+ AIRLOCK_FAST_BOOT=1 14.2 s
+ AIRLOCK_DISABLE_WIFI=1 13.7 s