{"id":49849497,"url":"https://github.com/robertderose/atomicnix","last_synced_at":"2026-05-14T14:09:02.182Z","repository":{"id":351197510,"uuid":"1207319541","full_name":"RobertDeRose/AtomicNIx","owner":"RobertDeRose","description":"NixOS focused on embedded devices","archived":false,"fork":false,"pushed_at":"2026-04-29T23:12:01.000Z","size":2760,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-30T00:31:13.974Z","etag":null,"topics":["embedded","nix","nixos","podman","rauc","systemd","u-boot"],"latest_commit_sha":null,"homepage":"https://robertderose.github.io/AtomicNIx/","language":"Nix","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RobertDeRose.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-10T20:11:45.000Z","updated_at":"2026-04-28T21:25:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/RobertDeRose/AtomicNIx","commit_stats":null,"previous_names":["robertderose/atomicnix"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/RobertDeRose/AtomicNIx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobertDeRose%2FAtomicNIx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobertDeRose%2FAtomicNIx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobertDeRose%2FAtomicNIx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobertDeRose%2FAtomicNIx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RobertDeRose","download_url":"https://codeload.github.com/RobertDeRose/AtomicNIx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobertDeRose%2FAtomicNIx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33028250,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["embedded","nix","nixos","podman","rauc","systemd","u-boot"],"created_at":"2026-05-14T14:09:01.014Z","updated_at":"2026-05-14T14:09:02.169Z","avatar_url":"https://github.com/RobertDeRose.png","language":"Nix","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- rumdl-disable-file MD041 --\u003e\n\n\u003cp align=\"center\"\u003e \u003cimg src=\"docs/src/atomicnix.png\" alt=\"AtomicNix logo\" width=\"300\" height=\"300\" /\u003e \u003c/p\u003e\n\nNixOS-based firmware image with atomic A/B OTA updates, automatic rollback, and hardware watchdog protection.\nThe device serves as a network security boundary (EN18031) for legacy LAN devices.\n\n\u003c!-- BEGIN mktoc {\"min_depth\": 2, \"max_depth\": 4, \"wrap_in_details\": false} --\u003e\n\n- [Supported Boards](#supported-boards)\n- [Features](#features)\n- [Architecture](#architecture)\n  - [eMMC partition layout (16 GB)](#emmc-partition-layout-16-gb)\n  - [Network topology](#network-topology)\n  - [Update and rollback flow](#update-and-rollback-flow)\n  - [Squashfs rootfs](#squashfs-rootfs)\n  - [Authentication (EN18031)](#authentication-en18031)\n- [LAN Range Configuration](#lan-range-configuration)\n- [Project structure](#project-structure)\n- [Building](#building)\n  - [With mise (recommended)](#with-mise-recommended)\n- [Running E2E Tests](#running-e2e-tests)\n  - [Run all tests](#run-all-tests)\n  - [Run individual tests](#run-individual-tests)\n  - [Test performance: macOS apple-virt vs Linux TCG](#test-performance-macos-apple-virt-vs-linux-tcg)\n  - [Interactive debugging](#interactive-debugging)\n  - [Running tests directly with Nix](#running-tests-directly-with-nix)\n- [Provisioning](#provisioning)\n- [mise Task Reference](#mise-task-reference)\n- [Flake outputs](#flake-outputs)\n- [Versioned Image Naming](#versioned-image-naming)\n- [Status](#status)\n\u003c!-- END mktoc --\u003e\n\n## Supported Boards\n\n- Rock64 (RK3328, aarch64)\n\n## Features\n\n- **Atomic OTA updates** -- installs to the inactive slot pair while the active slot stays online\n- **Automatic rollback** -- uses U-Boot boot-count fallback after repeated failed boots\n- **Hardware watchdog (currently disabled)** -- integration is implemented and tested in QEMU;\n  Rock64 runtime enablement is pending boot-stability validation\n- **Local health-check confirmation** -- commits new slots only after service and manifest container checks pass\n- **Signed RAUC bundles** -- builds reproducible, signed `.raucb` artifacts from the flake\n- **Network isolation boundary** -- keeps LAN clients off the internet; only explicit application-layer proxying is\n  allowed\n- **EN18031-ready authentication** -- ships without default credentials; per-device credentials are provisioned\n\n## Architecture\n\n### eMMC partition layout (16 GB)\n\n```text\nOffset     Size       Content          Filesystem\n0          16 MB      U-Boot           raw (idbloader + u-boot.itb)\n16 MB      128 MB     boot slot A      vfat (kernel + initrd + DTB + boot.scr)\n144 MB     1 GB       rootfs slot A    squashfs (zstd, 1 MB block)\n1168 MB    128 MB     boot slot B      vfat (created on first boot)\n1296 MB    1 GB       rootfs slot B    squashfs (created on first boot)\n2308 MB    ~13.3 GB   /data            f2fs (created on first boot; containers, state, logs)\n```\n\n### Network topology\n\n```mermaid\nflowchart LR\n    WAN[\"WAN (internet)\"] --\u003e ETH0[\"eth0\\nDHCP client\\nHTTPS (443)\\nOpenVPN (1194)\"]\n    LAN[\"LAN (isolated)\"] --\u003e ETH1[\"eth1\\nLAN gateway IP (/24)\\nDHCP server (dnsmasq)\\nNTP server (chrony)\"]\n\n    subgraph GATEWAY[\"Gateway device\"]\n        ETH0\n        CORE[\"ip_forward = OFF\\nno NAT, no routing\"]\n        ETH1\n    end\n```\n\nLAN devices get DHCP and NTP but have zero internet access. Application-layer proxying (Traefik, running as a container)\nselectively bridges WAN and LAN with authentication.\n\n### Update and rollback flow\n\n```text\n1. os-upgrade.service polls update server for new .raucb bundle\n2. rauc install writes boot + rootfs to the INACTIVE slot pair\n3. RAUC sets U-Boot env: BOOT_ORDER=B A, BOOT_B_LEFT=3\n4. Device reboots into new slot\n5. U-Boot decrements BOOT_B_LEFT on each boot attempt\n6. os-verification.service runs health checks:\n   - eth0 has WAN address, eth1 has the configured LAN gateway IP\n   - dnsmasq, chronyd running\n   - sustained 60s stability check\n7a. All pass  → rauc status mark-good (slot committed)\n7b. Any fail  → exit non-zero, boot-count continues to decrement\n8. After 3 failed boots → U-Boot falls back to previous good slot\n```\n\nThe hardware watchdog integration is implemented, but runtime enforcement is currently disabled\nduring development on Rock64 until boot stability is fully validated.\n\n### Squashfs rootfs\n\nThe read-only root filesystem contains the full NixOS system closure:\n\n- Stripped Linux 6.19 kernel (RK3328 drivers built-in, WiFi/BT as modules)\n- systemd, podman, OpenVPN, openssh, chrony, dnsmasq, nftables, RAUC\n\n### Authentication (EN18031)\n\nThe image ships with no embedded credentials. During provisioning:\n\n- `/data/config/ssh-authorized-keys/admin` -- operator's SSH public key\n- `/data/config/nixstasis/` -- planned persistent state for Nixstasis enrollment and agent credentials\n\nNormal operator access is SSH-key-only. On Rock64, `_RUT_OH_=1` in U-Boot\nenables a deterministic physical serial-root recovery path on the next boot.\n\nRemote management is intended to flow through Nixstasis using device-approved enrollment, reverse tunnels, and short-lived\nSSH credentials. The device itself keeps SSH-based LAN recovery and local gateway services.\n\n## LAN Range Configuration\n\nUse the `mise` helper task to update LAN settings across all required files in one command.\n\n```sh\nmise run config:lan-range --gateway-cidr 10.50.0.1/24 --dhcp-start 10.50.0.10 --dhcp-end 10.50.0.254\n```\n\nThis updates:\n\n- `modules/networking.nix` (`eth1` static address)\n- `modules/lan-gateway.nix` (DHCP pool, DHCP options, chrony allow subnet)\n- `scripts/os-verification.sh` (expected `eth1` IP)\n\nAfter changing the range, rebuild:\n\n```sh\nmise run check\nmise run build\n```\n\n## Project structure\n\nFor a detailed project layout overview, see\n[`docs/src/reference/project-structure.md`](docs/src/reference/project-structure.md).\n\nIt covers the main modules, scripts, tasks, tests, and documentation entry\npoints in the repo.\n\n## Building\n\nBuilds require an aarch64-linux system (native or cross). All outputs target `aarch64-linux`.\n\n### With mise (recommended)\n\n```sh\n# Install tools and hooks\nmise install\n\n# Check the flake evaluates cleanly\nmise run check\n\n# Build individual artifacts\nmise run build:squashfs        # result-squashfs/\nmise run build:rauc-bundle     # result-rauc-bundle/\nmise run build:boot-script     # result-boot-script/\nmise run build                 # .gcroots/images/image.1/\n\n# Build everything\nmise run build\n\n# Build via Lima VM (when nix-darwin linux-builder is not available)\nmise run build -- --lima\nmise run build -- --lima --vm my-builder\n```\n\n## Running E2E Tests\n\nThe core `mise run e2e` suite runs 9 NixOS VM integration tests for the RAUC lifecycle and network behavior. The flake\nalso exposes additional provisioning and forensics checks directly under `checks.*`. Tests run on both Linux (TCG\nsoftware emulation) and macOS (Apple Virtualization Framework). The mise task wrappers auto-detect the platform and\nselect the correct flake output (`aarch64-linux` or `aarch64-darwin`).\n\n### Run all tests\n\n```sh\nmise run e2e\n\n# Run all tests inside a Lima VM\nmise run e2e --lima\nmise run e2e --lima --vm my-builder\n```\n\n### Run individual tests\n\n```sh\nmise run e2e:rauc-slots          # RAUC sees all 4 A/B slots after boot\nmise run e2e:rauc-update         # Bundle install writes to inactive slot pair, slot switches A→B\nmise run e2e:rauc-rollback       # Install to B, mark bad, verify rollback to A\nmise run e2e:rauc-confirm        # os-verification health checks pass, slot marked good (~3 min)\nmise run e2e:rauc-power-loss     # Crash VM mid-install, verify slot A intact after reboot\nmise run e2e:rauc-watchdog       # Freeze systemd to trigger watchdog, verify boot-count rollback\nmise run e2e:firewall            # 2-node test: WAN allows HTTPS/VPN, LAN allows SSH/DHCP/NTP\nmise run e2e:network-isolation   # 2-node test: LAN gets DHCP/NTP, cannot reach WAN\nmise run e2e:ssh-wan-toggle      # Flag file enables/disables SSH on WAN via nftables reload\n\n# Run an individual test inside Lima\nmise run e2e:rauc-slots --lima\n```\n\n### Test performance: macOS apple-virt vs Linux TCG\n\nOn macOS, the nix-darwin `linux-builder` builds the NixOS test closures and the test driver runs QEMU natively on the\nMac host using Apple Virtualization Framework (`apple-virt`) for hardware acceleration. On Linux (e.g. Lima VM), tests\nrun under TCG software emulation without KVM. Measured wall-clock times:\n\n| Test                | macOS (apple-virt) | Linux (TCG, Lima 4 GB) | Speedup   |\n|---------------------|--------------------|------------------------|-----------|\n| `rauc-slots`        | 34s                | 132s                   | 3.9x      |\n| `rauc-update`       | 25s                | 137s                   | 5.5x      |\n| `rauc-rollback`     | 22s                | 120s                   | 5.5x      |\n| `rauc-confirm`      | 95s                | 171s                   | 1.8x      |\n| `rauc-power-loss`   | 46s                | 184s                   | 4.0x      |\n| `rauc-watchdog`     | 57s                | 315s                   | 5.5x      |\n| `firewall`          | 65s                | 205s                   | 3.2x      |\n| `network-isolation` | 68s                | --                     | --        |\n| `ssh-wan-toggle`    | 35s                | --                     | --        |\n| **Total**           | **~7.5 min**       | **~21 min** (7 tests)  | **~3.7x** |\n\nThe `rauc-confirm` test has the smallest speedup because most of its runtime is a fixed 60s sustained health check\ntimer. For CPU-bound tests (boot, install, rollback), apple-virt delivers a consistent 4-5x improvement over TCG.\n\n### Interactive debugging\n\nLaunch an interactive QEMU VM with a Python REPL for hands-on debugging:\n\n```sh\n# Debug the default test (rauc-slots)\nmise run e2e:debug\n\n# Debug a specific test\nmise run e2e:debug -t update\nmise run e2e:debug -t confirm\nmise run e2e:debug -t watchdog\n\n# Keep VM state between runs\nmise run e2e:debug -t slots --keep\n```\n\nAvailable test short names: `slots`, `update`, `rollback`, `confirm`, `power-loss`, `watchdog`, `firewall`, `net-iso`,\n`ssh-toggle`.\n\nInside the REPL:\n\n```python\ngateway.start()                          # boot the VM\ngateway.wait_for_unit(\"multi-user.target\")\ngateway.succeed(\"rauc status\")           # run a command\ngateway.shell_interact()                 # drop into a root shell\ngateway.screenshot(\"name\")              # save a screenshot\n# Ctrl+D to exit\n```\n\n### Running tests directly with Nix\n\n```sh\n# Linux\nnix build .#checks.aarch64-linux.rauc-slots --no-link -L\n\n# macOS (requires nix-darwin with linux-builder enabled)\nnix build .#checks.aarch64-darwin.rauc-slots --no-link -L\n\n# When evaluating local Darwin checks that depend on nix/tests/rauc-qemu-config.nix,\n# prefer a path flake ref so local files are visible even if they are untracked.\nnix build \"path:$PWD#checks.aarch64-darwin.rauc-slots\" --no-link -L\n```\n\n## Provisioning\n\nBuild an `.img` file that can be written to eMMC (or SD card):\n\n```sh\nmise run build\n\n# Flash to a disk device (eMMC module via USB/SD adapter)\nmise run flash /dev/disk4          # macOS — auto-detects image, uses raw device for speed\nmise run flash -i custom.img /dev/disk4   # specify image explicitly\nmise run flash -y /dev/mmcblk0     # Linux — skip confirmation prompt\n```\n\nThe image includes U-Boot plus slot A (`boot-a` + `rootfs-a`). It leaves the remaining eMMC space unallocated so initrd\n`systemd-repart` can create slot B (`boot-b` + `rootfs-b`) and `/data` on first boot before the live system mounts it.\n\nRecovery mode: hold the reset button before power-on and keep holding for\n5 seconds. U-Boot will expose the on-board eMMC as\na USB mass storage device over the Rock64 OTG USB port so you can reflash it directly from a host machine.\n\n## mise Task Reference\n\nAll tasks are run with `mise run \u003ctask\u003e`. Run `mise tasks` to list them.\n\n| Task                    | Description                                                 |\n|-------------------------|-------------------------------------------------------------|\n| `check`                 | Verify flake evaluates cleanly (`nix flake check`)          |\n| **Build**               |                                                             |\n| `build`                 | Build and retain image artifacts under `.gcroots/`          |\n| `build:squashfs`        | Build squashfs rootfs → `result-squashfs/`                  |\n| `build:rauc-bundle`     | Build signed RAUC bundle → `result-rauc-bundle/`            |\n| `build:boot-script`     | Build U-Boot boot script → `result-boot-script/`            |\n|                         | Optional `-o \u003cpath\u003e` copies the latest `.img` to a path     |\n|                         | All `build:*` tasks accept `--lima` and `--vm \u003cname\u003e`       |\n| **E2E Tests**           |                                                             |\n| `e2e`                   | Run the core 9-task E2E suite sequentially                  |\n| `e2e:rauc-slots`        | RAUC slot detection after boot                              |\n| `e2e:rauc-update`       | Bundle install + slot switch A→B                            |\n| `e2e:rauc-rollback`     | Install → mark bad → rollback to previous slot              |\n| `e2e:rauc-confirm`      | os-verification health check → mark-good (~3 min)           |\n| `e2e:rauc-power-loss`   | Crash mid-install, verify recovery                          |\n| `e2e:rauc-watchdog`     | Watchdog + boot-count rollback                              |\n| `e2e:firewall`          | WAN/LAN/VPN port allow/deny (2-node VLAN)                   |\n| `e2e:network-isolation` | DHCP/NTP/WAN isolation (2-node VLAN)                        |\n| `e2e:ssh-wan-toggle`    | SSH-on-WAN flag enable/disable                              |\n| `e2e:debug`             | Interactive QEMU VM for debugging (`-t \u003ctest\u003e`, `--keep`)   |\n| **Provisioning**        |                                                             |\n| `flash`                 | Flash image to disk device with dd + progress (macOS/Linux) |\n| **Configuration**       |                                                             |\n| `config:lan-range`      | Update LAN gateway/DHCP range across all config files       |\n| **Documentation**       |                                                             |\n| `docs:build`            | Build the documentation site (mdBook) → `book/`             |\n| `docs:serve`            | Serve the documentation site locally with hot-reload        |\n\n## Flake outputs\n\n| Output                               | Description                                              |\n|--------------------------------------|----------------------------------------------------------|\n| `nixosConfigurations.rock64`         | Real hardware NixOS system                               |\n| `nixosConfigurations.rock64-qemu`    | QEMU aarch64-virt testing target                         |\n| `packages.aarch64-linux.squashfs`    | Compressed squashfs root filesystem                      |\n| `packages.aarch64-linux.rauc-bundle` | Signed multi-slot `.raucb` bundle                        |\n| `packages.aarch64-linux.boot-script` | Compiled U-Boot `boot.scr`                               |\n| `packages.aarch64-linux.image`       | Flashable eMMC disk image (U-Boot + boot-a + rootfs-a)   |\n| `apps.aarch64-linux.rock64-qemu-vm`  | QEMU VM runner                                           |\n| `checks.aarch64-linux.*`             | Full VM test suite from `nix/tests/`                     |\n| `checks.aarch64-darwin.*`            | Same VM test suite, run natively on macOS via apple-virt |\n| `devShells.*.default`                | Development shell with mdBook                            |\n\n## Versioned Image Naming\n\nThe flashable image filename includes the pinned NixOS release series from your flake lock.\n\n- Current pinned input: `nixpkgs.url = \"github:NixOS/nixpkgs/nixos-25.11\"`\n- Output image name pattern: `atomicnix-\u003cseries\u003e.img` (for example, `atomicnix-25.11.img`)\n- Source of truth: `flake.nix` nixpkgs input ref\n\nWhen you move to a new series (for example `nixos-26.05`), update `flake.nix`/`flake.lock` and rebuild. The image name\nupdates automatically everywhere it is produced.\n\n## Status\n\nImplementation is in progress. **99 of 116 tasks complete (85%)**. All core implementation, E2E integration tests, and\nsoftware-verifiable tasks are done. Hardware bring-up is underway — NixOS boots to login prompt on Rock64 via serial\nconsole. The remaining 17 tasks require physical hardware testing or network access for container image pulls:\n\n- **Hardware verification (15 tasks)** — require a physical Rock64 board (NIC naming, DHCP/NTP on real\n  network, RAUC on-device, provisioning boot, Cockpit SSH bridge, credential verification)\n- **Integration tests needing containers (2 tasks)** — remaining hardware/runtime validation tied to real\n  provisioned workloads and on-device confirmation behavior\n\nSee `openspec/changes/rock64-ab-image/tasks.md` for the full task breakdown.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobertderose%2Fatomicnix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobertderose%2Fatomicnix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobertderose%2Fatomicnix/lists"}