From 58399dcac01bfc17e64574091fdfd07eefc1901b Mon Sep 17 00:00:00 2001 From: Alexander Joss Date: Thu, 29 Oct 2026 23:25:33 -0700 Subject: [PATCH] systems: add sample config and instructions for hosting guix on sporestack.com --- .../systems/sporestack/config.scm | 50 ++++ .../sporestack/full-flow-debian-to-guix.md | 232 ++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 infiniteadaptability/systems/sporestack/config.scm create mode 100644 infiniteadaptability/systems/sporestack/full-flow-debian-to-guix.md diff --git a/infiniteadaptability/systems/sporestack/config.scm b/infiniteadaptability/systems/sporestack/config.scm new file mode 100644 index 0000000..0e9475d --- /dev/null +++ b/infiniteadaptability/systems/sporestack/config.scm @@ -0,0 +1,50 @@ +(use-modules (gnu) + (gnu packages ssh) + (gnu services networking) + (gnu services ssh) + (infiniteadaptability services cloud-init) + (guix gexp)) + +(define %ssh-key "[INSERT_SSH_KEY]") + +;; use: blkid -s UUID -o value /dev/vda1 +(define %blkid "[INSERT_UUID]") + +(operating-system + (host-name "guix-test") + (timezone "Etc/UTC") + (locale "en_US.utf8") + + ;; The supplied layout booted in legacy BIOS mode. + ;; Keep Debian's GRUB; installation below uses --no-bootloader. + (bootloader (bootloader-configuration + (bootloader grub-bootloader) + (targets '("/dev/vda")))) + + ;; Guix's root is the @guix Btrfs subvolume on /dev/vda1. + (file-systems (cons* (file-system + (device (uuid %blkid)) + (mount-point "/") + (type "btrfs") + (options "subvol=@guix")) %base-file-systems)) + + (services + (append (list (service cloud-init-service-type + ;; An example of extra configuration files. This specific + ;; file is required for properly running cloud-init on DigitalOcean + (cloud-init-configuration (extra-configuration-files ` + (("99-digitalocean.cfg" , + (plain-file + "99-digitalocean.cfg" + "datasource_list: [ ConfigDrive, DigitalOcean, NoCloud, None ]")))))) + + (service network-manager-service-type) + (service wpa-supplicant-service-type) + (service openssh-service-type + (openssh-configuration (openssh openssh-sans-x) + (permit-root-login 'prohibit-password) + (password-authentication? #f) + (authorized-keys `(("root" ,(plain-file + "auth-key" + %ssh-key))))))) + %base-services))) diff --git a/infiniteadaptability/systems/sporestack/full-flow-debian-to-guix.md b/infiniteadaptability/systems/sporestack/full-flow-debian-to-guix.md new file mode 100644 index 0000000..47a2c57 --- /dev/null +++ b/infiniteadaptability/systems/sporestack/full-flow-debian-to-guix.md @@ -0,0 +1,232 @@ +# Debian VPS → Guix System on a Btrfs subvolume (full flow) + +This replaces a Debian VPS with Guix System in place. It needs no custom image, no installer and +no recovery console of your own. The only part that needs offline access to the disk (the +ext4 → Btrfs conversion) is done by the provider's operator. Everything else runs over SSH. + +Tested end to end on a DigitalOcean droplet (Debian 13, 10 GB disk, GPT, legacy BIOS boot). + +Assumed disk layout (check with `lsblk -f`): + +| Partition | Type | Role | +|--------------|----------------------|-----------------------------------| +| `/dev/vda1` | Linux root | ext4 → Btrfs, holds `@guix` later | +| `/dev/vda14` | BIOS boot (~3 MB) | GRUB core image (BIOS on GPT) | +| `/dev/vda15` | EFI System (vfat) | unused, left alone | + +The flow: **operator converts root to Btrfs, updates `/etc/fstab` and reinstalls GRUB → you install Guix on Debian → create `@guix` +subvolume → `guix system init` into it → boot Guix → delete the old Debian files.** + +--- + +## Phase 1 — Operator, in recovery mode + +See `sporestack-btrfs-convert-request.md` for the version to send to support. The operator +makes **three required changes**: + +1. Convert the filesystem. +2. Update the root entry in `/etc/fstab`. +3. Reinstall GRUB. + +```sh +lsblk -f +e2fsck -f /dev/vda1 +btrfs-convert /dev/vda1 # keeps ext4 copy in ext2_saved (rollback) + +mount -o subvolid=5 /dev/vda1 /mnt +for d in dev proc sys run; do mount --rbind /$d /mnt/$d; mount --make-rslave /mnt/$d; done +chroot /mnt + +# fstab: the root line must point at the new Btrfs filesystem +blkid -s UUID -o value /dev/vda1 # filesystem UUID (not PARTUUID / UUID_SUB) +cp -a /etc/fstab /etc/fstab.bak +vi /etc/fstab +findmnt --verify + +grub-install --target=i386-pc --recheck --modules="part_gpt btrfs" /dev/vda +update-grub +exit +umount -R /mnt +reboot +``` + +The `/` line in `/etc/fstab` changes from +`PARTUUID=… / ext4 rw,discard,errors=remount-ro,x-systemd.growfs 0 1` to: + +```fstab +UUID= / btrfs defaults 0 0 +``` + +The changes are: type `btrfs`, no `errors=remount-ro` (ext4-only), and fsck pass `0`. +Leave `/boot/efi` alone. + +Why each change matters: +- **fstab:** after conversion, the root line still says `ext4` with the old identifier, which no + longer matches the filesystem. +- **GRUB:** without the reinstall, the VM stops at `grub rescue>` with `error: unknown filesystem`. +If anything goes wrong, rollback from recovery is `btrfs-convert -r /dev/vda1` (only while +`ext2_saved` still exists). + +--- + +## Phase 2 — You, on Debian (root is now Btrfs) + +### 2.1 Verify the operator's changes and make root writable + +```sh +findmnt -no SOURCE,FSTYPE,OPTIONS / # expect /dev/vda1 btrfs … (may be ro, see below) +grep ' / ' /etc/fstab # expect UUID=… / btrfs defaults 0 0 +blkid -s UUID -o value /dev/vda1 # write this down; config.scm needs it +``` + +**Known open issue:** after the conversion, root came up read-only on every boot of the Debian +system, and the cause is still unknown. Logging in still worked. Remount it read-write by device, +which works and is needed before any of the steps below: + +```sh +mount -o remount,rw /dev/vda1 / +findmnt -no OPTIONS / # now rw +``` + +Do this again after any reboot while still on Debian. Once you're running Guix (Phase 3), it +no longer applies. + +### 2.2 Install the Guix package manager on Debian + +```sh +apt update +apt install uidmap btrfs-progs git # uidmap provides newgidmap, needed by the installer +wget https://guix.gnu.org/guix-install.sh +chmod +x guix-install.sh +./guix-install.sh +exit # log in again so the Guix profile is loaded +``` + +### 2.3 Add swap (`guix pull` gets OOM-killed on a small VPS) + +```sh +btrfs filesystem mkswapfile --size 500M /swapfile +chmod 600 /swapfile +swapon /swapfile +free -h +guix pull +``` + +500 MB was enough. This swapfile is deleted with the Debian files in Phase 4. + +### 2.4 Get the cloud-init channel and the system config + +```sh +git clone https://git.infiniteadaptability.org/git/channel ~/channel +vi config.scm # see section 2.6 +``` + +### 2.5 Create the `@guix` subvolume and mount it + +```sh +mkdir -p /mnt/btrfs-top +mount -o subvolid=5 /dev/vda1 /mnt/btrfs-top # Btrfs top level +btrfs subvolume create /mnt/btrfs-top/@guix +btrfs subvolume list /mnt/btrfs-top # expect ext2_saved and @guix +umount /mnt/btrfs-top + +mkdir -p /mnt/guix +mount -o subvol=@guix /dev/vda1 /mnt/guix +``` + +Don't point Debian's fstab at `@guix`. Debian keeps running from the top level until the reboot. + +### 2.6 `config.scm` — things to check before installing + +Start from `config.scm` in this directory and change: + +- **`file-system` UUID**: the UUID from step 2.1, with `(options "subvol=@guix")`. +- **Bootloader**: `grub-bootloader` (BIOS) with `(targets '("/dev/vda"))`, the whole disk. +- **SSH key**: put your public key directly into `openssh-configuration`: + `(authorized-keys `(("root" ,(plain-file "auth-key" "ssh-ed25519 AAAA… you@host"))))`. + The `openssh-authorized-keys` build failed several times before the key was given this way + as one exact public-key line. If it fails, the build log is in + `/var/log/guix/drvs/…-openssh-authorized-keys.drv.gz`. +- **SSH policy**: `permit-root-login 'prohibit-password` and `password-authentication? #f`. + Key login is the only way in after reboot, so get the key right. +- **Networking**: `network-manager-service-type` (DHCP). +- **cloud-init**: the `99-digitalocean.cfg` datasource list is specific to DigitalOcean. + Change or remove it to suit the provider. +- **host-name**, **timezone**, **locale** as you like. + +### 2.7 Install Guix System into the subvolume + +```sh +df -h # check free space (~8 GB was enough) +guix system -L ~/channel/ init config.scm /mnt/guix +``` + +This builds the system into `/mnt/guix` and installs Guix's GRUB to `/dev/vda`, which replaces +Debian's. If it fails, fix `config.scm` and run it again. Each run reuses what is already built. + +--- + +## Phase 3 — Reboot into Guix System + +```sh +reboot +``` + +Then log in over SSH as root with your key and check: + +```sh +guix system describe +ip addr +ping -c3 gnu.org +df -h +``` + +--- + +## Phase 4 — Remove the old Debian data + +Guix runs from `@guix`, but the old Debian files are still at the Btrfs top level (subvolid 5), +along with the `ext2_saved` ext4 image (a single `image` file the size of the disk). Neither +shows up from Guix's `/`. + +```sh +mkdir -p /mnt/btrfs-top +mount -o subvolid=5 /dev/vda1 /mnt/btrfs-top +btrfs subvolume list /mnt/btrfs-top +ls -lha /mnt/btrfs-top # Debian dirs + @guix + ext2_saved + +# (optional) look at the old system first +mkdir -p /mnt/old-debian +mount -o ro,subvol=ext2_saved /dev/vda1 /mnt/old-debian && ls -lha /mnt/old-debian +umount /mnt/old-debian + +# point of no return: removes the ext4 rollback copy +btrfs subvolume delete /mnt/btrfs-top/ext2_saved + +# delete everything at the top level except @guix (includes the old /swapfile) +find /mnt/btrfs-top -mindepth 1 -maxdepth 1 ! -name '@guix' ! -name 'ext2_saved' \ + -exec rm -rf -- {} + + +ls -lha /mnt/btrfs-top # only @guix should remain +umount /mnt/btrfs-top +df -h # space reclaimed +reboot +``` + +Once it comes back up, you're done: Guix System running from `@guix`, with Debian gone. + +--- + +## Troubleshooting from the test run + +| Symptom | Cause / fix | +|---------------------------------------------------------|-------------| +| `grub rescue>` / `error: unknown filesystem` | GRUB wasn't reinstalled with Btrfs. Rerun Phase 1 `grub-install` + `update-grub`. | +| `grub-install: cannot backup … Read-only file system` | `/mnt` was mounted read-only. Run `mount -o remount,rw /mnt` before the chroot. | +| `grub-install: More than one install device?` | The disk was given twice / stray argument. Give `/dev/vda` exactly once. | +| Root always mounts read-only on Debian after conversion | **Unresolved; cause unknown.** Workaround: `mount -o remount,rw /dev/vda1 /` (step 2.1). | +| `mount: /: can't find PARTUUID=` | Plain `mount -o remount,rw /` looks up a bad fstab entry. Name the device: `mount -o remount,rw /dev/vda1 /`. | +| Guix installer complains about `newgidmap` | `apt install uidmap`. | +| `guix pull` / `guix system init` gets killed | Out of memory. Add the swapfile (step 2.3). | +| `openssh-authorized-keys.drv` build fails | Malformed key string in `config.scm`. Check the drv log and use one exact public-key line. | +| Disk still full after booting Guix | Debian + `ext2_saved` are still at subvolid 5. See Phase 4. | -- 2.55.0