Skip to content

v14.8.0 (2026-10-05) - "Live Boot Release"

Choose a tag to compare

@github-actions github-actions released this 04 Oct 23:56
· 241 commits to main since this release

Live boot support enables running D-PlaneOS directly from USB without installation. Full daemon and UI run in RAM, existing ZFS pools are auto-discovered and imported, and persistence is optional via USB drive.

Live Boot Features (NEW)

  • Bootable ISO: Same release ISO can be used as installer (traditional) or live boot (no installation needed). Dual-purpose build with branching logic at boot time.

  • Ephemeral by default: System runs entirely from RAM using impermanence module. All state (configs, logs, container data) is lost on shutdown unless persistent USB attached. Zero disk writes to boot USB (read-only squashfs).

  • Auto-import ZFS pools: On boot, systemd service auto-discovers and imports any ZFS pools found on attached drives. Pools appear in UI immediately without manual intervention. Existing pool data preserved and accessible.

  • Full daemon capability: Everything works the same as installed version: SMB/NFS/iSCSI sharing, Docker containers, dataset management, snapshots, replication, UI accessible at http://<ip>/ (nginx, port 80).

  • Optional persistence: Attach USB drive labeled dplane-persist to preserve daemon state across reboots. Without persistence, state resets on shutdown. Useful for trial/rescue scenarios or persistent operation after first boot.

  • VM test suite: nixos/tests/live-boot.nix validates boot, daemon startup, ZFS auto-import, Docker engine, ephemeral root, and clean shutdown. Runs on every CI build.

Live Boot CI Fix

The live-boot VM test had failed since v14.7.0 with dplaned.service in state "failed":

  • PostgreSQL over the Unix socket: NixOS PostgreSQL does not listen on TCP unless enableTCPIP is set, so the pg_isready -h localhost pre-start probe could never succeed, and the default TCP DSN had no password for NixOS's default pg_hba.conf. The live config now connects via host=/run/postgresql, creates the role and database with ensureUsers/ensureDatabases and maps root to dplaneos via peer auth. The pre-start probe follows the DSN. Installed systems keep their existing DSN default.
  • Startup ordering: dplaned is ordered after postgresql-setup.service (which creates roles) and waits up to 60s for the database before running migrations instead of exiting.
  • Logging: daemon output goes to the journal again (journalctl -u dplaned), replacing the kmsg experiment. The VM test dumps unit status and journals on failure and checks the UI on port 80, not 9000.

Guided Storage UX (NEW)

  • Automatic pool layout: Create Pool picks disks by layout, disk size group (optionally treating the size as a minimum), width and vdev count, with a live preview of vdevs, usable capacity and fault tolerance. Warnings for stripes, RAID-Z1 on 8 TB+ disks, RAID-Z wider than 12 and leftover disks. Manual topology remains available.
  • Pool import dialog: GET /api/zfs/pool/importable and POST /api/zfs/pool/import import exported or foreign pools by GUID, with optional rename and explicit forced import for pools last used by another host. Refused on an HA standby.
  • Dataset presets: Generic, SMB share (case-insensitive), Apps/containers and Media. POST /api/zfs/datasets accepts acltype (posix/off; OpenZFS on Linux has no NFSv4 ACLs) and casesensitivity, which is passed to zfs create -o through a new whitelisted command.
  • Rollback safety levels: POST /api/zfs/snapshots/rollback takes mode = safe | destroy_newer | destroy_clones (no flag / -r / -R). The dialog lists the snapshots a destructive level would delete. ZFS refusals return 409 with ZFS's message.
  • Per-share SMB options: Time Machine (with optional size cap), Previous Versions (shadow copies), recycle bin and hosts allow/deny are set per share (migration 00012; existing shares inherit the previous global toggles). Share purposes in the UI: Default, Time Machine, Windows with file history, Public read-only. New optional share keys in state.yaml; omitted keys are left unmanaged.

Fixes

  • Disk pickers: Create Pool, RAID-Z expand, wipe and cache/log/special/spare dialogs read a path field the disk API never returns, so all disks shared one undefined id (ticking one ticked all). They now use by_id_path/dev_path and list only unused disks.
  • Rollback: both rollback dialogs always sent force (zfs rollback -r), silently destroying newer snapshots; the Pools page copy posted the wrong field and always failed; failures were reported as success.
  • Hot-swap auto-import: ran zpool import ... -g <guid>; zpool import has no -g flag, so automatic imports always failed. The GUID is now positional, via the whitelisted command.
  • Samba on NixOS: the daemon wrote its own [global] (security, workgroup, map to guest) into the shares include, overriding modules/samba.nix (including AD security = ads). It now writes share stanzas only and re-opens [global] at the end of the include.
  • GitOps shares: GitOps used its own reduced smb.conf writer that dropped browsable, masks and VFS options on every apply; it now shares the renderer with the UI (internal/smbconf). SyncDB upserts instead of deleting and re-inserting all shares, and writes read_only/guest_ok as integers. The generated state.yaml keeps share comments.
  • Share editing: the edit form never sent the share id, so every edit failed with "Share ID required".
  • Time Machine discovery: the Bonjour _adisk record advertised a non-existent share "DPlaneOS"; it now lists the actual Time Machine shares.
  • Dataset creation: ZFS failures (200 {success:false}) were shown as success.

NixOS Runtime Fixes (full-codebase audit)

  • Database on installed systems: non-HA installs had no PostgreSQL. services.dplaneos.database.createLocally (default without HA) runs it with data in dbPath, socket DSN and peer auth, and refuses to start an empty cluster next to existing data.
  • Daemon tools on PATH: zfs, zpool, lsblk, smartctl, exportfs, git, ssh, rsync, samba, avahi and the other ~80 tools the daemon executes were not on the service PATH; storage, replication and GitOps operations failed with "executable not found".
  • SMB shares reach Samba: -smb-conf is now passed, so UI shares land in the file samba.nix includes (they went to the read-only /etc/samba/smb.conf); the file is rewritten at startup.
  • Schedules run, also after reboots: snapshot, scrub, rsync and SMART timers are runtime units on NixOS with absolute ExecStart, and all platforms re-install them at daemon start (they embed the per-boot token, so they failed after every reboot).
  • Settings persist through rebuilds: hostname, timezone, NTP servers, ARC limit, swappiness, the web UI TLS certificate and the UPS policy go through the JSON bridge into dplane-generated.nix.
  • FTP and S3: vsftpd (services.dplaneos.ftp.enable, default on, with PAM) and MinIO (services.dplaneos.s3.enable) run as daemon-managed runtime units with config in /var/lib/dplaneos; FTPS gets a self-signed certificate.
  • UPS: new services.dplaneos.ups (driver, port) configures NUT; the UPS page sets low-battery level, final delay and action.
  • Replication key in /var/lib/dplaneos/ssh (ProtectHome hid /root/.ssh). Time Machine Bonjour record names the real shares.
  • Hardening phases (resource watcher, health checks, crash-recovery journal, event log, rollback manager) now start with the daemon; their migrations (feature_flags, operation_journal, audit_events) are applied, and the Settings Features tab works.
  • OTA: the inactive slot is resolved by partition label to exactly one device.
  • UI: Expand Pool adds a data vdev; the Network page gets per-interface data (it was blank and its VLAN/bond tabs crashed); actions whose failures came back as 200 {success:false} now show the error.
  • Web UI layout (regression since v14.6.0): #root was a CSS grid while the shell positions the sidebar and top bar as fixed elements, so <main> landed in the 260px sidebar column and its margin left it 0px wide: every page body rendered blank. Tooltip wrappers no longer take 100% width in flex rows (squeezed the top bar search and the sidebar user row).
  • Datasets created from the UI were not mounted on the host: dplaned ran in a private mount namespace (ProtectSystem/ProtectHome/PrivateTmp/ReadWritePaths), so its ZFS mounts stayed inside the service until a reboot, invisible to Samba and NFS, and mountpoints outside the allow-list could not be created. The namespace is removed in the NixOS module, the Samba and fencing modules (they appended ReadWritePaths) and the Debian unit, and a module assertion keeps it out; NoNewPrivileges and the capability bounding set remain.
  • No more silently dropped settings (inputs that were skipped or failed while the API reported success):
    • Dataset create: compression, quota, atime, sync, recordsize, xattr, secondarycache, acltype and dedup are validated up front (400, nothing created) and applied atomically with zfs create -o.
    • Bond / VLAN create: slaves must exist, IPs must be CIDR; every step is checked and a half-built interface is removed on failure. Skipped slaves were previously still saved for boot.
    • Interface configure: the Network page posted to the netplan endpoint with the wrong body; it now uses PUT /api/system/network, supports DHCP, and the promised auto-revert is real (the networkd file is restored unless confirmed within the countdown; boot state is saved on confirm). The MTU field, which was never applied, is removed. DNS: invalid entries are rejected instead of filtered, and the form sent the wrong field.
    • Netplan endpoint: answers 501 where netplan is missing (NixOS) and restores the old file when netplan apply fails.
    • CTDB, NTP, nginx reload, network persistence, networkctl reload: failures are returned instead of logged.
    • SMB shares: a failed smb.conf write or Samba reload is reported (the share is saved, Samba not updated) instead of success.
    • Time Machine discovery: avahi-daemon --reload always failed ("Operation not permitted": avahi has dropped to its own user and dplaned has no CAP_KILL), so the Bonjour record never refreshed. Avahi is now reloaded through systemd, and a failure is shown as a warning on the share.
    • Schedules (snapshot, scrub, rsync, SMART): hook timers run bash -c "curl ...", but bash was not on dplaned's PATH, so ExecStart stayed relative and systemd could not run it on NixOS; install failures were only logged. bash is on the PATH, an unresolvable program is now an install error, and the schedule pages report it.
    • Scheduled jobs never ran: timer hooks reach the daemon over its Unix socket, where the request address is "@", but the hook check only accepted 127.0.0.1/::1, so every snapshot, rsync and SMART timer call was rejected as unauthenticated. Socket callers with the per-boot token are now accepted (token compared in constant time).
    • Tools missing from script PATHs (bare names in units without a matching path, errors hidden): the ZFS boot gate never found zpool and always reported "no pools" (it never waited for pools); the ZED hook was named dplaneos-notify.sh, which ZED never runs (now all-dplaneos-notify.sh, so pool faults reach the daemon); the OTA wrapper had no awk, so the inactive-slot lookup failed and the degraded-pool check always passed; the USB persistence link never saw the stick (mountpoint); the SBD lease dataset was never created (zfs); the HA failover hook and the persist check lost their log lines (logger). All use absolute paths or a unit path, and the kernel-matched config.boot.zfs.package.
    • Quotas page: dataset quotas were posted to the dataset-create endpoint and user/group quotas sent the wrong fields; unparseable sizes became 0 (= no quota).
  • Dataset mountpoints: an invalid mountpoint (only /mnt/ and /media/ are allowed) was dropped silently after the create, which still reported success; it is now rejected with 400 before anything is created. The New Dataset dialog no longer sends one and inherits <pool mountpoint>/<name>.
  • First-run setup failed on fresh installs: the startup seed created admin with the default user role, so the setup wizard tried to insert a second admin and returned "Creation failed". The seed now sets the role, setup updates the seeded account, and the RBAC admin role is granted.
  • Audit log writes: both write paths bound a Go bool to the INTEGER success column, so no audit rows were stored; retention rotation compared the epoch timestamp with a date string and never deleted anything.
  • Health checker: no longer panics without a hardware profile, and no longer deadlocks on its own lock on the first check cycle.
  • NFSv4 ACL endpoints answer 501 with an explanation (OpenZFS on Linux has POSIX ACLs only; nfs4-acl-tools is not in nixpkgs).

Use Cases

  • Trial before install: Boot live, manage existing pools, run workloads, decide if you want to commit to disk installation
  • Rescue mode: Non-destructive access to existing ZFS pools if main NAS becomes unavailable
  • Evaluation: Full feature set accessible without modifying hardware
  • Temporary workload: Run D-PlaneOS for a specific task with automatic cleanup on shutdown

Documentation Changes

  • Added: docs/admin/LIVE-BOOT-QUICKSTART.md - Getting started guide with troubleshooting
  • Updated: README.md - Links to live boot option, feature maturity table updated
  • Updated: CHANGELOG.md - This entry

CI/CD Changes

  • New jobs: build-live-iso (x86_64 + aarch64 parallel), test-live-boot (VM integration test)
  • Updated release job: Downloads, signs, and publishes live ISOs alongside installer ISOs
  • Release artifacts: Both installer and live ISOs available in same GitHub release

No Breaking Changes

Live boot is an optional feature. Existing installer ISO unchanged. All installations created before v14.8.0 continue to work exactly as before. OTA updates disabled in live mode (but normal installed systems are unaffected).



🚀 Installation

curl -fsSL https://get.dplaneos.io | sudo bash -s -- --version 14.8.0