Subject: [bug#77638] [PATCH 4/8] guix home: ‘container’ provides a read-only root file system.

From: Ludovic Courtès <[email-masked]>
Date: Tue, 8 Apr 2025 14:24:44 +0200

* guix/scripts/home.scm (spawn-home-container): Move creation of
accounts, /etc/hosts, /tmp, and HOME-DIRECTORY from the first argument
of ‘eval/container’ to #:populate-file-system.  Remove #:writable-root?.
* tests/guix-home.sh: Test that the root file system is read-only.

Change-Id: Icda54706321d51b95b563c86c3fb2238cc65ee20
---
 guix/scripts/home.scm | 79 +++++++++++++++++++++----------------------
 tests/guix-home.sh    |  3 +-
 2 files changed, 41 insertions(+), 41 deletions(-)

diff --git a/guix/scripts/home.scm b/guix/scripts/home.scm
index 7ce6217324..6fcb0ca382 100644
--- a/guix/scripts/home.scm
+++ b/guix/scripts/home.scm
@@ -34,6 +34,10 @@ (define-module (guix scripts home)
                                              home-shepherd-configuration-services
                                              shepherd-service-requirement)
   #:autoload   (guix modules) (source-module-closure)
+  #:autoload   (gnu build accounts) (password-entry
+                                     group-entry
+                                     write-passwd
+                                     write-group)
   #:autoload   (gnu build linux-container) (call-with-container %namespaces)
   #:autoload   (gnu system linux-container) (eval/container)
   #:autoload   (gnu system file-systems) (file-system
@@ -283,14 +287,13 @@ (define* (spawn-home-container home
    (with-extensions (list guile-gcrypt)
      (with-imported-modules `(((guix config) => ,(make-config.scm))
                               ,@(source-module-closure
-                                 '((gnu build accounts)
-                                   (guix profiles)
+                                 '((guix profiles)
                                    (guix build utils)
                                    (guix build syscalls))
                                  #:select? not-config?))
        #~(begin
            (use-modules (guix build utils)
-                        (gnu build accounts)
+                        ((guix profiles) #:select (load-profile))
                         ((guix build syscalls)
                          #:select (set-network-interface-up)))
 
@@ -300,46 +303,10 @@ (define* (spawn-home-container home
            (define term
              #$(getenv "TERM"))
 
-           (define passwd
-             (password-entry
-              (name #$user-name)
-              (real-name #$user-real-name)
-              (uid #$uid) (gid #$gid) (shell shell)
-              (directory #$home-directory)))
-
-           (define groups
-             (list (group-entry (name "users") (gid #$gid))
-                   (group-entry (gid 65534)       ;the overflow GID
-                                (name "overflow"))))
-
-           ;; (guix profiles) loads (guix utils), which calls 'getpw' from the
-           ;; top level.  Thus, arrange so that it's loaded after /etc/passwd
-           ;; has been created.
-           (module-autoload! (current-module)
-                             '(guix profiles) '(load-profile))
-
-           ;; Create /etc/passwd for applications that need it, such as mcron.
-           (mkdir-p "/etc")
-           (write-passwd (list passwd))
-           (write-group groups)
-
-           (unless #$network?
-             ;; When isolated from the network, provide a minimal /etc/hosts
-             ;; to resolve "localhost".
-             (call-with-output-file "/etc/hosts"
-               (lambda (port)
-                 (display "127.0.0.1 localhost\n" port)
-                 (chmod port #o444))))
-
-           ;; Create /tmp; bits of code expect it, such as
-           ;; 'least-authority-wrapper'.
-           (mkdir-p "/tmp")
-
            ;; Set PATH for things that the activation script might expect, such
            ;; as "env".
            (load-profile #$system-profile)
 
-           (mkdir-p #$home-directory)
            (setenv "HOME" #$home-directory)
            (setenv "GUIX_NEW_HOME" #$home)
            (primitive-load (string-append #$home "/activate"))
@@ -359,6 +326,39 @@ (define* (spawn-home-container home
                        ((_ ...)
                         #~("-c" #$(string-join command))))))))
 
+   #:populate-file-system
+   (lambda ()
+     ;; Create files before the root file system is made read-only.
+     (define passwd
+       (password-entry
+        (name user-name)
+        (real-name user-real-name)
+        (uid uid) (gid gid)
+        (shell "/bin/sh")          ;unused, doesn't have to match (user-shell)
+        (directory home-directory)))
+
+     (define groups
+       (list (group-entry (name "users") (gid gid))
+             (group-entry (gid 65534)             ;the overflow GID
+                          (name "overflow"))))
+
+     ;; Create /etc/passwd for applications that need it, such as mcron.
+     (mkdir-p "/etc")
+     (write-passwd (list passwd))
+     (write-group groups)
+
+     (unless network?
+       ;; When isolated from the network, provide a minimal /etc/hosts
+       ;; to resolve "localhost".
+       (call-with-output-file "/etc/hosts"
+         (lambda (port)
+           (display "127.0.0.1 localhost\n" port)
+           (chmod port #o444))))
+
+     ;; Create /tmp; bits of code expect it, such as
+     ;; 'least-authority-wrapper'.
+     (mkdir-p "/tmp"))
+
    #:namespaces (if network?
                     (delq 'net %namespaces)       ; share host network
                     %namespaces)
@@ -375,7 +375,6 @@ (define* (spawn-home-container home
                     (type "tmpfs")
                     (check? #f)))
    #:mappings (append network-mappings mappings)
-   #:writable-root? #t
    #:guest-uid uid
    #:guest-gid gid))
 
diff --git a/tests/guix-home.sh b/tests/guix-home.sh
index 649d811a0c..dbfe7dbd48 100644
--- a/tests/guix-home.sh
+++ b/tests/guix-home.sh
@@ -1,7 +1,7 @@
 # GNU Guix --- Functional package management for GNU
 # Copyright © 2021-2023 Andrew Tropin <[email-masked]>
 # Copyright © 2021 Oleg Pykhalov <[email-masked]>
-# Copyright © 2022, 2023 Ludovic Courtès <[email-masked]>
+# Copyright © 2022-2023, 2025 Ludovic Courtès <[email-masked]>
 #
 # This file is part of GNU Guix.
 #
@@ -132,6 +132,7 @@ EOF
 	     test -f '$HOME/sample/home.scm'
 	guix home container home.scm --expose="$PWD=$HOME/sample" -- \
 	     rm -v '$HOME/sample/home.scm' && false
+	guix home container home.scm -- touch /whatever && false
     else
 	echo "'guix home container' test SKIPPED" >&2
     fi
-- 
2.49.0