Hi Guix Community, During a severe heatwave this past summer, my main system's old hard drive unfortunately failed. Fortunately, I was able to temporarily boot into Windows using a Ventoy-based "WindowsToGo" USB image I had created a while ago. However, my primary OS is GuixSD. While struggling to recover my environment, an idea struck me: "Can we declaratively create a 'GuixToGo' system that boots from a single USB drive while fully maintaining configurations and support upgrades, just like WindowsToGo?"
After a lot of trial and error and hacking into the early boot stages, I successfully achieved a working boot setup and built a custom module named (local system mapped-devices). Please note that my current setup and environment are not yet completely stable, so I might not be able to respond to individual questions or provide technical support later. Instead, I am sharing the implementation details, core ideas, and code here to archive this work and hopefully inspire others trying similar experiments. *[Module Commentary]*The custom module contains detailed usage guides and comments. Here is the core commentary from the module: ;;; Commentary: ;;; ;;; This module provides advanced mapped devices for customized root boot. ;;; ;;; It supports two distinct boot architectures: ;;; 1. Booting a system installed under a specific subdirectory rather than ;;; the default physical root (via `proxy-mapping'). ;;; 2. Booting a system from a raw disk image via pre-mounted storage and ;;; loopback setups (via `mount-mapping' and `loopback-bootstrap'). ;;; ;;; Both configurations bypass the standard layout to enable highly flexible ;;; and isolated runtime environments on a physical partition. ;;; *[Core Ideas & Implementation Highlights]*This project covers two main approaches with the following underlying mechanisms: *1. Loopback Root Boot:*During the early boot stage (prior to pivoting to the real root filesystem), a raw disk image (.raw) residing on a host partition is associated with a loop device via losetup. This /dev/loop0 device is then mounted as the root filesystem. Read performance is optimized using --direct-io=on and blockdev --setra 4096. Upon shutdown, the system flushes buffers and safely detaches the loop device to prevent filesystem corruption. *2. Subdirectory Proxy Boot:*This approach isolates and boots a Guix system installed under an arbitrary subdirectory (e.g., GuixSD) rather than directly under the physical partition root (/). During early boot, a temporary ramdisk (/dev/ram0) is formatted as ext2 and mounted as the temporary root /. Then, the essential directories (/gnu, /var, /etc, etc.) from the target subdirectory are bound to the ramdisk using Linux bind-mount flags. *[A Useful NTFS Workaround for Developers]*While adapting this to a Ventoy multi-boot environment (which typically uses NTFS), I discovered an interesting limitation: Guix's built-in find-partition-by-label fails on NTFS partitions because it does not fully parse the NTFS Master File Table (MFT) structure. To work around this, I implemented a custom discovery function that bypasses the filesystem layer entirely. It parses the GPT partition name (PARTNAME=) interface via sysfs. This reliably locates the NTFS partition even if the drive node letters change. This workaround might be useful for future upstream improvements in Guix. *[Important Warnings]*If you want to test this, please keep the following critical points in mind: • GRUB Safeguard: You must use the --no-bootloader option during system initialization (init) or reconfigurations (reconfigure) to prevent the system from overwriting the host's primary GRUB. In the standalone example files, I added a dummy bootloader safeguard that redirects the bootloader configuration to /dev/null to prevent accidents. • Manual Root Home Creation: Because these methods stitch the subdirectory or internal image paths manually through custom mount chains, the system init process does not automatically create the root user's home directory (.../root). You must create it manually after initialization. • Temporary Root Warning: In both proxy and loopback boot environments, the real physical storage partition is mounted under /storage (it is only / in a native boot). When initializing another variant system while booted into these environments, never work under the current temporary root /. You must perform all operations under the physical storage path (e.g., /storage/...). *[Testing & Attachment Guide]*To make it easier to test or reference, I am sharing the simplified standalone configurations (*.scm), GRUB examples (*.cfg), and pre-built sample images. *Note: The sample image files below are hosted on a temporary anonymous hosting service and will only remain available for exactly three days (72 hours) from the time this email is sent.* *1. Standard Loopback Boot Example*• Attached files: loopback_example_configuration.scm, loopback_example_grub.cfg • Sample image (GuixSD.raw.xz <https://litter.catbox.moe/4sq5310algx3yd3c.xz>): https://litter.catbox.moe/4sq5310algx3yd3c.xz • Usage: Decompress the image and place it in the root directory of a drive labeled Guix. Then add the GRUB configuration. (If your drive label is different, you can adjust it using e2label). *2. Ventoy Variant Loopback Boot Example*• Attached files: ventoy_configuration.scm, ventoy_grub.cfg • Sample image (GuixSD-RawImageOnVentoyPartition.raw.xz <https://litter.catbox.moe/vr6cf5agihkmmj4c.xz>): https://litter.catbox.moe/vr6cf5agihkmmj4c.xz • Usage: Decompress the image and place it on your Ventoy NTFS partition. Create a directory named ventoy on that partition and place ventoy_grub.cfg inside it. Boot into Ventoy, press F6, and select the custom menuentry to boot. *3. Subdirectory Proxy Boot Example*• Attached files: proxy_example_configuration.scm, proxy_example_grub.cfg • Sample image (GuixSD.tar.xz <https://litter.catbox.moe/velvtrt2216wilor.xz>): https://litter.catbox.moe/velvtrt2216wilor.xz • Usage: Extract the archive into your target subdirectory following the guide in the comments, and link it to your main GRUB configuration. (Remember to manually prepend the subdirectory name as a prefix to the kernel/initrd paths in GRUB). I have also attached the full module files that encapsulate all this abstraction logic: local_system_mapped-devices.scm, local_build_file-systems.scm, local_file-systems.scm, and local_lists.scm. I hope these notes and code snippets prove helpful to other GuixSD users and developers exploring flexible boot configurations. Best regards,
;;; grub.scm
(define-module (local bootloader grub)
#:use-module ((gnu bootloader) #:select (bootloader))
#:use-module ((gnu bootloader grub) #:select (grub-efi-bootloader))
#:use-module ((guix gexp) #:select (gexp))
#:export (grub-dummy-bootloader))
;;; Commentary:
;;;
;;; This module defines a dummy GRUB bootloader. It disables the installation
;;; process and GRUB configuration.
;;;
;;; Code:
(define grub-dummy-bootloader
(bootloader (inherit grub-efi-bootloader)
(configuration-file "/dev/null")
(installer #~(const #t))))
;;; Local Variables:
;;; mode: scheme
;;; coding: utf-8
;;; End:
;;; grub.scm ends here
;;; file-systems.scm
(define-module (local build file-systems)
#:use-module ((gnu build file-systems) #:select (disk-partitions
find-partition-by-label))
#:use-module ((guix build syscalls) #:select (MS_RDONLY
MS_NOSUID
MS_NODEV
MS_NOATIME))
#:use-module ((ice-9 rdelim) #:select (read-string))
#:export (default-mount-flags
default-mount-options
find-device-by-label))
;;; Commentary:
;;;
;;; Provides a utility layer for partition discovery and declarative mounting
;;; metadata extraction.
;;;
;;; Code:
;; WORKAROUND: Guix 1.5.0's missing Master File Table (MFT) for NTFS.
;; WARNING : This must ONLY be TARGETED for NTFS.
(define (workaround-find-partition-by-label label) ; e.g. label - "Guix".
"Inspect raw block uevents via sysfs to resolve the target partition node."
(let ((found (find (lambda (node) ; e.g. node - "sda1".
(and (< 2 (string-length node))
(string-prefix? "sd" node)
(string-every char-numeric? (string-drop node 3))
(let ((uevent (string-append
"/sys/class/block/"
(string-take node 3)
"/" node "/uevent")))
;; e.g., uevent -
;; "/sys/class/block/sda/sda1/uevent".
(and (file-exists? uevent)
(let ((line (find (lambda (str)
(string-prefix?
"PARTNAME=" str))
(string-split
(call-with-input-file
uevent read-string)
#\newline))))
;; e.g., line - "PARTNAME=Guix".
(and line (string=? (substring line 9)
label)))))))
(disk-partitions))))
(and found (string-append "/dev/" found))))
(define* (find-device-by-label label #:optional (fstype "ext4"))
"Resolve and verify the storage device node matching LABEL."
(let ((find-device (if (string=? fstype "ntfs")
workaround-find-partition-by-label
find-partition-by-label)))
(let loop ((device (find-device label)) (reducer 10))
(cond (device device)
((positive? reducer)
(sleep 1)
(loop (find-device label) (- reducer 1)))
(else #f)))))
(define (default-mount-flags fstype)
"Return the recommended numeric mount flags bitmask for FSTYPE."
(if (string=? fstype "ufs")
(logior MS_RDONLY MS_NOSUID MS_NODEV)
(logior MS_NOSUID MS_NODEV MS_NOATIME)))
(define (default-mount-options fstype)
"Return the recommended options string for FSTYPE, or #f."
(cond ((string=? fstype "ntfs") "nohidden")
((string=? fstype "ufs") "ufstype=ufs2")
(else #f)))
;;; Local Variables:
;;; mode: scheme
;;; coding: utf-8
;;; End:
;;; file-systems.scm ends here
;;; file-systems.scm
(define-module (local file-systems)
#:use-module ((local lists) #:select (append-merge))
#:export (absolute-path
normalize-components
normalize-path
split-path
vicinity-merge))
;;; Code:
(define (absolute-path path)
"Force PATH into a standardized absolute path format."
(string-append "/" (string-trim-both path #\/)))
(define (split-path path)
"Split PATH into a list of its components, omitting empty segments."
(filter (lambda (s) (not (string-null? s)))
(string-split path #\/)))
(define* (normalize-components components #:optional (absolute? #f))
"Clean up '.' and '..' from a list of path COMPONENTS as much as possible."
;; The ordering of REST and ACC follows Scheme conventions,
;; opposite to Haskell's.
(let loop ((rest components) (acc '()))
(cond ((null? rest) ; Return result.
(reverse acc))
((string=? (car rest) "..") ; Clean up "..".
(cond (absolute? (loop (cdr rest) (if (null? acc) '() (cdr acc))))
((and (not (null? acc))
(not (string=? (car acc) "..")))
(loop (cdr rest) (cdr acc)))
(else (loop (cdr rest) (cons ".." acc)))))
((string=? (car rest) ".") ; Clean up ".".
(loop (cdr rest) acc))
(else ; Continue loop.
(loop (cdr rest) (cons (car rest) acc))))))
(define (normalize-path path)
"Clean up '.' and '..' from PATH as much as possible."
(let* ((absolute? (string-prefix? "/" path))
(joined (string-join
(normalize-components (split-path path) absolute?)
"/")))
(if absolute? (string-append "/" joined) joined)))
(define (vicinity-merge directory file)
"Combine DIRECTORY and FILE, merging overlapping path components."
(let ((absolute? (string-prefix? "/" directory))
(normalized-directory (normalize-path directory))
(normalized-file (normalize-path file)))
(let* ((merged-components (append-merge
(split-path normalized-directory)
(split-path normalized-file)))
(joined (string-join merged-components "/")))
(if absolute? (string-append "/" joined) joined))))
;;; Local Variables:
;;; mode: scheme
;;; coding: utf-8
;;; End:
;;; file-systems.scm ends here
;;; list.scm
(define-module (local lists)
#:use-module ((srfi srfi-1) #:select (drop drop-right))
#:export (append-merge
get-overlap
list-prefix?))
;;; Code:
(define (list-prefix? prefix lst)
"Return #t if PREFIX is a prefix of LST, otherwise #f."
(cond ((null? prefix) #t)
((null? lst) #f)
((equal? (car prefix) (car lst))
(list-prefix? (cdr prefix) (cdr lst)))
(else #f)))
(define (get-overlap left right)
"Return the longest tail-to-head overlap of LEFT and RIGHT."
(let ((head (and (not (null? right)) (car right))))
(let loop ((tail (member head left)))
(cond ((or (not tail) (null? tail)) '())
((list-prefix? tail right) tail)
(else (loop (member head (cdr tail))))))))
(define (append-merge left right)
"Append LEFT and RIGHT, merging their common overlap."
(let* ((overlap (get-overlap left right))
(len (length overlap)))
(append (drop-right left len) overlap (drop right len))))
;;; Local Variables:
;;; mode: scheme
;;; coding: utf-8
;;; End:
;;; list.scm ends here
;;; mapped-devices.scm
(define-module (local system mapped-devices)
#:use-module ((gnu packages linux) #:select (e2fsprogs
util-linux))
#:use-module ((gnu services) #:select (service-extension
service-type))
#:use-module ((gnu services shepherd)
#:select (shepherd-service shepherd-root-service-type))
#:use-module ((gnu system file-systems)
#:select (%base-file-systems file-system file-system-label))
#:use-module ((gnu system mapped-devices)
#:select (mapped-device
mapped-device-arguments
mapped-device-kind
mapped-device-source
mapped-device-targets
mapped-device-type))
#:use-module ((guix gexp)
#:select (gexp with-imported-modules))
#:use-module ((ice-9 match) #:select (match))
#:use-module ((local build file-systems)
#:select (default-mount-flags
default-mount-options
find-device-by-label))
#:use-module ((local file-systems) #:select (absolute-path
vicinity-merge))
#:use-module ((srfi srfi-1) #:select (find))
#:export (loopback-bootstrap
loopback-runtime
mount-mapping
mount-mapping-runtime
proxy-device
proxy-file-systems
proxy-mapping))
;;; Commentary:
;;;
;;; This module provides advanced mapped devices for customized root boot.
;;;
;;; It supports two distinct boot architectures:
;;; 1. Booting a system installed under a specific subdirectory rather than
;;; the default physical root (via `proxy-mapping').
;;; 2. Booting a system from a raw disk image via pre-mounted storage and
;;; loopback setups (via `mount-mapping' and `loopback-bootstrap').
;;;
;;; Both configurations bypass the standard layout to enable highly flexible
;;; and isolated runtime environments on a physical partition.
;;;
;;; Code:
;;;
;;; proxy-mapping
;;;
;; Usage Example (Declarative Proxy Root Setup):
;;
;; 1. Add the custom mapped device and file systems to your `configuration.scm'
;; file:
;;
;; (mapped-devices (list proxy-device))
;; (file-systems (cons (file-system (mount-point "/")
;; (device "/dev/ram0")
;; (type "ext2")
;; (flags '(no-atime))
;; (check? #f)
;; (dependencies mapped-devices))
;; (proxy-file-systems "Guix" "/storage" "GuixSD")))
;;
;; 2. Add the custom menuentry to your `grub.cfg' file to boot this path:
;;
;; menuentry "GuixSD (Proxy Root)" {
;; search --no-floppy --set --label Guix
;; linux /GuixSD/gnu/store/...-linux-libre-7.2.6/bzImage \
;; root=/dev/ram0 \
;; gnu.system=/var/guix/profiles/system \
;; gnu.load=/var/guix/profiles/system/boot \
;; modprobe.blacklist=usbmouse,usbkbd \
;; quiet
;; initrd /GuixSD/gnu/store/...-raw-initrd/initrd.cpio.gz
;; }
;;
;; Implementation Notes:
;;
;; * We recommend using the `--no-bootloader` option during system
;; initialization (`init') and reconfigurations (`reconfigure') to prevent
;; the system from overriding or breaking the primary `grub.cfg' file.
;; * The `linux' and `initrd' parameters must point to the actual files
;; inside the physical storage partition.
;; * The volume label "Guix", directory "/storage" and "GuixSD" are
;; placeholders. Customize them to match your own environment.
;;
;; How to Initialize the Guix System in Subdirectory (e.g., GuixSD):
;;
;; 1. Create the target installation directory.
;; mkdir GuixSD
;; 2. Initialize the Guix System into the created path.
;; guix system --no-bootloader init configuration.scm GuixSD
;; 3. Create the home directory for the root user.
;; mkdir GuixSD/root
;; 4. Check the generated `grub.cfg' to find `linux' and `initrd' paths.
;; cat GuixSD/gnu/store/*-grub.cfg
;;
;; * WARNING ON PATHS: The paths inside the generated `grub.cfg' will appear
;; as `/gnu/store/...'. You MUST manually prefix them with `/GuixSD'
;; (e.g., `/GuixSD/gnu/store/...') when copying them into the primary
;; `grub.cfg' file.
;; * WARNING ON SUBSEQUENT INITS: If you are currently booted into a system
;; using this proxy setup, the *ROOT* "/" is a temporary ramdisk. To
;; create or initialize another system (e.g., `GuixSD-1'), you MUST perform
;; the operations under the physical storage path "/storage" (e.g., `mkdir
;; /storage/GuixSD-1'), NOT under the temporary *ROOT* "/".
(define proxy-mapping
(mapped-device-kind
(open (lambda _
#~(zero? (system* (string-append #$e2fsprogs "/sbin/mkfs.ext2")
"/dev/ram0"))))))
(define proxy-device
(mapped-device (type proxy-mapping) (source "dummy") (target "dummy")))
(define* (proxy-file-systems label mount-point root #:optional (fstype "ext4"))
"Return base filesystems combined with custom bind-mounts."
(let ((root (string-append
"/root" (vicinity-merge (absolute-path mount-point) root))))
(cons* (file-system (mount-point mount-point)
(device (file-system-label label))
(type fstype)
(needed-for-boot? #t)
(create-mount-point? #t))
(file-system (mount-point "/gnu")
(device (string-append root "/gnu"))
(type "none")
(flags '(bind-mount no-atime))
(needed-for-boot? #t)
(check? #f)
(create-mount-point? #t))
(file-system (mount-point "/var")
(device (string-append root "/var"))
(type "none")
(flags '(bind-mount no-atime))
(needed-for-boot? #t)
(check? #f)
(create-mount-point? #t))
(file-system (mount-point "/etc")
(device (string-append root "/etc"))
(type "none")
(flags '(bind-mount no-atime))
(needed-for-boot? #t)
(check? #f)
(create-mount-point? #t))
(file-system (mount-point "/tmp")
(device (string-append root "/tmp"))
(type "none")
(flags '(bind-mount no-atime))
(needed-for-boot? #t)
(check? #f)
(create-mount-point? #t))
(file-system (mount-point "/root")
(device (string-append root "/root"))
(type "none")
(flags '(bind-mount no-atime))
(needed-for-boot? #t)
(check? #f)
(create-mount-point? #t))
(file-system (mount-point "/home")
(device (string-append root "/home"))
(type "none")
(flags '(bind-mount no-atime))
(needed-for-boot? #t)
(check? #f)
(create-mount-point? #t))
%base-file-systems)))
;;;
;;; mount-mapping
;;;
;; Mounts a physical storage partition early in boot (initrd) by its label and
;; executes lifecycle hooks to initialize the primary target runtime
;; environment (typically the *ROOT* "/" filesystem). This abstract pipeline
;; isolates physical device attachment from the concrete system runtime setup.
;;
;; Source: "LABEL"
;; - The volume label of the backing hardware partition
;; (e.g., "Guix").
;; Targets: '("target_mount_point")
;; - Target mount point path (e.g., "/storage").
;;
;; Optional Key Arguments:
;;
;; #:bootstrap A procedure executed immediately after successful partition
;; mount during early boot. Designed to bootstrap the initial
;; runtime dependencies before the main system root takes over.
;; Defaults to `(lambda _ #t)'.
;; #:runtime A dynamic Shepherd service object injected into the system
;; graph. The service's `start' clause handles actions after
;; the host OS boots (runtime initialization), while its `stop'
;; clause orchestrates the orderly teardown during system
;; shutdown.
;; #:fstype Filesystem type of the backing physical partition.
;; Defaults to "ext4".
(define* (open-mount-mapping
label targets ; e.g., label - "Guix".
#:key (fstype "ext4") (bootstrap (lambda _ #t)) #:allow-other-keys)
"Mount a device by its LABEL to TARGETS and execute the BOOTSTRAP hook."
(match targets
((mount-point) ; e.g., mount-point - "/storage".
(with-imported-modules
'((local lists) (local file-systems) (local build file-systems))
#~(begin
(use-modules ((guix build syscalls)
#:select (MS_RDONLY mount umount))
((local build file-systems)
#:select (default-mount-flags
default-mount-options
find-device-by-label))
((local file-systems) ; This uses (local lists).
#:select (absolute-path vicinity-merge)))
(let ((device (find-device-by-label #$label #$fstype))
(mount-point (absolute-path #$mount-point))
(flags (default-mount-flags #$fstype))
(options (default-mount-options #$fstype)))
(cond (device
(mkdir mount-point)
(mount device mount-point #$fstype flags options)
(#$bootstrap mount-point #$fstype flags))
(else #f))))))))
(define mount-mapping
(mapped-device-kind (open open-mount-mapping)))
(define mount-mapping-runtime
(service-type
(name 'mount-mapping-runtime)
(description
(string-append "Registers and activates custom runtime closures "
"declared in arguments of mount-mapping mapped-device."))
(extensions
(list (service-extension
shepherd-root-service-type
(lambda (mapped-devices-list)
(let ((mapping (find (lambda (device)
(eq? (mapped-device-type device)
mount-mapping))
mapped-devices-list)))
(if mapping
(let ((source (mapped-device-source mapping))
(targets (mapped-device-targets mapping))
(arguments (mapped-device-arguments mapping)))
(let ((runtime-argument (member #:runtime arguments))
(fstype-argument (member #:fstype arguments)))
(if runtime-argument
(let ((runtime (cadr runtime-argument))
(label source)
(mount-point (absolute-path (car targets)))
(fstype (or (and fstype-argument
(cadr fstype-argument))
"ext4")))
;; e.g., ((loopback-runtime)
;; "/dev/sda1" "/storage" "ext4"
;; (logior MS_NOSUID MS_NODEV MS_NOATIME)
;; #f).
(runtime (find-device-by-label label fstype)
mount-point fstype
(default-mount-flags fstype)
(default-mount-options fstype)))
(error "#:runtime argument is missing"))))
(error "mount-mapping is missing")))))))))
;; Usage Example (Declarative Root Loopback Setup):
;;
;; 1. Add the custom mapped device, file systems, and services to your
;; `configuration.scm' file:
;;
;; (mapped-devices (list (mapped-device
;; (type mount-mapping)
;; (source "Guix")
;; (target "/storage")
;; (arguments (list #:bootstrap (loopback-bootstrap
;; "GuixSD.raw")
;; #:runtime (loopback-runtime))))))
;; (file-systems (cons (file-system (mount-point "/")
;; (device "/dev/loop0")
;; (type "ext4")
;; (flags '(no-atime))
;; (options "data=writeback")
;; (dependencies mapped-devices))
;; %base-file-systems))
;; (services (cons* (service mount-mapping-runtime mapped-devices)
;; (service dhcpcd-service-type) ; Add your system services...
;; %base-services))
;;
;; 2. Add the custom menuentry to your `grub.cfg' file to boot this image:
;;
;; menuentry "GuixSD (Root Loopback)" {
;; search --no-floppy --set --label Guix
;; loopback loop /GuixSD.raw
;; linux (loop)/var/guix/profiles/system/kernel/bzImage \
;; root=/dev/loop0 \
;; gnu.system=/var/guix/profiles/system \
;; gnu.load=/var/guix/profiles/system/boot \
;; modprobe.blacklist=usbmouse \
;; quiet
;; initrd (loop)/var/guix/profiles/system/initrd
;; }
;;
;; Implementation Notes:
;;
;; * We recommend using the `--no-bootloader` option during system
;; initialization (`init') and reconfigurations (`reconfigure') to prevent
;; the system from overriding or breaking the primary `grub.cfg' file.
;; * The `linux' and `initrd' parameters must point to the actual files
;; inside the loopback device.
;; * The volume label "Guix", directory "/storage" and image "GuixSD.raw" are
;; placeholders. Customize them to match your own environment.
;;
;; How to Create the Guix System Image File (e.g., GuixSD.raw):
;;
;; 1. Pre-allocate a contiguous physical block area (e.g., 64 GiB).
;; fallocate -l 64G GuixSD.raw
;; 2. Create the target filesystem inside the loopback image file.
;; Use high-performance options (not recommended for standard root
;; filesystems).
;; mkfs.ext4 -m 1 -O extents -F GuixSD.raw
;; 3. Mount the loopback image file to /mnt directory.
;; mount GuixSD.raw /mnt
;; 4. Initialize the Guix System into the mounted path.
;; guix system --no-bootloader init configuration.scm /mnt
;; 5. Unmount the loopback image file to finalize the installation.
;; umount /mnt
(define (loopback-bootstrap image) ; e.g., image - "GuixSD.raw".
"Return a G-expression that detaches and sets up a loopback device."
#~(lambda (mount-point fstype flags) ; e.g., mount-point - "/storage".
(let ((image (vicinity-merge mount-point #$image)))
(cond ((file-exists? image) ; e.g., image - "/storage/GuixSD.raw".
(cond ((and
(zero? (system*
;; Fallback to `losetup' binary because
;; `%ioctl' function fails in initrd Guile.
(string-append #$util-linux "/sbin/losetup")
(if (logtest flags MS_RDONLY)
"--read-only" "--direct-io=on")
"/dev/loop0" image))
(zero? (system*
;; Fallback to `blockdev' binary because
;; `%ioctl' function fails in initrd Guile.
(string-append #$util-linux "/sbin/blockdev")
"--setra" "4096"
"/dev/loop0"))
(zero? (system*
;; Fallback to `unmount' binary because `umount'
;; function rejects flag in initrd Guile.
(string-append #$util-linux "/bin/umount")
"--lazy" mount-point))) #t)
(else (umount mount-point) #f)))
(else (umount mount-point) #f)))))
(define (loopback-runtime)
"Return a Shepherd service to mount the loopback device."
(lambda (device mount-point fstype flags options)
(list (shepherd-service
(provision '(loopback-runtime))
(requirement '(root-file-system))
(start
#~(lambda _
(use-modules ((guix build syscalls) #:select (mount)))
(unless (file-exists? #$mount-point) (mkdir #$mount-point))
(mount #$device #$mount-point #$fstype #$flags #$options)
#t))
(stop
#~(lambda _
(use-modules ((guix build syscalls) #:select (umount)))
;; Use `blockdev' binary for pipeline consistency.
(system* (string-append #$util-linux "/sbin/blockdev")
"--flushbufs" "--setro" "/dev/loop0")
;; Use `losetup' binary for pipeline consistency.
(system* (string-append #$util-linux "/sbin/losetup")
"--detach" "/dev/loop0")
(umount #$mount-point)
#t))))))
;;; Local Variables:
;;; mode: scheme
;;; coding: utf-8
;;; End:
;;; mapped-devices.scm ends here
;;; configuration.scm
;; Indicate which modules to import to access the variables
;; used in this configuration.
(use-modules (gnu) (gnu build file-systems) (guix build syscalls) (ice-9 match))
(use-package-modules linux)
(use-service-modules networking shepherd)
(define grub-dummy-bootloader
(bootloader (inherit grub-efi-bootloader)
(configuration-file "/dev/null")
(installer #~(const #t))))
(define (open-loop0 label targets)
(match targets
((mount-point)
#~(let ((image (string-append #$mount-point "/" "GuixSD.raw")))
(use-modules (gnu build file-systems) (guix build syscalls))
(let loop ((device (find-partition-by-label #$label)) (reducer 10))
(cond (device (mkdir #$mount-point)
(mount device #$mount-point "ext4"
(logior MS_NOSUID MS_NODEV MS_NOATIME))
(and
(zero? (system*
(string-append #$util-linux "/sbin/losetup")
"--direct-io=on" "/dev/loop0" image))
(zero? (system*
(string-append #$util-linux "/sbin/blockdev")
"--setra" "4096" "/dev/loop0"))
(zero? (system*
(string-append #$util-linux "/bin/umount")
"--lazy" #$mount-point))))
((positive? reducer) (sleep 1)
(loop (find-partition-by-label #$label) (- reducer 1)))
(else #f)))))))
(define loop0-mapping
(mapped-device-kind (open open-loop0)))
(operating-system
(locale "en_US.utf8")
(timezone "Asia/Seoul")
(host-name "guix")
;; Safeguard against GRUB updates when missing `--no-bootloader'.
(bootloader (bootloader-configuration
(bootloader grub-dummy-bootloader)))
;; Specify a mapped device for the root partition.
(mapped-devices (list (mapped-device (type loop0-mapping)
(source "Guix")
(target "/storage"))))
;; The list of file systems that get "mounted".
(file-systems (cons (file-system (mount-point "/")
(device "/dev/loop0")
(type "ext4")
(flags '(no-atime))
(options "data=writeback")
(dependencies mapped-devices))
%base-file-systems))
;; Below is the list of system services. To search for available
;; services, run 'guix system search KEYWORD' in a terminal.
(services (cons* (simple-service
'loop0-mapping-runtime
shepherd-root-service-type
(list (let ((device (find-partition-by-label "Guix"))
(mount-point "/storage")
(fstype "ext4")
(flags (logior MS_NOSUID MS_NODEV MS_NOATIME)))
(shepherd-service
(provision '(loop0-runtime))
(requirement '(root-file-system))
(start
#~(lambda _
(use-modules (guix build syscalls))
(unless (file-exists? #$mount-point)
(mkdir #$mount-point))
(mount #$device #$mount-point #$fstype
#$flags)
#t))
(stop
#~(lambda _
(use-modules (guix build syscalls))
(system*
(string-append #$util-linux "/sbin/blockdev")
"--flushbufs" "--setro" "/dev/loop0")
(system*
(string-append #$util-linux "/sbin/losetup")
"--detach" "/dev/loop0")
(umount #$mount-point)
#t))))))
(service dhcpcd-service-type)
%base-services)))
;;; Local Variables:
;;; mode: scheme
;;; coding: utf-8
;;; End:
;;; configuration.scm ends here
;;; configuration.scm
;; Indicate which modules to import to access the variables
;; used in this configuration.
(use-modules (gnu))
(use-package-modules linux)
(use-service-modules networking)
(define grub-dummy-bootloader
(bootloader (inherit grub-efi-bootloader)
(configuration-file "/dev/null")
(installer #~(const #t))))
(define proxy-mapping
(mapped-device-kind
(open (lambda _
#~(zero? (system* (string-append #$e2fsprogs "/sbin/mkfs.ext2")
"/dev/ram0"))))))
(define proxy-device
(mapped-device (type proxy-mapping) (source "dummy") (target "dummy")))
(define* (proxy-file-systems label mount-point root #:optional (fstype "ext4"))
(let ((root (string-append "/root" mount-point "/" root)))
(cons* (file-system (mount-point mount-point)
(device (file-system-label label))
(type fstype)
(needed-for-boot? #t)
(create-mount-point? #t))
(file-system (mount-point "/gnu")
(device (string-append root "/gnu"))
(type "none")
(flags '(bind-mount no-atime))
(needed-for-boot? #t)
(check? #f)
(create-mount-point? #t))
(file-system (mount-point "/var")
(device (string-append root "/var"))
(type "none")
(flags '(bind-mount no-atime))
(needed-for-boot? #t)
(check? #f)
(create-mount-point? #t))
(file-system (mount-point "/etc")
(device (string-append root "/etc"))
(type "none")
(flags '(bind-mount no-atime))
(needed-for-boot? #t)
(check? #f)
(create-mount-point? #t))
(file-system (mount-point "/tmp")
(device (string-append root "/tmp"))
(type "none")
(flags '(bind-mount no-atime))
(needed-for-boot? #t)
(check? #f)
(create-mount-point? #t))
(file-system (mount-point "/root")
(device (string-append root "/root"))
(type "none")
(flags '(bind-mount no-atime))
(needed-for-boot? #t)
(check? #f)
(create-mount-point? #t))
(file-system (mount-point "/home")
(device (string-append root "/home"))
(type "none")
(flags '(bind-mount no-atime))
(needed-for-boot? #t)
(check? #f)
(create-mount-point? #t))
%base-file-systems)))
(operating-system
(locale "en_US.utf8")
(timezone "Asia/Seoul")
(host-name "guix")
;; Safeguard against GRUB updates when missing `--no-bootloader'.
(bootloader (bootloader-configuration
(bootloader grub-dummy-bootloader)))
;; Specify a mapped device for the root partition.
(mapped-devices (list proxy-device))
;; The list of file systems that get "mounted".
(file-systems (cons (file-system (mount-point "/")
(device "/dev/ram0")
(type "ext2")
(flags '(no-atime))
(check? #f)
(dependencies mapped-devices))
(proxy-file-systems "Guix" "/storage" "GuixSD")))
;; Below is the list of system services. To search for available
;; services, run 'guix system search KEYWORD' in a terminal.
(services (cons (service dhcpcd-service-type)
%base-services)))
;;; Local Variables:
;;; mode: scheme
;;; coding: utf-8
;;; End:
;;; configuration.scm ends here
;;; configuration.scm
;; Indicate which modules to import to access the variables
;; used in this configuration.
(use-modules (gnu)
(gnu build file-systems)
(guix build syscalls)
(ice-9 match)
(ice-9 rdelim)
(srfi srfi-1))
(use-package-modules linux)
(use-service-modules networking shepherd)
(define grub-dummy-bootloader
(bootloader (inherit grub-efi-bootloader)
(configuration-file "/dev/null")
(installer #~(const #t))))
(define (open-loop0 label targets)
(match targets
((mount-point)
#~(let ((image (string-append #$mount-point "/"
"GuixSD-RawImageOnVentoyPartition.raw")))
(use-modules (gnu build file-systems)
(guix build syscalls)
(ice-9 rdelim)
(srfi srfi-1))
(let loop ((device
(let ((found
(find
(lambda (node)
(and (< 2 (string-length node))
(string-prefix? "sd" node)
(string-every char-numeric?
(string-drop node 3))
(let ((uevent (string-append
"/sys/class/block/"
(string-take node 3)
"/" node "/uevent")))
(and (file-exists? uevent)
(let ((line
(find
(lambda (str)
(string-prefix?
"PARTNAME=" str))
(string-split
(call-with-input-file
uevent read-string)
#\newline))))
(and line (string=?
(substring line 9)
#$label)))))))
(disk-partitions))))
(and found (string-append "/dev/" found)))
)
(reducer 10))
(cond (device (mkdir #$mount-point)
(mount device #$mount-point "ntfs"
(logior MS_NOSUID MS_NODEV MS_NOATIME)
"nohidden")
(and
(zero? (system*
(string-append #$util-linux "/sbin/losetup")
"--direct-io=on" "/dev/loop0" image))
(zero? (system*
(string-append #$util-linux "/sbin/blockdev")
"--setra" "4096" "/dev/loop0"))
(zero? (system*
(string-append #$util-linux "/bin/umount")
"--lazy" #$mount-point))))
((positive? reducer) (sleep 1)
(loop (find-partition-by-label #$label) (- reducer 1)))
(else #f)))))))
(define loop0-mapping
(mapped-device-kind (open open-loop0)))
(operating-system
(locale "en_US.utf8")
(timezone "Asia/Seoul")
(host-name "guix")
;; Safeguard against GRUB updates when missing `--no-bootloader'.
(bootloader (bootloader-configuration
(bootloader grub-dummy-bootloader)))
;; Specify a mapped device for the root partition.
(initrd-modules (cons* "nls_utf8" "ntfs" (base-initrd-modules linux-libre)))
(mapped-devices (list (mapped-device (type loop0-mapping)
(source "Ventoy")
(target "/Ventoy"))))
;; The list of file systems that get "mounted".
(file-systems (cons (file-system (mount-point "/")
(device "/dev/loop0")
(type "ext4")
(flags '(no-atime))
(options "data=writeback")
(dependencies mapped-devices))
%base-file-systems))
;; Below is the list of system services. To search for available
;; services, run 'guix system search KEYWORD' in a terminal.
(services (cons* (simple-service
'loop0-mapping-runtime
shepherd-root-service-type
(list
(let ((device
(let ((found
(find
(lambda (node)
(and (< 2 (string-length node))
(string-prefix? "sd" node)
(string-every char-numeric?
(string-drop node 3))
(let ((uevent (string-append
"/sys/class/block/"
(string-take node 3)
"/" node "/uevent")))
(and (file-exists? uevent)
(let ((line
(find
(lambda (str)
(string-prefix?
"PARTNAME=" str))
(string-split
(call-with-input-file
uevent
read-string)
#\newline))))
(and line (string=?
(substring
line 9)
"Ventoy")))))))
(disk-partitions))))
(and found (string-append "/dev/" found))))
(mount-point "/Ventoy")
(fstype "ntfs")
(flags (logior MS_NOSUID MS_NODEV MS_NOATIME))
(options "nohidden"))
(shepherd-service
(provision '(loop0-runtime))
(requirement '(root-file-system))
(start
#~(lambda _
(use-modules (guix build syscalls))
(unless (file-exists? #$mount-point)
(mkdir #$mount-point))
(mount #$device #$mount-point #$fstype
#$flags #$options)
#t))
(stop
#~(lambda _
(use-modules (guix build syscalls))
(system*
(string-append #$util-linux "/sbin/blockdev")
"--flushbufs" "--setro" "/dev/loop0")
(system*
(string-append #$util-linux "/sbin/losetup")
"--detach" "/dev/loop0")
(umount #$mount-point)
#t))))))
(service dhcpcd-service-type)
%base-services)))
;;; Local Variables:
;;; mode: scheme
;;; coding: utf-8
;;; End:
;;; configuration.scm ends here
loopback_example_grub.cfg
Description: Binary data
proxy_example_grub.cfg
Description: Binary data
ventoy_grub.cfg
Description: Binary data
