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

Attachment: loopback_example_grub.cfg
Description: Binary data

Attachment: proxy_example_grub.cfg
Description: Binary data

Attachment: ventoy_grub.cfg
Description: Binary data

Reply via email to