]> infiniteadaptability.org Git - channel/commitdiff
systems: add sample config and instructions for hosting guix on sporestack.com
authorAlexander Joss <[email protected]>
Fri, 30 Oct 2026 06:25:33 +0000 (23:25 -0700)
committerAlexander Joss <[email protected]>
Fri, 30 Oct 2026 06:25:33 +0000 (23:25 -0700)
infiniteadaptability/systems/sporestack/config.scm [new file with mode: 0644]
infiniteadaptability/systems/sporestack/full-flow-debian-to-guix.md [new file with mode: 0644]

diff --git a/infiniteadaptability/systems/sporestack/config.scm b/infiniteadaptability/systems/sporestack/config.scm
new file mode 100644 (file)
index 0000000..0e9475d
--- /dev/null
@@ -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 (file)
index 0000000..47a2c57
--- /dev/null
@@ -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=<uuid-from-blkid>  /  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. |