GNU bug report logs - #77288
[PATCH 0/6] Rootless guix-daemon on Guix System

Previous Next

Package: guix-patches;

Reported by: Ludovic Courtès <ludo <at> gnu.org>

Date: Wed, 26 Mar 2025 16:50:01 UTC

Severity: normal

Tags: patch

Done: Ludovic Courtès <ludo <at> gnu.org>

Bug is archived. No further changes may be made.

To add a comment to this bug, you must first unarchive it, by sending
a message to control AT debbugs.gnu.org, with unarchive 77288 in the body.
You can then email your comments to 77288 AT debbugs.gnu.org in the normal way.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to pelzflorian <at> pelzflorian.de, julien <at> lepiller.eu, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Wed, 26 Mar 2025 16:50:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Ludovic Courtès <ludo <at> gnu.org>:
New bug report received and forwarded. Copy sent to pelzflorian <at> pelzflorian.de, julien <at> lepiller.eu, guix-patches <at> gnu.org. (Wed, 26 Mar 2025 16:50:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: guix-patches <at> gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>,
 Reepca Russelstein <reepca <at> russelstein.xyz>
Subject: [PATCH 0/6] Rootless guix-daemon on Guix System
Date: Wed, 26 Mar 2025 17:48:38 +0100
Hello Guix,

This is a followup to <https://issues.guix.gnu.org/75810>,
which also depends on <https://issues.guix.gnu.org/77189>,
allowing us to run ‘guix-daemon’ without root privileges on
Guix System.  It is the second step of the migration path
outlined in <https://issues.guix.gnu.org/75810#111-lineno40>.

This is made difficult by the fact that all this is stateful:
if I switch my system to unprivileged mode, then the store and
all the data files of the daemon must have their owner changed
to ‘guix-daemon’.

This is implemented by an intermediate ‘guix-ownership’ one-shot
service, which completes instantaneously in the normal case and
chowns if when switching from privileged to unprivileged and vice
versa.  This service remains in ‘starting’ state until it is done.

Another complication is that of /gnu/store being mounted read-only.
To provide the ‘guix-ownership’ and ‘guix-daemon’ processes write
access to the store, they are started by a wrapper that creates a
new mount namespace and remounts the store read-write (similar to
‘makeStoreWritable’ in the daemon).

An open issue is ‘--keep-failed’: currently /tmp/guix-build-*
directories will remain owned by ‘guix-daemon’ as was discussed in
the initial message at <https://issues.guix.gnu.org/75810>.  It’s
a regression, but maybe it’s acceptable if we consider that this
feature is primarily used on single-user machines.

For now, the installation procedure creates /gnu/store, /var/guix,
etc. with root:root ownership.  Eventually, if/when we settle on
unprivileged guix-daemon, we should change that code to have
guix-daemon:guix-daemon as the owner.

Ludovic Courtès (6):
  syscalls: Add ‘unshare’.
  services: account: Create /var/guix/profiles/per-user/$USER.
  tests: guix-daemon: Send system log output to /dev/console.
  tests: guix-daemon: Wait for the ‘guix-daemon’ service to be up.
  services: guix: Allow ‘guix-daemon’ to run without root privileges.
  DRAFT news: Add entry about unprivileged guix-daemon on Guix System.

 doc/guix.texi           |  30 +++++++
 etc/news.scm            |  24 ++++++
 gnu/services/base.scm   | 187 ++++++++++++++++++++++++++++++++++++----
 gnu/system/shadow.scm   |  19 +++-
 gnu/tests/base.scm      |  60 +++++++++++--
 guix/build/syscalls.scm |  18 ++++
 tests/syscalls.scm      |   9 ++
 7 files changed, 325 insertions(+), 22 deletions(-)


base-commit: 1a69acce515de9be9b95df04c553a47a808e5034
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Wed, 26 Mar 2025 16:52:01 GMT) Full text and rfc822 format available.

Message #8 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH 2/6] services: account: Create
 /var/guix/profiles/per-user/$USER.
Date: Wed, 26 Mar 2025 17:51:03 +0100
* gnu/system/shadow.scm (account-shepherd-service): Create
/var/guix/profiles/per-user/$USER in ‘user-homes’ service.

Change-Id: I22e66e8a34d63686df9bae64c68df65c8889e72a
---
 gnu/system/shadow.scm | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/gnu/system/shadow.scm b/gnu/system/shadow.scm
index b68a818871..d0f1b6b2b1 100644
--- a/gnu/system/shadow.scm
+++ b/gnu/system/shadow.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2013-2020, 2022, 2023 Ludovic Courtès <ludo <at> gnu.org>
+;;; Copyright © 2013-2020, 2022-2023, 2025 Ludovic Courtès <ludo <at> gnu.org>
 ;;; Copyright © 2016 Alex Griffin <a <at> ajgrf.com>
 ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke <at> gnu.org>
 ;;; Copyright © 2020, 2023 Efraim Flashner <efraim <at> flashner.co.il>
@@ -460,6 +460,12 @@ (define (account-shepherd-service accounts+groups)
   (define accounts
     (filter user-account? accounts+groups))
 
+  (define regular-account-names
+    (filter-map (lambda (account)
+                  (and (not (user-account-system? account))
+                       (user-account-name account)))
+                accounts))
+
   ;; Create home directories only once 'file-systems' is up.  This makes sure
   ;; they are created in the right place if /home lives on a separate
   ;; partition.
@@ -480,6 +486,17 @@ (define (account-shepherd-service accounts+groups)
                       (activate-user-home
                        (map sexp->user-account
                             (list #$@(map user-account->gexp accounts))))
+
+                      ;; Create the user's profile directory upfront:
+                      ;; guix-daemon lacks permissions to create it when it is
+                      ;; running as an unprivileged user.
+                      (for-each (lambda (account)
+                                  (let ((profile (in-vicinity
+                                                  "/var/guix/profiles/per-user"
+                                                  account))
+                                        (owner (getpwnam account)))
+                                    (mkdir-p/perms profile owner #o755)))
+                                '#$regular-account-names)
                       #t)))                       ;success
          (documentation "Create user home directories."))))
 
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Wed, 26 Mar 2025 16:52:02 GMT) Full text and rfc822 format available.

Message #11 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH 1/6] syscalls: Add ‘unshare’.
Date: Wed, 26 Mar 2025 17:51:02 +0100
* guix/build/syscalls.scm (unshare): New procedure.

Change-Id: I344273b8bdeaa9366334e6e20ee7efc37eb6c8f7
---
 guix/build/syscalls.scm | 18 ++++++++++++++++++
 tests/syscalls.scm      |  9 +++++++++
 2 files changed, 27 insertions(+)

diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm
index 42232fc7f1..cf09cae3a4 100644
--- a/guix/build/syscalls.scm
+++ b/guix/build/syscalls.scm
@@ -145,6 +145,7 @@ (define-module (guix build syscalls)
             CLONE_NEWPID
             CLONE_NEWNET
             clone
+            unshare
             setns
 
             kexec-load-file
@@ -1213,6 +1214,23 @@ (define clone
                    (list err))
             ret)))))
 
+(define unshare
+  (let ((proc (syscall->procedure int "unshare" (list int))))
+    (lambda (flags)
+      "Disassociate the current process from parts of its execution context
+according to FLAGS, which must be a logical or of CLONE_NEW* constants.
+
+Note that CLONE_NEWUSER requires that the calling process be single-threaded,
+which is possible if and only if libgc is running a single marker thread; this
+can be achieved by setting the GC_MARKERS environment variable to 1.  If the
+calling process is multi-threaded, this throws to 'system-error' with EINVAL."
+      (let-values (((ret err)
+                    (without-automatic-finalization (proc flags))))
+        (unless (zero? ret)
+          (throw 'system-error "unshare" "~a: ~A"
+                 (list flags (strerror err))
+                 (list err)))))))
+
 (define setns
   ;; Some systems may be using an old (pre-2.14) version of glibc where there
   ;; is no 'setns' function available.
diff --git a/tests/syscalls.scm b/tests/syscalls.scm
index d2848879d7..879c3e4f25 100644
--- a/tests/syscalls.scm
+++ b/tests/syscalls.scm
@@ -149,6 +149,15 @@ (define perform-container-tests?
             ((_ . status)
              (= 42 (status:exit-val status))))))))
 
+(test-equal "unshare"
+  EPERM
+  ;; Unless running as root, (unshare CLONE_NEWNS) returns EPERM.
+  (catch 'system-error
+    (lambda ()
+      (unshare CLONE_NEWNS))
+    (lambda args
+      (system-error-errno args))))
+
 (unless perform-container-tests?
   (test-skip 1))
 (test-assert "setns"
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Wed, 26 Mar 2025 16:52:02 GMT) Full text and rfc822 format available.

Message #14 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH 3/6] tests: guix-daemon: Send system log output to
 /dev/console.
Date: Wed, 26 Mar 2025 17:51:04 +0100
* gnu/tests/base.scm (%daemon-os): New variable.
(%test-guix-daemon): Use it.

Change-Id: Iea31808cc59e94971ea4cbc12d565c94348bf7a4
---
 gnu/tests/base.scm | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm
index a7f8a5bf7c..0f7fb543a7 100644
--- a/gnu/tests/base.scm
+++ b/gnu/tests/base.scm
@@ -994,6 +994,10 @@ (define %test-activation
 ;;; Build daemon.
 ;;;
 
+(define %daemon-os
+  (operating-system-with-console-syslog
+   (simple-operating-system)))
+
 (define (manifest-entry-without-grafts entry)
   "Return ENTRY with grafts disabled on its contents."
   (manifest-entry
@@ -1168,7 +1172,7 @@ (define %test-guix-daemon
     (let ((os (marionette-operating-system
                (operating-system
                  (inherit (operating-system-with-gc-roots
-                           %simple-os
+                           %daemon-os
                            (list (profile
                                   (name "hello-build-dependencies")
                                   (content %hello-dependencies-manifest)))))
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Wed, 26 Mar 2025 16:52:02 GMT) Full text and rfc822 format available.

Message #17 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH 4/6] tests: guix-daemon: Wait for the ‘guix-daemon’ service to be up.
Date: Wed, 26 Mar 2025 17:51:05 +0100
* gnu/tests/base.scm (run-guix-daemon-test): Add “guix-daemon service is
up” test.

Change-Id: I4d44a1248599fec45c854c285d4da201c30eb00c
---
 gnu/tests/base.scm | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm
index 0f7fb543a7..83e047f7e6 100644
--- a/gnu/tests/base.scm
+++ b/gnu/tests/base.scm
@@ -1157,6 +1157,13 @@ (define (run-guix-daemon-test os)
           (test-runner-current (system-test-runner #$output))
           (test-begin "guix-daemon")
 
+          (test-assert "guix-service is running"
+            ;; Wait for 'guix-daemon' to be up.
+            (marionette-eval '(begin
+                                (use-modules (gnu services herd))
+                                (start-service 'guix-daemon))
+                             marionette))
+
           #$(guix-daemon-test-cases #~marionette)
 
           (test-end))))
-- 
2.49.0





Information forwarded to pelzflorian <at> pelzflorian.de, julien <at> lepiller.eu, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Wed, 26 Mar 2025 16:52:03 GMT) Full text and rfc822 format available.

Message #20 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH 6/6] DRAFT news: Add entry about unprivileged guix-daemon on
 Guix System.
Date: Wed, 26 Mar 2025 17:51:07 +0100
DRAFT: Temporary commit.

* etc/news.scm: Add it.

Change-Id: I28eae7f7b4305225b13281b99458cbedda3c3b94
---
 etc/news.scm | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/etc/news.scm b/etc/news.scm
index 4b3da44540..840f5cea53 100644
--- a/etc/news.scm
+++ b/etc/news.scm
@@ -37,6 +37,30 @@
 (channel-news
  (version 0)
 
+ (entry (commit "XXX")
+        (title
+         (en "Guix System can run @command{guix-daemon} without root
+privileges"))
+        (body
+         (en "On Guix System, @code{guix-service-type} can now be configured
+to run the build daemon, @command{guix-daemon}, without root privileges.  In
+that configuration, the daemon runs with the authority of the
+@code{guix-daemon} user, which we think can reduce the impact of some classes
+of vulnerabilities that could affect it.
+
+For now, this is opt-in: you have to change @code{guix-configuration} to set
+the @code{privileged?} field to @code{#f}.  When you do this, all the files in
+@file{/gnu/store}, @file{/var/guix}, etc. will have their ownership changed to
+the @code{guix-daemon} user (instead of @code{root}); this can take a while,
+especially if the store is big.  To learn more about it, run:
+
+@example
+info guix --index-search=guix-service-type
+@end example
+
+Eventually running @command{guix-daemon} without root privileges may become
+the default.")))
+
  (entry (commit "0e51c6547ffdaf91777f7383da4a52a1a07b7286")
         (title
          (en "Incompatible upgrade of the Syncthing service"))
-- 
2.49.0





Information forwarded to ludo <at> gnu.org, maxim.cournoyer <at> gmail.com, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Wed, 26 Mar 2025 16:52:03 GMT) Full text and rfc822 format available.

Message #23 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH 5/6] services: guix: Allow ‘guix-daemon’ to run without root privileges.
Date: Wed, 26 Mar 2025 17:51:06 +0100
* gnu/services/base.scm (run-with-writable-store)
(guix-ownership-change-program): New procedures.
(<guix-configuration>)[privileged?]: New field.
(guix-shepherd-service): Rename to…
(guix-shepherd-services): … this.   Add the ‘guix-ownership’ service.
Change ‘guix-daemon’ service to depend on it; when unprivileged,
prefix ‘daemon-command’ by ‘run-with-writable-store’ and
omit ‘--build-users-group’; adjust socket activation endpoints.
(guix-accounts): When unprivileged, create the “guix-daemon” user and
group.
(guix-service-type)[extensions]: Adjust to name change.
* gnu/tests/base.scm (run-guix-daemon-test): Add ‘name’ parameter.
(%test-guix-daemon): Adjust accordingly.
(%test-guix-daemon-unprivileged): New test.
* doc/guix.texi (Base Services): Document ‘privileged?’.

Change-Id: I28a9a22e617416c551dccb24e43a253b544ba163
---
 doc/guix.texi         |  30 +++++++
 gnu/services/base.scm | 187 ++++++++++++++++++++++++++++++++++++++----
 gnu/tests/base.scm    |  47 +++++++++--
 3 files changed, 244 insertions(+), 20 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 5af41830ca..f58688f57a 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -20046,6 +20046,36 @@ Base Services
 The Guix package to use.  @xref{Customizing the System-Wide Guix} to
 learn how to provide a package with a pre-configured set of channels.
 
+@cindex unprivileged @command{guix-daemon}
+@cindex rootless @command{guix-daemon}
+@item @code{privileged?} (default: @code{#t})
+Whether to run @command{guix-daemon} as root.
+
+When true, @command{guix-daemon} runs with root privileges and build
+processes run under unprivileged user accounts as specified by
+@code{build-group} and @code{build-accounts} (see below); when false,
+@command{guix-daemon} run as the @code{guix-daemon} user, which is
+unprivileged, and so do build processes.  The unprivileged or
+``rootless'' mode can reduce the impact of some classes of
+vulnerabilities that could affect the daemon.
+
+The default is currently @code{#t} (@command{guix-daemon} runs with root
+privileges) but may eventually be changed to @code{#f}.
+
+@quotation Warning
+When changing this option, @file{/gnu/store}, @file{/var/guix}, and
+@file{/etc/guix} have their ownership automatically changed by the
+@code{guix-ownership} service to either the @code{guix-daemon} user or
+the @code{root} user.
+
+This can take a while, especially if @file{/gnu/store} is big; it cannot
+be interrupted and @command{guix-daemon} cannot be used until it has
+completed.
+@end quotation
+
+@xref{Build Environment Setup}, for more information on the two ways to
+run @command{guix-daemon}.
+
 @item @code{build-group} (default: @code{"guixbuild"})
 Name of the group for build user accounts.
 
diff --git a/gnu/services/base.scm b/gnu/services/base.scm
index 9a9dfdb304..8f66f54e74 100644
--- a/gnu/services/base.scm
+++ b/gnu/services/base.scm
@@ -1917,6 +1917,100 @@ (define (guix-machines-files-installation machines)
                                    #$machines))
                  machines-file))))
 
+(define (run-with-writable-store)
+  "Return a wrapper that runs the given command under the specified UID and
+GID in a context where the store is writable, even if it was bind-mounted
+read-only via %IMMUTABLE-STORE (this wrapper must run as root)."
+  (program-file "run-with-writable-store"
+                (with-imported-modules (source-module-closure
+                                        '((guix build syscalls)))
+                  #~(begin
+                      (use-modules (guix build syscalls)
+                                   (ice-9 match))
+
+                      (define (ensure-writable-store store)
+                        ;; Create a new mount namespace and remount STORE with
+                        ;; write permissions if it's read-only.
+                        (unshare CLONE_NEWNS)
+                        (let ((fs (statfs store)))
+                          (unless (zero? (logand (file-system-mount-flags fs)
+                                                 ST_RDONLY))
+                            (mount store store "none"
+                                   (logior MS_BIND MS_REMOUNT)))))
+
+                      (match (command-line)
+                        ((_ user group command args ...)
+                         (ensure-writable-store #$(%store-prefix))
+                         (let ((uid (or (string->number user)
+                                        (passwd:uid (getpwnam user))))
+                               (gid (or (string->number group)
+                                        (group:gid (getgrnam group)))))
+                           (setgroups #())
+                           (setgid gid)
+                           (setuid uid)
+                           (apply execl command command args))))))))
+
+(define (guix-ownership-change-program)
+  "Return a program that changes ownership of the store and other data files
+of Guix to the given UID and GID."
+  (program-file "validate-guix-ownership"
+                (with-imported-modules (source-module-closure
+                                        '((guix build utils)))
+                  #~(begin
+                      (use-modules (guix build utils)
+                                   (ice-9 ftw)
+                                   (ice-9 match))
+
+                      (define (lchown file uid gid)
+                        (let ((parent (open (dirname file) O_DIRECTORY)))
+                          (chown-at parent (basename file) uid gid
+                                    AT_SYMLINK_NOFOLLOW)
+                          (close-port parent)))
+
+                      (define (change-ownership directory uid gid)
+                        ;; chown -R UID:GID DIRECTORY
+                        (file-system-fold (const #t)                 ;enter?
+                                          (lambda (file stat result) ;leaf
+                                            (if (eq? 'symlink (stat:type stat))
+                                                (lchown file uid gid)
+                                                (chown file uid gid)))
+                                          (const #t) ;down
+                                          (lambda (directory stat result) ;up
+                                            (chown directory uid gid))
+                                          (const #t) ;skip
+                                          (lambda (file stat errno result)
+                                            (format (current-error-port) "i/o error: ~a: ~a~%"
+                                                    file (strerror errno))
+                                            #f)
+                                          #t      ;seed
+                                          directory
+                                          lstat))
+
+                      (define (claim-data-ownership uid gid)
+                        (format #t "Changing file ownership for /gnu/store \
+and data directories to ~a:~a...~%"
+                                uid gid)
+                        (change-ownership #$(%store-prefix) uid gid)
+                        (let ((excluded '("." ".." "profiles" "userpool")))
+                          (for-each (lambda (directory)
+                                      (change-ownership (in-vicinity "/var/guix" directory)
+                                                        uid gid))
+                                    (scandir "/var/guix"
+                                             (lambda (file)
+                                               (not (member file
+                                                            excluded))))))
+                        (chown "/var/guix" uid gid)
+                        (change-ownership "/etc/guix" uid gid)
+                        (mkdir-p "/var/log/guix")
+                        (change-ownership "/var/log/guix" uid gid))
+
+                      (match (command-line)
+                        ((_ (= string->number (? integer? uid))
+                            (= string->number (? integer? gid)))
+                         (setlocale LC_ALL "C.UTF-8") ;for file name decoding
+                         (setvbuf (current-output-port) 'line)
+                         (claim-data-ownership uid gid)))))))
+
 (define-record-type* <guix-configuration>
   guix-configuration make-guix-configuration
   guix-configuration?
@@ -1958,6 +2052,8 @@ (define-record-type* <guix-configuration>
                     (default #f))
   (tmpdir           guix-tmpdir                   ;string | #f
                     (default #f))
+  (privileged?      guix-configuration-privileged?
+                    (default #t))
   (build-machines   guix-configuration-build-machines ;list of gexps | '()
                     (default '()))
   (environment      guix-configuration-environment  ;list of strings
@@ -2020,7 +2116,7 @@ (define shepherd-discover-action
                     (environ environment)
                     #t)))))
 
-(define (guix-shepherd-service config)
+(define (guix-shepherd-services config)
   "Return a <shepherd-service> for the Guix daemon service with CONFIG."
   (define locales
     (let-system (system target)
@@ -2029,16 +2125,57 @@ (define (guix-shepherd-service config)
           glibc-utf8-locales)))
 
   (match-record config <guix-configuration>
-    (guix build-group build-accounts chroot? authorize-key? authorized-keys
+    (guix privileged?
+          build-group build-accounts chroot? authorize-key? authorized-keys
           use-substitutes? substitute-urls max-silent-time timeout
           log-compression discover? extra-options log-file
           http-proxy tmpdir chroot-directories environment
           socket-directory-permissions socket-directory-group
           socket-directory-user)
     (list (shepherd-service
+           (provision '(guix-ownership))
+           (requirement '(user-processes user-homes))
+           (one-shot? #t)
+           (start #~(lambda ()
+                      (let* ((store #$(%store-prefix))
+                             (stat (lstat store))
+                             (privileged? #$(guix-configuration-privileged?
+                                             config))
+                             (change-ownership #$(guix-ownership-change-program))
+                             (with-writable-store #$(run-with-writable-store)))
+                        ;; Check whether we're switching from privileged to
+                        ;; unprivileged guix-daemon, or vice versa, and adjust
+                        ;; file ownership accordingly.  Spawn a child process
+                        ;; if and only if something needs to be changed.
+                        ;;
+                        ;; Note: This service remains in 'starting' state for
+                        ;; as long as CHANGE-OWNERSHIP is running.  That way,
+                        ;; 'guix-daemon' starts only once we're done.
+                        (cond ((and (not privileged?)
+                                    (or (zero? (stat:uid stat))
+                                        (zero? (stat:gid stat))))
+                               (let ((user (getpwnam "guix-daemon")))
+                                 (format #t "Changing to unprivileged guix-daemon.~%")
+                                 (zero?
+                                  (system* with-writable-store "0" "0"
+                                           change-ownership
+                                           (number->string (passwd:uid user))
+                                           (number->string (passwd:gid user))))))
+                              ((and privileged?
+                                    (and (not (zero? (stat:uid stat)))
+                                         (not (zero? (stat:gid stat)))))
+                               (format #t "Changing to privileged guix-daemon.~%")
+                               (zero? (system* with-writable-store "0" "0"
+                                               change-ownership "0" "0")))
+                              (else #t)))))
+           (documentation "Ensure that the store and other data files used by
+guix-daemon have the right ownership."))
+
+          (shepherd-service
            (documentation "Run the Guix daemon.")
            (provision '(guix-daemon))
            (requirement `(user-processes
+                          guix-ownership
                           ,@(if discover? '(avahi-daemon) '())))
            (actions (list shepherd-set-http-proxy-action
                           shepherd-discover-action))
@@ -2062,8 +2199,15 @@ (define (guix-shepherd-service config)
                     (or (getenv "discover") #$discover?))
 
                   (define daemon-command
-                    (cons* #$(file-append guix "/bin/guix-daemon")
-                           "--build-users-group" #$build-group
+                    (cons* #$@(if privileged?
+                                  #~()
+                                  #~(#$(run-with-writable-store)
+                                     "guix-daemon" "guix-daemon"))
+
+                           #$(file-append guix "/bin/guix-daemon")
+                           #$@(if privileged?
+                                  #~("--build-users-group" #$build-group)
+                                  #~())
                            "--max-silent-time"
                            #$(number->string max-silent-time)
                            "--timeout" #$(number->string timeout)
@@ -2144,9 +2288,11 @@ (define (guix-shepherd-service config)
                                      "/var/guix/daemon-socket/socket")
                                     #:name "socket"
                                     #:socket-owner
-                                    (or #$socket-directory-user 0)
+                                    (or #$socket-directory-user
+                                        #$(if privileged? 0 "guix-daemon"))
                                     #:socket-group
-                                    (or #$socket-directory-group 0)
+                                    (or #$socket-directory-group
+                                        #$(if privileged? 0 "guix-daemon"))
                                     #:socket-directory-permissions
                                     #$socket-directory-permissions)))
                        ((make-systemd-constructor daemon-command
@@ -2161,15 +2307,26 @@ (define (guix-shepherd-service config)
 
 (define (guix-accounts config)
   "Return the user accounts and user groups for CONFIG."
-  (cons (user-group
-         (name (guix-configuration-build-group config))
-         (system? #t)
+  (if (guix-configuration-privileged? config)
+      (cons (user-group
+             (name (guix-configuration-build-group config))
+             (system? #t)
 
-         ;; Use a fixed GID so that we can create the store with the right
-         ;; owner.
-         (id 30000))
-        (guix-build-accounts (guix-configuration-build-accounts config)
-                             #:group (guix-configuration-build-group config))))
+             ;; Use a fixed GID so that we can create the store with the right
+             ;; owner.
+             (id 30000))
+            (guix-build-accounts (guix-configuration-build-accounts config)
+                                 #:group (guix-configuration-build-group
+                                          config)))
+      (list (user-group (name "guix-daemon") (system? #t))
+            (user-account
+             (name "guix-daemon")
+             (group "guix-daemon")
+             (system? #t)
+             (supplementary-groups '("kvm"))
+             (comment "Guix Daemon User")
+             (home-directory "/var/empty")
+             (shell (file-append shadow "/sbin/nologin"))))))
 
 (define (guix-activation config)
   "Return the activation gexp for CONFIG."
@@ -2227,7 +2384,7 @@ (define guix-service-type
   (service-type
    (name 'guix)
    (extensions
-    (list (service-extension shepherd-root-service-type guix-shepherd-service)
+    (list (service-extension shepherd-root-service-type guix-shepherd-services)
           (service-extension account-service-type guix-accounts)
           (service-extension activation-service-type guix-activation)
           (service-extension profile-service-type
diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm
index 83e047f7e6..12d4e70ee5 100644
--- a/gnu/tests/base.scm
+++ b/gnu/tests/base.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2016-2020, 2022, 2024 Ludovic Courtès <ludo <at> gnu.org>
+;;; Copyright © 2016-2020, 2022, 2024-2025 Ludovic Courtès <ludo <at> gnu.org>
 ;;; Copyright © 2018 Clément Lassieur <clement <at> lassieur.org>
 ;;; Copyright © 2022 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
 ;;; Copyright © 2022 Marius Bakke <marius <at> gnu.org>
@@ -63,7 +63,8 @@ (define-module (gnu tests base)
 
             %hello-dependencies-manifest
             guix-daemon-test-cases
-            %test-guix-daemon))
+            %test-guix-daemon
+            %test-guix-daemon-unprivileged))
 
 (define %simple-os
   (simple-operating-system))
@@ -1121,7 +1122,7 @@ (define (guix-daemon-test-cases marionette)
                               (system-error-errno args)))
                          #$marionette))))
 
-(define (run-guix-daemon-test os)
+(define (run-guix-daemon-test os name)
   (define test-image
     (image (operating-system os)
            (format 'compressed-qcow2)
@@ -1161,6 +1162,12 @@ (define (run-guix-daemon-test os)
             ;; Wait for 'guix-daemon' to be up.
             (marionette-eval '(begin
                                 (use-modules (gnu services herd))
+                                (start-service 'guix-daemon)
+
+                                ;; XXX: Do it a second time to work around
+                                ;; <https://issues.guix.gnu.org/77274> and its
+                                ;; effect on the 'guix-ownership' service.
+                                ;; TODO: Remove when Shepherd 1.0.4 is out.
                                 (start-service 'guix-daemon))
                              marionette))
 
@@ -1168,7 +1175,7 @@ (define (run-guix-daemon-test os)
 
           (test-end))))
 
-  (gexp->derivation "guix-daemon-test" test))
+  (gexp->derivation name test))
 
 (define %test-guix-daemon
   (system-test
@@ -1190,4 +1197,34 @@ (define %test-guix-daemon
                               %base-user-accounts)))
                #:imported-modules '((gnu services herd)
                                     (guix combinators)))))
-      (run-guix-daemon-test os)))))
+      (run-guix-daemon-test os "guix-daemon-test")))))
+
+(define %test-guix-daemon-unprivileged
+  (system-test
+   (name "guix-daemon-unprivileged")
+   (description
+    "Test 'guix-daemon' behavior on a multi-user system, where 'guix-daemon'
+runs unprivileged.")
+   (value
+    (let ((os (marionette-operating-system
+               (let ((base (operating-system-with-gc-roots
+                            %daemon-os
+                            (list (profile
+                                   (name "hello-build-dependencies")
+                                   (content %hello-dependencies-manifest))))))
+                 (operating-system
+                   (inherit base)
+                   (kernel-arguments '("console=ttyS0"))
+                   (users (cons (user-account
+                                 (name "user")
+                                 (group "users"))
+                                %base-user-accounts))
+                   (services
+                    (modify-services (operating-system-user-services base)
+                      (guix-service-type
+                       config => (guix-configuration
+                                  (inherit config)
+                                  (privileged? #f)))))))
+               #:imported-modules '((gnu services herd)
+                                    (guix combinators)))))
+      (run-guix-daemon-test os "guix-daemon-unprivileged-test")))))
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Thu, 27 Mar 2025 13:28:03 GMT) Full text and rfc822 format available.

Message #26 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: Julien Lepiller <julien <at> lepiller.eu>, 77288 <at> debbugs.gnu.org,
 Reepca Russelstein <reepca <at> russelstein.xyz>
Subject: Re: [bug#77288] [PATCH 6/6] DRAFT news: Add entry about
 unprivileged guix-daemon on Guix System.
Date: Thu, 27 Mar 2025 14:27:33 +0100
[Message part 1 (text/plain, inline)]
Rootless daemon is an important change, though I have not tested yet...

Ludovic Courtès <ludo <at> gnu.org> writes:
> +Eventually running @command{guix-daemon} without root privileges may become
> +the default.")))
> +

I dislike the word “may” in this last sentence.  How about “likely
will” or some such thing, even if we have not reviewed bugfreeness of
Linux here?

Can you tell foreign distro users about their rootless options in the
news, too?

Could you add this German translation?

[german-rootless-news.scm (text/plain, inline)]
 (entry (commit "XXX")
        (title
         (en "Guix System can run @command{guix-daemon} without root
privileges")
         (de "Guix System kann @command{guix-daemon} ohne root-Berechtigungen
ausführen"))
        (body
         (en "On Guix System, @code{guix-service-type} can now be configured
to run the build daemon, @command{guix-daemon}, without root privileges.  In
that configuration, the daemon runs with the authority of the
@code{guix-daemon} user, which we think can reduce the impact of some classes
of vulnerabilities that could affect it.

For now, this is opt-in: you have to change @code{guix-configuration} to set
the @code{privileged?} field to @code{#f}.  When you do this, all the files in
@file{/gnu/store}, @file{/var/guix}, etc. will have their ownership changed to
the @code{guix-daemon} user (instead of @code{root}); this can take a while,
especially if the store is big.  To learn more about it, run:

@example
info guix --index-search=guix-service-type
@end example

Eventually running @command{guix-daemon} without root privileges may become
the default.")
         (de "Auf Guix System kann @code{guix-service-type} jetzt so
konfiguriert werden, dass der Erstellungs-Daemon @command{guix-daemon} ohne
root-Berechtigungen ausgeführt wird.  In dieser Konfiguration läuft der Daemon
mit den Berechtigungen des Benutzers @code{guix-daemon}, wovon wir glauben,
dass es die Auswirkungen mancher Schwachstellen-Kategorien verringert, die ihn
betreffen könnten.

Fürs Erste bleibt es Ihnen überlassen: Sie müssen @code{guix-configuration}
anpassen und dort das Feld @code{privileged?} auf @code{#f} setzen. Wenn Sie
das tun, wird der Besitzer aller Dateien in @file{/gnu/store},
@file{/var/guix}, usw.@: auf den Benutzer @code{guix-daemon} geändert (anstelle
von @code{root}); das kann eine Weile dauern, besonders wenn der Store groß
ist.  Um mehr zu erfahren, führen Sie aus:

@example
info guix --index-search=guix-service-type
@end example

Schließlich wird das Ausführen von @command{guix-daemon} ohne
root-Berechtigungen vielleicht die Vorgabe werden.")))
[Message part 3 (text/plain, inline)]

Regards,
Florian



Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Thu, 27 Mar 2025 13:40:05 GMT) Full text and rfc822 format available.

Message #29 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
Cc: Julien Lepiller <julien <at> lepiller.eu>, 77288 <at> debbugs.gnu.org,
 Reepca Russelstein <reepca <at> russelstein.xyz>
Subject: Re: [bug#77288] [PATCH 6/6] DRAFT news: Add entry about
 unprivileged guix-daemon on Guix System.
Date: Thu, 27 Mar 2025 14:38:54 +0100
Hi Florian,

"pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de> skribis:

> Rootless daemon is an important change, though I have not tested yet...
>
> Ludovic Courtès <ludo <at> gnu.org> writes:
>> +Eventually running @command{guix-daemon} without root privileges may become
>> +the default.")))
>> +
>
> I dislike the word “may” in this last sentence.  How about “likely
> will” or some such thing, even if we have not reviewed bugfreeness of
> Linux here?

Sure; I view “may” and “likely will” as synonymous, but maybe there are
subtleties that escape me.

> Can you tell foreign distro users about their rootless options in the
> news, too?

This news item is specifically about Guix System (announced upfront),
but I guess we can add a sentence toward the end.  (Thing is, the
situation will be simpler on foreign distros: we won’t support switching
between privileged and unprivileged, so either you get one or the
other.)

Thanks for the comments and for the translation!

Ludo’.




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Fri, 28 Mar 2025 07:41:01 GMT) Full text and rfc822 format available.

Message #32 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: Julien Lepiller <julien <at> lepiller.eu>, 77288 <at> debbugs.gnu.org,
 Reepca Russelstein <reepca <at> russelstein.xyz>
Subject: Re: [bug#77288] [PATCH 6/6] DRAFT news: Add entry about
 unprivileged guix-daemon on Guix System.
Date: Fri, 28 Mar 2025 08:39:14 +0100
Hi Ludo.

Ludovic Courtès <ludo <at> gnu.org> writes:
> "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de> skribis:
>> Ludovic Courtès <ludo <at> gnu.org> writes:
>>> +Eventually running @command{guix-daemon} without root privileges may become
>>> +the default.")))
>>> +
>>
>> I dislike the word “may” in this last sentence.  How about “likely
>> will” or some such thing, even if we have not reviewed bugfreeness of
>> Linux here?
>
> Sure; I view “may” and “likely will” as synonymous, but maybe there are
> subtleties that escape me.
>

I had understood “likely” to be “probably”.  With more thinking and
Wiktionary reading, “likely” can also mean “plausibly”, though not in
this context, I think.

The word “may” you had used means “possibly”, which sounds
indecisive/uncommitted and if even Guix does not know yet if rootless is
a good idea, how should a news-reading user know?  I thought you wrote
it for this purpose, because distros had been wary of USERNS some time
ago, or something.

This is why I translated to German as „vielleicht” (maybe).

Apparently some people want to banish the word “may” for its suppossed
ambiguity; it can mean “possibly” and “have permission to”, according to
Wiktionary.  This subtlety is news to me; I think context makes clear
what “may” means.

But perhaps it really is better to use unambiguous adverbs, adjectives
like “probably”.

In case you write this “probably” or “likely”, I would translate in
German as „wahrscheinlich” (probably).



>> Can you tell foreign distro users about their rootless options in the
>> news, too?
>
> This news item is specifically about Guix System (announced upfront),
> but I guess we can add a sentence toward the end.  (Thing is, the
> situation will be simpler on foreign distros: we won’t support switching
> between privileged and unprivileged, so either you get one or the
> other.)
>

Yes, please!  That sentence in parentheses explains the situation, but
needs rewording for etc/news.scm of course.


> Thanks for the comments and for the translation!
>
> Ludo’.

:)

I also now notice it is impossible to translate

info guix --index-search=guix-service-type

as

info guix.de --index-search=guix-service-type

although

info guix.fr --index-search=guix-service-type

works fine.  No idea why; both texi files look the same here.  But since
the rootless documentation is not translated yet anyway, please leave

info guix --index-search=guix-service-type

Regards,
Florian




Information forwarded to pelzflorian <at> pelzflorian.de, julien <at> lepiller.eu, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Thu, 17 Apr 2025 14:23:02 GMT) Full text and rfc822 format available.

Message #35 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>,
 "pelzflorian \(Florian Pelz\)" <pelzflorian <at> pelzflorian.de>
Subject: [PATCH v2 0/8] Rootless guix-daemon on Guix System
Date: Thu, 17 Apr 2025 16:21:35 +0200
Hello Guix,

Changes since v1:

  • ‘guix pull’ will now install systemd ‘.service’ files, which
    is necessary to ease migration on systemd-based distros.

  • Migration to the unprivileged daemon is now documented, for
    foreign distros in particular.

  • News entry reads “will likely” instead of “may eventually”
    and it mentions unprivileged daemon migration for other distros,
    as suggested by Florian.

I’d like to push this in the coming days.

Let me know what you think!

Ludo’.

Ludovic Courtès (8):
  self: Install systemd ‘.service’ files.
  doc: Document migration to the unprivileged daemon.
  syscalls: Add ‘unshare’.
  services: account: Create /var/guix/profiles/per-user/$USER.
  tests: guix-daemon: Send system log output to /dev/console.
  tests: guix-daemon: Wait for the ‘guix-daemon’ service to be up.
  services: guix: Allow ‘guix-daemon’ to run without root privileges.
  DRAFT news: Add entry about unprivileged guix-daemon on Guix System.

 doc/guix.texi           | 106 ++++++++++++++++++++++-
 etc/news.scm            |  31 +++++++
 gnu/services/base.scm   | 187 ++++++++++++++++++++++++++++++++++++----
 gnu/system/shadow.scm   |  19 +++-
 gnu/tests/base.scm      |  60 +++++++++++--
 guix/build/syscalls.scm |  18 ++++
 guix/self.scm           |  42 ++++++---
 tests/syscalls.scm      |   9 ++
 8 files changed, 439 insertions(+), 33 deletions(-)


base-commit: 4bd2949cfa7a8bf5dfe66adad1a76472af09708d
-- 
2.49.0





Information forwarded to guix <at> cbaines.net, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, zimon.toutoune <at> gmail.com, me <at> tobias.gr, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Thu, 17 Apr 2025 14:23:03 GMT) Full text and rfc822 format available.

Message #38 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v2 1/8] self: Install systemd ‘.service’ files.
Date: Thu, 17 Apr 2025 16:21:36 +0200
This is consistent with the ‘guix’ package and will prove helpful when
people want to replace /etc/systemd/system/guix*.service with newer
versions thereof.

* guix/self.scm (parameterized-file): New procedure, based on…
(selinux-policy): … this. Use ‘parameterized-file’.
(systemd-file): New procedure.
(miscellaneous-files): Add systemd files.

Change-Id: Ia489a955347cf648a86000cc1265769d66c3f0e8
---
 guix/self.scm | 42 ++++++++++++++++++++++++++++++++----------
 1 file changed, 32 insertions(+), 10 deletions(-)

diff --git a/guix/self.scm b/guix/self.scm
index 28239d53f5..2a99765359 100644
--- a/guix/self.scm
+++ b/guix/self.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2017-2023 Ludovic Courtès <ludo <at> gnu.org>
+;;; Copyright © 2017-2023, 2025 Ludovic Courtès <ludo <at> gnu.org>
 ;;; Copyright © 2020 Martin Becze <mjbecze <at> riseup.net>
 ;;; Copyright © 2023 Janneke Nieuwenhuizen <janneke <at> gnu.org>
 ;;; Copyright © 2024 gemmaro <gemmaro.dev <at> gmail.com>
@@ -666,24 +666,40 @@ (define* (guix-command modules
                 ;; Use a 'guile' variant that doesn't complain about locales.
                 #:guile (quiet-guile guile)))
 
-(define (selinux-policy source daemon)
-  "Return the SELinux policy file taken from SOURCE and adjusted to refer to
-DAEMON and to the current configuration variables."
+(define (parameterized-file source daemon file name)
+  "Return FILE taken from SOURCE (typically a '.in' file) and adjusted to
+refer to DAEMON and to the current configuration variables."
   (define build
     (with-imported-modules '((guix build utils))
       #~(begin
           (use-modules (guix build utils))
 
-          (copy-file #+(file-append* source "/etc/guix-daemon.cil.in")
-                     "guix-daemon.cil")
-          (substitute* "guix-daemon.cil"
+          (fluid-set! %default-port-encoding "UTF-8")
+          (copy-file #+(file-append* source file) #$name)
+          (substitute* #$name
             (("@guix_sysconfdir@") #$%sysconfdir)
             (("@guix_localstatedir@") #$%localstatedir)
+            (("@localstatedir@") #$%localstatedir)
             (("@storedir@") #$%storedir)
-            (("@prefix@") #$daemon))
-          (copy-file "guix-daemon.cil" #$output))))
+            (("@prefix@") #$daemon)
+            (("@GUIX_SUBSTITUTE_URLS@")
+             #$(string-join %default-substitute-urls)))
+          (copy-file #$name #$output))))
 
-  (computed-file "guix-daemon.cil" build))
+  (computed-file name build))
+
+(define (selinux-policy source daemon)
+  "Return the SELinux policy file taken from SOURCE and adjusted to refer to
+DAEMON and to the current configuration variables."
+  (parameterized-file source daemon
+                      "etc/guix-daemon.cil.in"
+                      "guix-daemon.cil"))
+
+(define (systemd-file source daemon file)
+  "Return the given systemd file from SOURCE parameterized for DAEMON."
+  (parameterized-file source daemon
+                      (string-append "etc/" file ".in")
+                      file))
 
 (define (miscellaneous-files source daemon)
   "Return data files taken from SOURCE."
@@ -698,6 +714,12 @@ (define (miscellaneous-files source daemon)
                    ,(file-append* source "/etc/completion/fish/guix.fish"))
                   ("share/selinux/guix-daemon.cil"
                    ,(selinux-policy source daemon))
+                  ,@(map (lambda (file)
+                           `(,(string-append "lib/systemd/system/" file)
+                             ,(systemd-file source daemon file)))
+                         '("guix-gc.service"
+                           "guix-publish.service"
+                           "guix-daemon.service"))
                   ("share/guix/berlin.guix.gnu.org.pub"
                    ,(file-append* source
                                   "/etc/substitutes/berlin.guix.gnu.org.pub"))
-- 
2.49.0





Information forwarded to ludo <at> gnu.org, maxim.cournoyer <at> gmail.com, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Thu, 17 Apr 2025 14:23:04 GMT) Full text and rfc822 format available.

Message #41 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v2 2/8] doc: Document migration to the unprivileged daemon.
Date: Thu, 17 Apr 2025 16:21:37 +0200
* doc/guix.texi (Build Environment Setup): Add “Migrating to the
Unprivileged Daemon” section.
(Upgrading Guix): Link to it.

Change-Id: I2bac3f4419d85b7c718c6c4a3908387b4f6ee582
---
 doc/guix.texi | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 67 insertions(+), 1 deletion(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 070528667f..377cb65326 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -1026,13 +1026,75 @@ Build Environment Setup
 In this configuration, @file{/gnu/store} is owned by the
 @code{guix-daemon} user.
 
+@anchor{unprivileged-daemon-migration}
+@unnumberedsubsubsec Migrating to the Unprivileged Daemon
+
+@cindex unprivileged daemon, migration
+@cindex rootless daemon, migration
+To switch an existing installation to the unprivileged execution mode, a
+number of steps must be taken: creating a new dedicated
+@code{guix-daemon} user account, changing ownership of the relevant
+files to @code{guix-daemon}, and ensuring that the @command{guix-daemon}
+program runs as @code{guix-daemon}.
+
+@quotation Warning
+Follow the instructions below only after making sure you have a recent
+version of @command{guix-daemon} with support for unprivileged
+execution.
+@end quotation
+
+File ownership can be changed, after stopping the daemon, by running the
+following commands as root (the @command{chown} can take a while if
+there are many files in @file{/gnu/store}):
+
+@example
+groupadd --system guix-daemon
+useradd -g guix-daemon -G guix-daemon,kvm               \
+        -d /var/empty -s $(which nologin)               \
+        -c "Guix daemon privilege separation user"      \
+        --system guix-daemon
+
+chown -R guix-daemon:guix-daemon                        \
+  /gnu                                                  \
+  /var/guix/@{daemon-socket,db,discover@}                 \
+  /var/guix/@{gcroots,offload,substitute,temproots@}      \
+  /var/log/guix                                         \
+  /etc/guix
+@end example
+
+If your system uses the systemd service manager, running the daemon as
+@code{guix-daemon} will be a matter of copying the relevant
+configuration files---make sure to review any changes you might have
+made in your own @file{.service} files before overwriting them:
+
+@example
+cp /var/guix/profiles/per-user/root/current-guix/lib/systemd/system/*.service \
+   /etc/systemd/system
+systemctl daemon-reload
+systemctl start guix-daemon
+@end example
+
+@quotation Warning
+The commands above assume that @command{guix pull} was run for the root
+user.  You can check whether this is the case by running this command:
+
+@example
+grep User=guix-daemon \
+  /var/guix/profiles/per-user/root/current-guix/lib/systemd/system/guix-daemon.service
+@end example
+
+If that command does not show the @code{User=guix-daemon} line, then run
+@command{guix pull} as the root user.
+@end quotation
+
 @unnumberedsubsubsec The Isolated Build Environment
 
 @cindex chroot
 @cindex build environment isolation
 @cindex isolated build environment
 @cindex hermetic build environment
-In both cases, the daemon starts build processes without privileges in
+In both cases, privileged and unprivileged,
+the daemon starts build processes without privileges in
 an @emph{isolated} or @emph{hermetic} build environment---a ``chroot''.
 On GNU/Linux, by default, the build environment contains nothing but:
 
@@ -2035,6 +2097,10 @@ Upgrading Guix
 On Guix System, upgrading the daemon is achieved by reconfiguring the
 system (@pxref{Invoking guix system, @code{guix system reconfigure}}).
 
+To migrate an existing installation to the @emph{unprivileged daemon}
+where @command{guix-daemon} does not run as root,
+@pxref{unprivileged-daemon-migration}.
+
 @c TODO What else?
 
 @c *********************************************************************
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Thu, 17 Apr 2025 14:23:05 GMT) Full text and rfc822 format available.

Message #44 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v2 3/8] syscalls: Add ‘unshare’.
Date: Thu, 17 Apr 2025 16:21:38 +0200
* guix/build/syscalls.scm (unshare): New procedure.

Change-Id: I344273b8bdeaa9366334e6e20ee7efc37eb6c8f7
---
 guix/build/syscalls.scm | 18 ++++++++++++++++++
 tests/syscalls.scm      |  9 +++++++++
 2 files changed, 27 insertions(+)

diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm
index 42232fc7f1..cf09cae3a4 100644
--- a/guix/build/syscalls.scm
+++ b/guix/build/syscalls.scm
@@ -145,6 +145,7 @@ (define-module (guix build syscalls)
             CLONE_NEWPID
             CLONE_NEWNET
             clone
+            unshare
             setns
 
             kexec-load-file
@@ -1213,6 +1214,23 @@ (define clone
                    (list err))
             ret)))))
 
+(define unshare
+  (let ((proc (syscall->procedure int "unshare" (list int))))
+    (lambda (flags)
+      "Disassociate the current process from parts of its execution context
+according to FLAGS, which must be a logical or of CLONE_NEW* constants.
+
+Note that CLONE_NEWUSER requires that the calling process be single-threaded,
+which is possible if and only if libgc is running a single marker thread; this
+can be achieved by setting the GC_MARKERS environment variable to 1.  If the
+calling process is multi-threaded, this throws to 'system-error' with EINVAL."
+      (let-values (((ret err)
+                    (without-automatic-finalization (proc flags))))
+        (unless (zero? ret)
+          (throw 'system-error "unshare" "~a: ~A"
+                 (list flags (strerror err))
+                 (list err)))))))
+
 (define setns
   ;; Some systems may be using an old (pre-2.14) version of glibc where there
   ;; is no 'setns' function available.
diff --git a/tests/syscalls.scm b/tests/syscalls.scm
index d2848879d7..879c3e4f25 100644
--- a/tests/syscalls.scm
+++ b/tests/syscalls.scm
@@ -149,6 +149,15 @@ (define perform-container-tests?
             ((_ . status)
              (= 42 (status:exit-val status))))))))
 
+(test-equal "unshare"
+  EPERM
+  ;; Unless running as root, (unshare CLONE_NEWNS) returns EPERM.
+  (catch 'system-error
+    (lambda ()
+      (unshare CLONE_NEWNS))
+    (lambda args
+      (system-error-errno args))))
+
 (unless perform-container-tests?
   (test-skip 1))
 (test-assert "setns"
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Thu, 17 Apr 2025 14:23:05 GMT) Full text and rfc822 format available.

Message #47 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v2 6/8] tests: guix-daemon: Wait for the ‘guix-daemon’ service to be up.
Date: Thu, 17 Apr 2025 16:21:41 +0200
* gnu/tests/base.scm (run-guix-daemon-test): Add “guix-daemon service is
up” test.

Change-Id: I4d44a1248599fec45c854c285d4da201c30eb00c
---
 gnu/tests/base.scm | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm
index 0f7fb543a7..83e047f7e6 100644
--- a/gnu/tests/base.scm
+++ b/gnu/tests/base.scm
@@ -1157,6 +1157,13 @@ (define (run-guix-daemon-test os)
           (test-runner-current (system-test-runner #$output))
           (test-begin "guix-daemon")
 
+          (test-assert "guix-service is running"
+            ;; Wait for 'guix-daemon' to be up.
+            (marionette-eval '(begin
+                                (use-modules (gnu services herd))
+                                (start-service 'guix-daemon))
+                             marionette))
+
           #$(guix-daemon-test-cases #~marionette)
 
           (test-end))))
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Thu, 17 Apr 2025 14:23:07 GMT) Full text and rfc822 format available.

Message #50 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v2 5/8] tests: guix-daemon: Send system log output to
 /dev/console.
Date: Thu, 17 Apr 2025 16:21:40 +0200
* gnu/tests/base.scm (%daemon-os): New variable.
(%test-guix-daemon): Use it.

Change-Id: Iea31808cc59e94971ea4cbc12d565c94348bf7a4
---
 gnu/tests/base.scm | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm
index a7f8a5bf7c..0f7fb543a7 100644
--- a/gnu/tests/base.scm
+++ b/gnu/tests/base.scm
@@ -994,6 +994,10 @@ (define %test-activation
 ;;; Build daemon.
 ;;;
 
+(define %daemon-os
+  (operating-system-with-console-syslog
+   (simple-operating-system)))
+
 (define (manifest-entry-without-grafts entry)
   "Return ENTRY with grafts disabled on its contents."
   (manifest-entry
@@ -1168,7 +1172,7 @@ (define %test-guix-daemon
     (let ((os (marionette-operating-system
                (operating-system
                  (inherit (operating-system-with-gc-roots
-                           %simple-os
+                           %daemon-os
                            (list (profile
                                   (name "hello-build-dependencies")
                                   (content %hello-dependencies-manifest)))))
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Thu, 17 Apr 2025 14:23:08 GMT) Full text and rfc822 format available.

Message #53 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v2 4/8] services: account: Create
 /var/guix/profiles/per-user/$USER.
Date: Thu, 17 Apr 2025 16:21:39 +0200
* gnu/system/shadow.scm (account-shepherd-service): Create
/var/guix/profiles/per-user/$USER in ‘user-homes’ service.

Change-Id: I22e66e8a34d63686df9bae64c68df65c8889e72a
---
 gnu/system/shadow.scm | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/gnu/system/shadow.scm b/gnu/system/shadow.scm
index b68a818871..d0f1b6b2b1 100644
--- a/gnu/system/shadow.scm
+++ b/gnu/system/shadow.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2013-2020, 2022, 2023 Ludovic Courtès <ludo <at> gnu.org>
+;;; Copyright © 2013-2020, 2022-2023, 2025 Ludovic Courtès <ludo <at> gnu.org>
 ;;; Copyright © 2016 Alex Griffin <a <at> ajgrf.com>
 ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke <at> gnu.org>
 ;;; Copyright © 2020, 2023 Efraim Flashner <efraim <at> flashner.co.il>
@@ -460,6 +460,12 @@ (define (account-shepherd-service accounts+groups)
   (define accounts
     (filter user-account? accounts+groups))
 
+  (define regular-account-names
+    (filter-map (lambda (account)
+                  (and (not (user-account-system? account))
+                       (user-account-name account)))
+                accounts))
+
   ;; Create home directories only once 'file-systems' is up.  This makes sure
   ;; they are created in the right place if /home lives on a separate
   ;; partition.
@@ -480,6 +486,17 @@ (define (account-shepherd-service accounts+groups)
                       (activate-user-home
                        (map sexp->user-account
                             (list #$@(map user-account->gexp accounts))))
+
+                      ;; Create the user's profile directory upfront:
+                      ;; guix-daemon lacks permissions to create it when it is
+                      ;; running as an unprivileged user.
+                      (for-each (lambda (account)
+                                  (let ((profile (in-vicinity
+                                                  "/var/guix/profiles/per-user"
+                                                  account))
+                                        (owner (getpwnam account)))
+                                    (mkdir-p/perms profile owner #o755)))
+                                '#$regular-account-names)
                       #t)))                       ;success
          (documentation "Create user home directories."))))
 
-- 
2.49.0





Information forwarded to pelzflorian <at> pelzflorian.de, julien <at> lepiller.eu, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Thu, 17 Apr 2025 14:23:08 GMT) Full text and rfc822 format available.

Message #56 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v2 8/8] DRAFT news: Add entry about unprivileged guix-daemon
 on Guix System.
Date: Thu, 17 Apr 2025 16:21:43 +0200
DRAFT: Temporary commit.

* etc/news.scm: Add it.

Change-Id: I28eae7f7b4305225b13281b99458cbedda3c3b94
---
 etc/news.scm | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/etc/news.scm b/etc/news.scm
index 4b3da44540..c1f2315e33 100644
--- a/etc/news.scm
+++ b/etc/news.scm
@@ -37,6 +37,37 @@
 (channel-news
  (version 0)
 
+ (entry (commit "XXX")
+        (title
+         (en "Guix System can run @command{guix-daemon} without root
+privileges"))
+        (body
+         (en "On Guix System, @code{guix-service-type} can now be configured
+to run the build daemon, @command{guix-daemon}, without root privileges.  In
+that configuration, the daemon runs with the authority of the
+@code{guix-daemon} user, which we think can reduce the impact of some classes
+of vulnerabilities that could affect it.
+
+For now, this is opt-in: you have to change @code{guix-configuration} to set
+the @code{privileged?} field to @code{#f}.  When you do this, all the files in
+@file{/gnu/store}, @file{/var/guix}, etc. will have their ownership changed to
+the @code{guix-daemon} user (instead of @code{root}); this can take a while,
+especially if the store is big.  To learn more about it, run:
+
+@example
+info guix --index-search=guix-service-type
+@end example
+
+Running @command{guix-daemon} without root privileges will likely become the
+default in the future.
+
+Users of Guix on other distributions can find information on how to migrate in
+the manual:
+
+@example
+info guix --index-search=migration
+@end example")))
+
  (entry (commit "0e51c6547ffdaf91777f7383da4a52a1a07b7286")
         (title
          (en "Incompatible upgrade of the Syncthing service"))
-- 
2.49.0





Information forwarded to ludo <at> gnu.org, maxim.cournoyer <at> gmail.com, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Thu, 17 Apr 2025 14:23:09 GMT) Full text and rfc822 format available.

Message #59 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v2 7/8] services: guix: Allow ‘guix-daemon’ to run without root privileges.
Date: Thu, 17 Apr 2025 16:21:42 +0200
* gnu/services/base.scm (run-with-writable-store)
(guix-ownership-change-program): New procedures.
(<guix-configuration>)[privileged?]: New field.
(guix-shepherd-service): Rename to…
(guix-shepherd-services): … this.   Add the ‘guix-ownership’ service.
Change ‘guix-daemon’ service to depend on it; when unprivileged,
prefix ‘daemon-command’ by ‘run-with-writable-store’ and
omit ‘--build-users-group’; adjust socket activation endpoints.
(guix-accounts): When unprivileged, create the “guix-daemon” user and
group.
(guix-service-type)[extensions]: Adjust to name change.
* gnu/tests/base.scm (run-guix-daemon-test): Add ‘name’ parameter.
(%test-guix-daemon): Adjust accordingly.
(%test-guix-daemon-unprivileged): New test.
* doc/guix.texi (Base Services): Document ‘privileged?’.
(Migrating to the Unprivileged Daemon): Explain that this is automatic
on Guix System.

Change-Id: I28a9a22e617416c551dccb24e43a253b544ba163
---
 doc/guix.texi         |  38 +++++++++
 gnu/services/base.scm | 187 ++++++++++++++++++++++++++++++++++++++----
 gnu/tests/base.scm    |  47 +++++++++--
 3 files changed, 252 insertions(+), 20 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 377cb65326..8243bd0547 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -1037,6 +1037,14 @@ Build Environment Setup
 files to @code{guix-daemon}, and ensuring that the @command{guix-daemon}
 program runs as @code{guix-daemon}.
 
+On Guix System, these steps are carried out automatically when you set
+the @code{privileged?} field of the @code{guix-configuration} record to
+@code{#f} and reconfigure (@pxref{guix-configuration-type,
+@code{guix-configuration}}).
+
+However, on a foreign distribution, the process is manual.  The
+following paragraphs describe what you need to do.
+
 @quotation Warning
 Follow the instructions below only after making sure you have a recent
 version of @command{guix-daemon} with support for unprivileged
@@ -20105,6 +20113,36 @@ Base Services
 The Guix package to use.  @xref{Customizing the System-Wide Guix} to
 learn how to provide a package with a pre-configured set of channels.
 
+@cindex unprivileged @command{guix-daemon}
+@cindex rootless @command{guix-daemon}
+@item @code{privileged?} (default: @code{#t})
+Whether to run @command{guix-daemon} as root.
+
+When true, @command{guix-daemon} runs with root privileges and build
+processes run under unprivileged user accounts as specified by
+@code{build-group} and @code{build-accounts} (see below); when false,
+@command{guix-daemon} run as the @code{guix-daemon} user, which is
+unprivileged, and so do build processes.  The unprivileged or
+``rootless'' mode can reduce the impact of some classes of
+vulnerabilities that could affect the daemon.
+
+The default is currently @code{#t} (@command{guix-daemon} runs with root
+privileges) but may eventually be changed to @code{#f}.
+
+@quotation Warning
+When changing this option, @file{/gnu/store}, @file{/var/guix}, and
+@file{/etc/guix} have their ownership automatically changed by the
+@code{guix-ownership} service to either the @code{guix-daemon} user or
+the @code{root} user (@pxref{unprivileged-daemon-migration}).
+
+This can take a while, especially if @file{/gnu/store} is big; it cannot
+be interrupted and @command{guix-daemon} cannot be used until it has
+completed.
+@end quotation
+
+@xref{Build Environment Setup}, for more information on the two ways to
+run @command{guix-daemon}.
+
 @item @code{build-group} (default: @code{"guixbuild"})
 Name of the group for build user accounts.
 
diff --git a/gnu/services/base.scm b/gnu/services/base.scm
index 490376d446..c48874b0d9 100644
--- a/gnu/services/base.scm
+++ b/gnu/services/base.scm
@@ -1918,6 +1918,100 @@ (define (guix-machines-files-installation machines)
                                    #$machines))
                  machines-file))))
 
+(define (run-with-writable-store)
+  "Return a wrapper that runs the given command under the specified UID and
+GID in a context where the store is writable, even if it was bind-mounted
+read-only via %IMMUTABLE-STORE (this wrapper must run as root)."
+  (program-file "run-with-writable-store"
+                (with-imported-modules (source-module-closure
+                                        '((guix build syscalls)))
+                  #~(begin
+                      (use-modules (guix build syscalls)
+                                   (ice-9 match))
+
+                      (define (ensure-writable-store store)
+                        ;; Create a new mount namespace and remount STORE with
+                        ;; write permissions if it's read-only.
+                        (unshare CLONE_NEWNS)
+                        (let ((fs (statfs store)))
+                          (unless (zero? (logand (file-system-mount-flags fs)
+                                                 ST_RDONLY))
+                            (mount store store "none"
+                                   (logior MS_BIND MS_REMOUNT)))))
+
+                      (match (command-line)
+                        ((_ user group command args ...)
+                         (ensure-writable-store #$(%store-prefix))
+                         (let ((uid (or (string->number user)
+                                        (passwd:uid (getpwnam user))))
+                               (gid (or (string->number group)
+                                        (group:gid (getgrnam group)))))
+                           (setgroups #())
+                           (setgid gid)
+                           (setuid uid)
+                           (apply execl command command args))))))))
+
+(define (guix-ownership-change-program)
+  "Return a program that changes ownership of the store and other data files
+of Guix to the given UID and GID."
+  (program-file "validate-guix-ownership"
+                (with-imported-modules (source-module-closure
+                                        '((guix build utils)))
+                  #~(begin
+                      (use-modules (guix build utils)
+                                   (ice-9 ftw)
+                                   (ice-9 match))
+
+                      (define (lchown file uid gid)
+                        (let ((parent (open (dirname file) O_DIRECTORY)))
+                          (chown-at parent (basename file) uid gid
+                                    AT_SYMLINK_NOFOLLOW)
+                          (close-port parent)))
+
+                      (define (change-ownership directory uid gid)
+                        ;; chown -R UID:GID DIRECTORY
+                        (file-system-fold (const #t)                 ;enter?
+                                          (lambda (file stat result) ;leaf
+                                            (if (eq? 'symlink (stat:type stat))
+                                                (lchown file uid gid)
+                                                (chown file uid gid)))
+                                          (const #t) ;down
+                                          (lambda (directory stat result) ;up
+                                            (chown directory uid gid))
+                                          (const #t) ;skip
+                                          (lambda (file stat errno result)
+                                            (format (current-error-port) "i/o error: ~a: ~a~%"
+                                                    file (strerror errno))
+                                            #f)
+                                          #t      ;seed
+                                          directory
+                                          lstat))
+
+                      (define (claim-data-ownership uid gid)
+                        (format #t "Changing file ownership for /gnu/store \
+and data directories to ~a:~a...~%"
+                                uid gid)
+                        (change-ownership #$(%store-prefix) uid gid)
+                        (let ((excluded '("." ".." "profiles" "userpool")))
+                          (for-each (lambda (directory)
+                                      (change-ownership (in-vicinity "/var/guix" directory)
+                                                        uid gid))
+                                    (scandir "/var/guix"
+                                             (lambda (file)
+                                               (not (member file
+                                                            excluded))))))
+                        (chown "/var/guix" uid gid)
+                        (change-ownership "/etc/guix" uid gid)
+                        (mkdir-p "/var/log/guix")
+                        (change-ownership "/var/log/guix" uid gid))
+
+                      (match (command-line)
+                        ((_ (= string->number (? integer? uid))
+                            (= string->number (? integer? gid)))
+                         (setlocale LC_ALL "C.UTF-8") ;for file name decoding
+                         (setvbuf (current-output-port) 'line)
+                         (claim-data-ownership uid gid)))))))
+
 (define-record-type* <guix-configuration>
   guix-configuration make-guix-configuration
   guix-configuration?
@@ -1959,6 +2053,8 @@ (define-record-type* <guix-configuration>
                     (default #f))
   (tmpdir           guix-tmpdir                   ;string | #f
                     (default #f))
+  (privileged?      guix-configuration-privileged?
+                    (default #t))
   (build-machines   guix-configuration-build-machines ;list of gexps | '()
                     (default '()))
   (environment      guix-configuration-environment  ;list of strings
@@ -2021,7 +2117,7 @@ (define shepherd-discover-action
                     (environ environment)
                     #t)))))
 
-(define (guix-shepherd-service config)
+(define (guix-shepherd-services config)
   "Return a <shepherd-service> for the Guix daemon service with CONFIG."
   (define locales
     (let-system (system target)
@@ -2030,16 +2126,57 @@ (define (guix-shepherd-service config)
           glibc-utf8-locales)))
 
   (match-record config <guix-configuration>
-    (guix build-group build-accounts chroot? authorize-key? authorized-keys
+    (guix privileged?
+          build-group build-accounts chroot? authorize-key? authorized-keys
           use-substitutes? substitute-urls max-silent-time timeout
           log-compression discover? extra-options log-file
           http-proxy tmpdir chroot-directories environment
           socket-directory-permissions socket-directory-group
           socket-directory-user)
     (list (shepherd-service
+           (provision '(guix-ownership))
+           (requirement '(user-processes user-homes))
+           (one-shot? #t)
+           (start #~(lambda ()
+                      (let* ((store #$(%store-prefix))
+                             (stat (lstat store))
+                             (privileged? #$(guix-configuration-privileged?
+                                             config))
+                             (change-ownership #$(guix-ownership-change-program))
+                             (with-writable-store #$(run-with-writable-store)))
+                        ;; Check whether we're switching from privileged to
+                        ;; unprivileged guix-daemon, or vice versa, and adjust
+                        ;; file ownership accordingly.  Spawn a child process
+                        ;; if and only if something needs to be changed.
+                        ;;
+                        ;; Note: This service remains in 'starting' state for
+                        ;; as long as CHANGE-OWNERSHIP is running.  That way,
+                        ;; 'guix-daemon' starts only once we're done.
+                        (cond ((and (not privileged?)
+                                    (or (zero? (stat:uid stat))
+                                        (zero? (stat:gid stat))))
+                               (let ((user (getpwnam "guix-daemon")))
+                                 (format #t "Changing to unprivileged guix-daemon.~%")
+                                 (zero?
+                                  (system* with-writable-store "0" "0"
+                                           change-ownership
+                                           (number->string (passwd:uid user))
+                                           (number->string (passwd:gid user))))))
+                              ((and privileged?
+                                    (and (not (zero? (stat:uid stat)))
+                                         (not (zero? (stat:gid stat)))))
+                               (format #t "Changing to privileged guix-daemon.~%")
+                               (zero? (system* with-writable-store "0" "0"
+                                               change-ownership "0" "0")))
+                              (else #t)))))
+           (documentation "Ensure that the store and other data files used by
+guix-daemon have the right ownership."))
+
+          (shepherd-service
            (documentation "Run the Guix daemon.")
            (provision '(guix-daemon))
            (requirement `(user-processes
+                          guix-ownership
                           ,@(if discover? '(avahi-daemon) '())))
            (actions (list shepherd-set-http-proxy-action
                           shepherd-discover-action))
@@ -2063,8 +2200,15 @@ (define (guix-shepherd-service config)
                     (or (getenv "discover") #$discover?))
 
                   (define daemon-command
-                    (cons* #$(file-append guix "/bin/guix-daemon")
-                           "--build-users-group" #$build-group
+                    (cons* #$@(if privileged?
+                                  #~()
+                                  #~(#$(run-with-writable-store)
+                                     "guix-daemon" "guix-daemon"))
+
+                           #$(file-append guix "/bin/guix-daemon")
+                           #$@(if privileged?
+                                  #~("--build-users-group" #$build-group)
+                                  #~())
                            "--max-silent-time"
                            #$(number->string max-silent-time)
                            "--timeout" #$(number->string timeout)
@@ -2145,9 +2289,11 @@ (define (guix-shepherd-service config)
                                      "/var/guix/daemon-socket/socket")
                                     #:name "socket"
                                     #:socket-owner
-                                    (or #$socket-directory-user 0)
+                                    (or #$socket-directory-user
+                                        #$(if privileged? 0 "guix-daemon"))
                                     #:socket-group
-                                    (or #$socket-directory-group 0)
+                                    (or #$socket-directory-group
+                                        #$(if privileged? 0 "guix-daemon"))
                                     #:socket-directory-permissions
                                     #$socket-directory-permissions)))
                        ((make-systemd-constructor daemon-command
@@ -2162,15 +2308,26 @@ (define (guix-shepherd-service config)
 
 (define (guix-accounts config)
   "Return the user accounts and user groups for CONFIG."
-  (cons (user-group
-         (name (guix-configuration-build-group config))
-         (system? #t)
+  (if (guix-configuration-privileged? config)
+      (cons (user-group
+             (name (guix-configuration-build-group config))
+             (system? #t)
 
-         ;; Use a fixed GID so that we can create the store with the right
-         ;; owner.
-         (id 30000))
-        (guix-build-accounts (guix-configuration-build-accounts config)
-                             #:group (guix-configuration-build-group config))))
+             ;; Use a fixed GID so that we can create the store with the right
+             ;; owner.
+             (id 30000))
+            (guix-build-accounts (guix-configuration-build-accounts config)
+                                 #:group (guix-configuration-build-group
+                                          config)))
+      (list (user-group (name "guix-daemon") (system? #t))
+            (user-account
+             (name "guix-daemon")
+             (group "guix-daemon")
+             (system? #t)
+             (supplementary-groups '("kvm"))
+             (comment "Guix Daemon User")
+             (home-directory "/var/empty")
+             (shell (file-append shadow "/sbin/nologin"))))))
 
 (define (guix-activation config)
   "Return the activation gexp for CONFIG."
@@ -2228,7 +2385,7 @@ (define guix-service-type
   (service-type
    (name 'guix)
    (extensions
-    (list (service-extension shepherd-root-service-type guix-shepherd-service)
+    (list (service-extension shepherd-root-service-type guix-shepherd-services)
           (service-extension account-service-type guix-accounts)
           (service-extension activation-service-type guix-activation)
           (service-extension profile-service-type
diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm
index 83e047f7e6..12d4e70ee5 100644
--- a/gnu/tests/base.scm
+++ b/gnu/tests/base.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2016-2020, 2022, 2024 Ludovic Courtès <ludo <at> gnu.org>
+;;; Copyright © 2016-2020, 2022, 2024-2025 Ludovic Courtès <ludo <at> gnu.org>
 ;;; Copyright © 2018 Clément Lassieur <clement <at> lassieur.org>
 ;;; Copyright © 2022 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
 ;;; Copyright © 2022 Marius Bakke <marius <at> gnu.org>
@@ -63,7 +63,8 @@ (define-module (gnu tests base)
 
             %hello-dependencies-manifest
             guix-daemon-test-cases
-            %test-guix-daemon))
+            %test-guix-daemon
+            %test-guix-daemon-unprivileged))
 
 (define %simple-os
   (simple-operating-system))
@@ -1121,7 +1122,7 @@ (define (guix-daemon-test-cases marionette)
                               (system-error-errno args)))
                          #$marionette))))
 
-(define (run-guix-daemon-test os)
+(define (run-guix-daemon-test os name)
   (define test-image
     (image (operating-system os)
            (format 'compressed-qcow2)
@@ -1161,6 +1162,12 @@ (define (run-guix-daemon-test os)
             ;; Wait for 'guix-daemon' to be up.
             (marionette-eval '(begin
                                 (use-modules (gnu services herd))
+                                (start-service 'guix-daemon)
+
+                                ;; XXX: Do it a second time to work around
+                                ;; <https://issues.guix.gnu.org/77274> and its
+                                ;; effect on the 'guix-ownership' service.
+                                ;; TODO: Remove when Shepherd 1.0.4 is out.
                                 (start-service 'guix-daemon))
                              marionette))
 
@@ -1168,7 +1175,7 @@ (define (run-guix-daemon-test os)
 
           (test-end))))
 
-  (gexp->derivation "guix-daemon-test" test))
+  (gexp->derivation name test))
 
 (define %test-guix-daemon
   (system-test
@@ -1190,4 +1197,34 @@ (define %test-guix-daemon
                               %base-user-accounts)))
                #:imported-modules '((gnu services herd)
                                     (guix combinators)))))
-      (run-guix-daemon-test os)))))
+      (run-guix-daemon-test os "guix-daemon-test")))))
+
+(define %test-guix-daemon-unprivileged
+  (system-test
+   (name "guix-daemon-unprivileged")
+   (description
+    "Test 'guix-daemon' behavior on a multi-user system, where 'guix-daemon'
+runs unprivileged.")
+   (value
+    (let ((os (marionette-operating-system
+               (let ((base (operating-system-with-gc-roots
+                            %daemon-os
+                            (list (profile
+                                   (name "hello-build-dependencies")
+                                   (content %hello-dependencies-manifest))))))
+                 (operating-system
+                   (inherit base)
+                   (kernel-arguments '("console=ttyS0"))
+                   (users (cons (user-account
+                                 (name "user")
+                                 (group "users"))
+                                %base-user-accounts))
+                   (services
+                    (modify-services (operating-system-user-services base)
+                      (guix-service-type
+                       config => (guix-configuration
+                                  (inherit config)
+                                  (privileged? #f)))))))
+               #:imported-modules '((gnu services herd)
+                                    (guix combinators)))))
+      (run-guix-daemon-test os "guix-daemon-unprivileged-test")))))
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Thu, 17 Apr 2025 14:25:09 GMT) Full text and rfc822 format available.

Message #62 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
Cc: Julien Lepiller <julien <at> lepiller.eu>, 77288 <at> debbugs.gnu.org,
 Reepca Russelstein <reepca <at> russelstein.xyz>
Subject: Re: [bug#77288] [PATCH 6/6] DRAFT news: Add entry about
 unprivileged guix-daemon on Guix System.
Date: Thu, 17 Apr 2025 16:24:20 +0200
Hi Florian,

"pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de> writes:

>> This news item is specifically about Guix System (announced upfront),
>> but I guess we can add a sentence toward the end.  (Thing is, the
>> situation will be simpler on foreign distros: we won’t support switching
>> between privileged and unprivileged, so either you get one or the
>> other.)
>>
>
> Yes, please!  That sentence in parentheses explains the situation, but
> needs rewording for etc/news.scm of course.

In the meantime some asked for instructions on how to migrate to the
unprivileged daemon on foreign distros, so I documented it, but that led
me to take a detour to ensure that ‘guix pull’ would install systemd
‘.service’ files.

This is all in v2, which I’ve just sent.

Ludo’.




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Fri, 18 Apr 2025 13:51:06 GMT) Full text and rfc822 format available.

Message #65 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: Julien Lepiller <julien <at> lepiller.eu>, 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 8/8] DRAFT news: Add entry about
 unprivileged guix-daemon on Guix System.
Date: Fri, 18 Apr 2025 15:32:50 +0200
[Message part 1 (text/plain, inline)]
Thank you Ludo for writing “Migrating to the Unprivileged Daemon”.
I have not tested on a foreign distro yet, though.

I try on Guix System the (privileged? #f) and get an error

florian <at> florianhp ~/src/guix$ sudo guix system reconfigure /etc/config.scm --allow-downgrades
guix system: error: the group `guixbuild' specified in `build-users-group' does not exist

It may have been that there were messages before like


The following derivation will be built:
  /gnu/store/w2bx5x6ms3drrcpyysc2jj5lzyjnxyf0-grub.cfg.drv

I temporarily added guixbuild with groupadd, but

substitute: looking for substitutes on 'https://substitutes.nonguix.org'... 100.0%
substitute: looking for substitutes on 'https://bordeaux.guix.gnu.org'... 100.0%
substitute: looking for substitutes on 'https://ci.guix.gnu.org'... 100.0%
The following derivations will be built:
  /gnu/store/s2xpbc0qy7nkwfca3cjy15ccbinz3lis-provenance.drv
  /gnu/store/sl57i03xygqc87q4853n6g3mmys066lm-system.drv
  /gnu/store/awwrr72s7z43nvrfp74wgqihr5qsa272-grub.cfg.drv

guix system: error: the group `guixbuild' specified in `build-users-group' does not exist


So the old daemon is still running and needs to build derivations, but its 
build-group is already gone?  I roll back for now.

Anyway.  Could you add this German translation?

[german-news-rootless.scm (text/plain, inline)]
 (entry (commit "XXX")
        (title
         (en "Guix System can run @command{guix-daemon} without root
privileges")
         (de "Guix System kann @command{guix-daemon} ohne root-Berechtigungen
ausführen"))
        (body
         (en "On Guix System, @code{guix-service-type} can now be configured
to run the build daemon, @command{guix-daemon}, without root privileges.  In
that configuration, the daemon runs with the authority of the
@code{guix-daemon} user, which we think can reduce the impact of some classes
of vulnerabilities that could affect it.

For now, this is opt-in: you have to change @code{guix-configuration} to set
the @code{privileged?} field to @code{#f}.  When you do this, all the files in
@file{/gnu/store}, @file{/var/guix}, etc. will have their ownership changed to
the @code{guix-daemon} user (instead of @code{root}); this can take a while,
especially if the store is big.  To learn more about it, run:

@example
info guix --index-search=guix-service-type
@end example

Running @command{guix-daemon} without root privileges will likely become the
default in the future.

Users of Guix on other distributions can find information on how to migrate in
the manual:

@example
info guix --index-search=migration
@end example")
         (de "Auf Guix System kann @code{guix-service-type} jetzt so
konfiguriert werden, dass der Erstellungs-Daemon @command{guix-daemon} ohne
root-Berechtigungen ausgeführt wird.  In dieser Konfiguration läuft der Daemon
mit den Berechtigungen des Benutzers @code{guix-daemon}, wovon wir glauben,
dass es die Auswirkungen mancher Schwachstellen-Kategorien verringert, die ihn
betreffen könnten.

Fürs Erste bleibt es Ihnen überlassen: Sie müssen @code{guix-configuration}
anpassen und dort das Feld @code{privileged?} auf @code{#f} setzen.  Wenn Sie
das tun, wird der Besitzer aller Dateien in @file{/gnu/store},
@file{/var/guix}, usw. auf den Benutzer @code{guix-daemon} geändert (anstelle
von @code{root}); das kann eine Weile dauern, besonders wenn der Store groß
ist.  Um mehr zu erfahren, führen Sie aus:

@example
info guix --index-search=guix-service-type
@end example

Schließlich wird das Ausführen von @command{guix-daemon} ohne
root-Berechtigungen wahrscheinlich die Vorgabe.

Wer Guix auf anderen Distributionen benutzt, kann sich mit dem Handbuch
informieren, wie man umsteigt:

@example
info guix --index-search=migration
@end example")))
[Message part 3 (text/plain, inline)]

Regards,
Florian

Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Fri, 18 Apr 2025 17:06:03 GMT) Full text and rfc822 format available.

Message #68 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
Cc: Julien Lepiller <julien <at> lepiller.eu>, 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 8/8] DRAFT news: Add entry about
 unprivileged guix-daemon on Guix System.
Date: Fri, 18 Apr 2025 19:04:35 +0200
Hello Florian,

"pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de> writes:

> I try on Guix System the (privileged? #f) and get an error
>
> florian <at> florianhp ~/src/guix$ sudo guix system reconfigure /etc/config.scm --allow-downgrades
> guix system: error: the group `guixbuild' specified in `build-users-group' does not exist
>
> It may have been that there were messages before like
>
>
> The following derivation will be built:
>   /gnu/store/w2bx5x6ms3drrcpyysc2jj5lzyjnxyf0-grub.cfg.drv
>
> I temporarily added guixbuild with groupadd, but
>
> substitute: looking for substitutes on 'https://substitutes.nonguix.org'... 100.0%
> substitute: looking for substitutes on 'https://bordeaux.guix.gnu.org'... 100.0%
> substitute: looking for substitutes on 'https://ci.guix.gnu.org'... 100.0%
> The following derivations will be built:
>   /gnu/store/s2xpbc0qy7nkwfca3cjy15ccbinz3lis-provenance.drv
>   /gnu/store/sl57i03xygqc87q4853n6g3mmys066lm-system.drv
>   /gnu/store/awwrr72s7z43nvrfp74wgqihr5qsa272-grub.cfg.drv
>
> guix system: error: the group `guixbuild' specified in `build-users-group' does not exist

That’s actually a message from guix-daemon (from ‘build.cc’).

Oh, I see where this is coming from: when running ‘guix system
reconfigure’, the activation snippet creating accounts and groups
immediately runs, thereby deleting ‘guixbuild’ and all the build users.

But at that point, we’re still running the privileged daemon.  So when
attempting a derivation after that, like ‘provenance.drv’ above, it
errors out because the build group and accounts are gone.

Problem is that this happens before the new generation has been added to
‘grub.cfg’.  So if you reboot, you’ll reboot into the previous
generation.

The safest way to work around that is to keep those accounts/groups
unconditionally.  It’s less pleasant to the eye, but it doesn’t hurt.
I guess I’ll have to send v3!

> Anyway.  Could you add this German translation?

Will do.

Thanks for testing & for updating the translation!

Ludo’.




Information forwarded to guix <at> cbaines.net, dev <at> jpoiret.xyz, ludo <at> gnu.org, othacehe <at> gnu.org, zimon.toutoune <at> gmail.com, me <at> tobias.gr, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Fri, 18 Apr 2025 19:48:03 GMT) Full text and rfc822 format available.

Message #71 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v3 1/8] self: Install systemd ‘.service’ files.
Date: Fri, 18 Apr 2025 21:46:46 +0200
This is consistent with the ‘guix’ package and will prove helpful when
people want to replace /etc/systemd/system/guix*.service with newer
versions thereof.

* guix/self.scm (parameterized-file): New procedure, based on…
(selinux-policy): … this. Use ‘parameterized-file’.
(systemd-file): New procedure.
(miscellaneous-files): Add systemd files.

Change-Id: Ia489a955347cf648a86000cc1265769d66c3f0e8
---
 guix/self.scm | 42 ++++++++++++++++++++++++++++++++----------
 1 file changed, 32 insertions(+), 10 deletions(-)

diff --git a/guix/self.scm b/guix/self.scm
index 28239d53f5..2a99765359 100644
--- a/guix/self.scm
+++ b/guix/self.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2017-2023 Ludovic Courtès <ludo <at> gnu.org>
+;;; Copyright © 2017-2023, 2025 Ludovic Courtès <ludo <at> gnu.org>
 ;;; Copyright © 2020 Martin Becze <mjbecze <at> riseup.net>
 ;;; Copyright © 2023 Janneke Nieuwenhuizen <janneke <at> gnu.org>
 ;;; Copyright © 2024 gemmaro <gemmaro.dev <at> gmail.com>
@@ -666,24 +666,40 @@ (define* (guix-command modules
                 ;; Use a 'guile' variant that doesn't complain about locales.
                 #:guile (quiet-guile guile)))
 
-(define (selinux-policy source daemon)
-  "Return the SELinux policy file taken from SOURCE and adjusted to refer to
-DAEMON and to the current configuration variables."
+(define (parameterized-file source daemon file name)
+  "Return FILE taken from SOURCE (typically a '.in' file) and adjusted to
+refer to DAEMON and to the current configuration variables."
   (define build
     (with-imported-modules '((guix build utils))
       #~(begin
           (use-modules (guix build utils))
 
-          (copy-file #+(file-append* source "/etc/guix-daemon.cil.in")
-                     "guix-daemon.cil")
-          (substitute* "guix-daemon.cil"
+          (fluid-set! %default-port-encoding "UTF-8")
+          (copy-file #+(file-append* source file) #$name)
+          (substitute* #$name
             (("@guix_sysconfdir@") #$%sysconfdir)
             (("@guix_localstatedir@") #$%localstatedir)
+            (("@localstatedir@") #$%localstatedir)
             (("@storedir@") #$%storedir)
-            (("@prefix@") #$daemon))
-          (copy-file "guix-daemon.cil" #$output))))
+            (("@prefix@") #$daemon)
+            (("@GUIX_SUBSTITUTE_URLS@")
+             #$(string-join %default-substitute-urls)))
+          (copy-file #$name #$output))))
 
-  (computed-file "guix-daemon.cil" build))
+  (computed-file name build))
+
+(define (selinux-policy source daemon)
+  "Return the SELinux policy file taken from SOURCE and adjusted to refer to
+DAEMON and to the current configuration variables."
+  (parameterized-file source daemon
+                      "etc/guix-daemon.cil.in"
+                      "guix-daemon.cil"))
+
+(define (systemd-file source daemon file)
+  "Return the given systemd file from SOURCE parameterized for DAEMON."
+  (parameterized-file source daemon
+                      (string-append "etc/" file ".in")
+                      file))
 
 (define (miscellaneous-files source daemon)
   "Return data files taken from SOURCE."
@@ -698,6 +714,12 @@ (define (miscellaneous-files source daemon)
                    ,(file-append* source "/etc/completion/fish/guix.fish"))
                   ("share/selinux/guix-daemon.cil"
                    ,(selinux-policy source daemon))
+                  ,@(map (lambda (file)
+                           `(,(string-append "lib/systemd/system/" file)
+                             ,(systemd-file source daemon file)))
+                         '("guix-gc.service"
+                           "guix-publish.service"
+                           "guix-daemon.service"))
                   ("share/guix/berlin.guix.gnu.org.pub"
                    ,(file-append* source
                                   "/etc/substitutes/berlin.guix.gnu.org.pub"))
-- 
2.49.0





Information forwarded to pelzflorian <at> pelzflorian.de, julien <at> lepiller.eu, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Fri, 18 Apr 2025 19:48:03 GMT) Full text and rfc822 format available.

Message #74 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v3 0/8] Rootless guix-daemon on Guix System
Date: Fri, 18 Apr 2025 21:46:45 +0200
Changes since v2:

  • ‘guix-service-type’ produces the “guixbuild” group and the
    build user accounts whether or not ‘privileged?’ is true so
    that ‘guix system reconfigure’ can run to completion during
    the privileged-to-unprivileged migration.

  • News entry includes German translation (by Florian) and
    French translation.

Ludo’.

Ludovic Courtès (8):
  self: Install systemd ‘.service’ files.
  doc: Document migration to the unprivileged daemon.
  syscalls: Add ‘unshare’.
  services: account: Create /var/guix/profiles/per-user/$USER.
  tests: guix-daemon: Send system log output to /dev/console.
  tests: guix-daemon: Wait for the ‘guix-daemon’ service to be up.
  services: guix: Allow ‘guix-daemon’ to run without root privileges.
  DRAFT news: Add entry about unprivileged guix-daemon on Guix System.

 doc/guix.texi           | 106 +++++++++++++++++++++-
 etc/news.scm            |  88 ++++++++++++++++++
 gnu/services/base.scm   | 192 ++++++++++++++++++++++++++++++++++++----
 gnu/system/shadow.scm   |  19 +++-
 gnu/tests/base.scm      |  60 +++++++++++--
 guix/build/syscalls.scm |  18 ++++
 guix/self.scm           |  42 ++++++---
 tests/syscalls.scm      |   9 ++
 8 files changed, 501 insertions(+), 33 deletions(-)


base-commit: d14663b94a7428eccbfa27aa620dc3d8ba67d752
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Fri, 18 Apr 2025 19:48:04 GMT) Full text and rfc822 format available.

Message #77 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v3 3/8] syscalls: Add ‘unshare’.
Date: Fri, 18 Apr 2025 21:46:48 +0200
* guix/build/syscalls.scm (unshare): New procedure.

Change-Id: I344273b8bdeaa9366334e6e20ee7efc37eb6c8f7
---
 guix/build/syscalls.scm | 18 ++++++++++++++++++
 tests/syscalls.scm      |  9 +++++++++
 2 files changed, 27 insertions(+)

diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm
index 42232fc7f1..cf09cae3a4 100644
--- a/guix/build/syscalls.scm
+++ b/guix/build/syscalls.scm
@@ -145,6 +145,7 @@ (define-module (guix build syscalls)
             CLONE_NEWPID
             CLONE_NEWNET
             clone
+            unshare
             setns
 
             kexec-load-file
@@ -1213,6 +1214,23 @@ (define clone
                    (list err))
             ret)))))
 
+(define unshare
+  (let ((proc (syscall->procedure int "unshare" (list int))))
+    (lambda (flags)
+      "Disassociate the current process from parts of its execution context
+according to FLAGS, which must be a logical or of CLONE_NEW* constants.
+
+Note that CLONE_NEWUSER requires that the calling process be single-threaded,
+which is possible if and only if libgc is running a single marker thread; this
+can be achieved by setting the GC_MARKERS environment variable to 1.  If the
+calling process is multi-threaded, this throws to 'system-error' with EINVAL."
+      (let-values (((ret err)
+                    (without-automatic-finalization (proc flags))))
+        (unless (zero? ret)
+          (throw 'system-error "unshare" "~a: ~A"
+                 (list flags (strerror err))
+                 (list err)))))))
+
 (define setns
   ;; Some systems may be using an old (pre-2.14) version of glibc where there
   ;; is no 'setns' function available.
diff --git a/tests/syscalls.scm b/tests/syscalls.scm
index d2848879d7..879c3e4f25 100644
--- a/tests/syscalls.scm
+++ b/tests/syscalls.scm
@@ -149,6 +149,15 @@ (define perform-container-tests?
             ((_ . status)
              (= 42 (status:exit-val status))))))))
 
+(test-equal "unshare"
+  EPERM
+  ;; Unless running as root, (unshare CLONE_NEWNS) returns EPERM.
+  (catch 'system-error
+    (lambda ()
+      (unshare CLONE_NEWNS))
+    (lambda args
+      (system-error-errno args))))
+
 (unless perform-container-tests?
   (test-skip 1))
 (test-assert "setns"
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Fri, 18 Apr 2025 19:48:05 GMT) Full text and rfc822 format available.

Message #80 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v3 4/8] services: account: Create
 /var/guix/profiles/per-user/$USER.
Date: Fri, 18 Apr 2025 21:46:49 +0200
* gnu/system/shadow.scm (account-shepherd-service): Create
/var/guix/profiles/per-user/$USER in ‘user-homes’ service.

Change-Id: I22e66e8a34d63686df9bae64c68df65c8889e72a
---
 gnu/system/shadow.scm | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/gnu/system/shadow.scm b/gnu/system/shadow.scm
index b68a818871..d0f1b6b2b1 100644
--- a/gnu/system/shadow.scm
+++ b/gnu/system/shadow.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2013-2020, 2022, 2023 Ludovic Courtès <ludo <at> gnu.org>
+;;; Copyright © 2013-2020, 2022-2023, 2025 Ludovic Courtès <ludo <at> gnu.org>
 ;;; Copyright © 2016 Alex Griffin <a <at> ajgrf.com>
 ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke <at> gnu.org>
 ;;; Copyright © 2020, 2023 Efraim Flashner <efraim <at> flashner.co.il>
@@ -460,6 +460,12 @@ (define (account-shepherd-service accounts+groups)
   (define accounts
     (filter user-account? accounts+groups))
 
+  (define regular-account-names
+    (filter-map (lambda (account)
+                  (and (not (user-account-system? account))
+                       (user-account-name account)))
+                accounts))
+
   ;; Create home directories only once 'file-systems' is up.  This makes sure
   ;; they are created in the right place if /home lives on a separate
   ;; partition.
@@ -480,6 +486,17 @@ (define (account-shepherd-service accounts+groups)
                       (activate-user-home
                        (map sexp->user-account
                             (list #$@(map user-account->gexp accounts))))
+
+                      ;; Create the user's profile directory upfront:
+                      ;; guix-daemon lacks permissions to create it when it is
+                      ;; running as an unprivileged user.
+                      (for-each (lambda (account)
+                                  (let ((profile (in-vicinity
+                                                  "/var/guix/profiles/per-user"
+                                                  account))
+                                        (owner (getpwnam account)))
+                                    (mkdir-p/perms profile owner #o755)))
+                                '#$regular-account-names)
                       #t)))                       ;success
          (documentation "Create user home directories."))))
 
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Fri, 18 Apr 2025 19:48:06 GMT) Full text and rfc822 format available.

Message #83 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v3 5/8] tests: guix-daemon: Send system log output to
 /dev/console.
Date: Fri, 18 Apr 2025 21:46:50 +0200
* gnu/tests/base.scm (%daemon-os): New variable.
(%test-guix-daemon): Use it.

Change-Id: Iea31808cc59e94971ea4cbc12d565c94348bf7a4
---
 gnu/tests/base.scm | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm
index a7f8a5bf7c..0f7fb543a7 100644
--- a/gnu/tests/base.scm
+++ b/gnu/tests/base.scm
@@ -994,6 +994,10 @@ (define %test-activation
 ;;; Build daemon.
 ;;;
 
+(define %daemon-os
+  (operating-system-with-console-syslog
+   (simple-operating-system)))
+
 (define (manifest-entry-without-grafts entry)
   "Return ENTRY with grafts disabled on its contents."
   (manifest-entry
@@ -1168,7 +1172,7 @@ (define %test-guix-daemon
     (let ((os (marionette-operating-system
                (operating-system
                  (inherit (operating-system-with-gc-roots
-                           %simple-os
+                           %daemon-os
                            (list (profile
                                   (name "hello-build-dependencies")
                                   (content %hello-dependencies-manifest)))))
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Fri, 18 Apr 2025 19:48:07 GMT) Full text and rfc822 format available.

Message #86 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v3 6/8] tests: guix-daemon: Wait for the ‘guix-daemon’ service to be up.
Date: Fri, 18 Apr 2025 21:46:51 +0200
* gnu/tests/base.scm (run-guix-daemon-test): Add “guix-daemon service is
up” test.

Change-Id: I4d44a1248599fec45c854c285d4da201c30eb00c
---
 gnu/tests/base.scm | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm
index 0f7fb543a7..83e047f7e6 100644
--- a/gnu/tests/base.scm
+++ b/gnu/tests/base.scm
@@ -1157,6 +1157,13 @@ (define (run-guix-daemon-test os)
           (test-runner-current (system-test-runner #$output))
           (test-begin "guix-daemon")
 
+          (test-assert "guix-service is running"
+            ;; Wait for 'guix-daemon' to be up.
+            (marionette-eval '(begin
+                                (use-modules (gnu services herd))
+                                (start-service 'guix-daemon))
+                             marionette))
+
           #$(guix-daemon-test-cases #~marionette)
 
           (test-end))))
-- 
2.49.0





Information forwarded to ludo <at> gnu.org, maxim.cournoyer <at> gmail.com, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Fri, 18 Apr 2025 19:48:11 GMT) Full text and rfc822 format available.

Message #89 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v3 2/8] doc: Document migration to the unprivileged daemon.
Date: Fri, 18 Apr 2025 21:46:47 +0200
* doc/guix.texi (Build Environment Setup): Add “Migrating to the
Unprivileged Daemon” section.
(Upgrading Guix): Link to it.

Change-Id: I2bac3f4419d85b7c718c6c4a3908387b4f6ee582
---
 doc/guix.texi | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 67 insertions(+), 1 deletion(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 070528667f..377cb65326 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -1026,13 +1026,75 @@ Build Environment Setup
 In this configuration, @file{/gnu/store} is owned by the
 @code{guix-daemon} user.
 
+@anchor{unprivileged-daemon-migration}
+@unnumberedsubsubsec Migrating to the Unprivileged Daemon
+
+@cindex unprivileged daemon, migration
+@cindex rootless daemon, migration
+To switch an existing installation to the unprivileged execution mode, a
+number of steps must be taken: creating a new dedicated
+@code{guix-daemon} user account, changing ownership of the relevant
+files to @code{guix-daemon}, and ensuring that the @command{guix-daemon}
+program runs as @code{guix-daemon}.
+
+@quotation Warning
+Follow the instructions below only after making sure you have a recent
+version of @command{guix-daemon} with support for unprivileged
+execution.
+@end quotation
+
+File ownership can be changed, after stopping the daemon, by running the
+following commands as root (the @command{chown} can take a while if
+there are many files in @file{/gnu/store}):
+
+@example
+groupadd --system guix-daemon
+useradd -g guix-daemon -G guix-daemon,kvm               \
+        -d /var/empty -s $(which nologin)               \
+        -c "Guix daemon privilege separation user"      \
+        --system guix-daemon
+
+chown -R guix-daemon:guix-daemon                        \
+  /gnu                                                  \
+  /var/guix/@{daemon-socket,db,discover@}                 \
+  /var/guix/@{gcroots,offload,substitute,temproots@}      \
+  /var/log/guix                                         \
+  /etc/guix
+@end example
+
+If your system uses the systemd service manager, running the daemon as
+@code{guix-daemon} will be a matter of copying the relevant
+configuration files---make sure to review any changes you might have
+made in your own @file{.service} files before overwriting them:
+
+@example
+cp /var/guix/profiles/per-user/root/current-guix/lib/systemd/system/*.service \
+   /etc/systemd/system
+systemctl daemon-reload
+systemctl start guix-daemon
+@end example
+
+@quotation Warning
+The commands above assume that @command{guix pull} was run for the root
+user.  You can check whether this is the case by running this command:
+
+@example
+grep User=guix-daemon \
+  /var/guix/profiles/per-user/root/current-guix/lib/systemd/system/guix-daemon.service
+@end example
+
+If that command does not show the @code{User=guix-daemon} line, then run
+@command{guix pull} as the root user.
+@end quotation
+
 @unnumberedsubsubsec The Isolated Build Environment
 
 @cindex chroot
 @cindex build environment isolation
 @cindex isolated build environment
 @cindex hermetic build environment
-In both cases, the daemon starts build processes without privileges in
+In both cases, privileged and unprivileged,
+the daemon starts build processes without privileges in
 an @emph{isolated} or @emph{hermetic} build environment---a ``chroot''.
 On GNU/Linux, by default, the build environment contains nothing but:
 
@@ -2035,6 +2097,10 @@ Upgrading Guix
 On Guix System, upgrading the daemon is achieved by reconfiguring the
 system (@pxref{Invoking guix system, @code{guix system reconfigure}}).
 
+To migrate an existing installation to the @emph{unprivileged daemon}
+where @command{guix-daemon} does not run as root,
+@pxref{unprivileged-daemon-migration}.
+
 @c TODO What else?
 
 @c *********************************************************************
-- 
2.49.0





Information forwarded to pelzflorian <at> pelzflorian.de, julien <at> lepiller.eu, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Fri, 18 Apr 2025 19:48:15 GMT) Full text and rfc822 format available.

Message #92 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>,
 Florian Pelz <pelzflorian <at> pelzflorian.de>
Subject: [PATCH v3 8/8] DRAFT news: Add entry about unprivileged guix-daemon
 on Guix System.
Date: Fri, 18 Apr 2025 21:46:53 +0200
DRAFT: Temporary commit.

* etc/news.scm: Add it.

Change-Id: I28eae7f7b4305225b13281b99458cbedda3c3b94
Co-authored-by: Florian Pelz <pelzflorian <at> pelzflorian.de>
---
 etc/news.scm | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)

diff --git a/etc/news.scm b/etc/news.scm
index 4b3da44540..fcac283636 100644
--- a/etc/news.scm
+++ b/etc/news.scm
@@ -37,6 +37,94 @@
 (channel-news
  (version 0)
 
+ (entry (commit "XXX")
+        (title
+         (en "Guix System can run @command{guix-daemon} without root
+privileges")
+         (de "Guix System kann @command{guix-daemon} ohne root-Berechtigungen
+ausführen")
+         (fr "Guix System peut faire tourner @command{guix-daemon} sans
+privilèges"))
+        (body
+         (en "On Guix System, @code{guix-service-type} can now be configured
+to run the build daemon, @command{guix-daemon}, without root privileges.  In
+that configuration, the daemon runs with the authority of the
+@code{guix-daemon} user, which we think can reduce the impact of some classes
+of vulnerabilities that could affect it.
+
+For now, this is opt-in: you have to change @code{guix-configuration} to set
+the @code{privileged?} field to @code{#f}.  When you do this, all the files in
+@file{/gnu/store}, @file{/var/guix}, etc. will have their ownership changed to
+the @code{guix-daemon} user (instead of @code{root}); this can take a while,
+especially if the store is big.  To learn more about it, run:
+
+@example
+info guix --index-search=guix-service-type
+@end example
+
+Running @command{guix-daemon} without root privileges will likely become the
+default in the future.
+
+Users of Guix on other distributions can find information on how to migrate in
+the manual:
+
+@example
+info guix --index-search=migration
+@end example")
+         (de "Auf Guix System kann @code{guix-service-type} jetzt so
+konfiguriert werden, dass der Erstellungs-Daemon @command{guix-daemon} ohne
+root-Berechtigungen ausgeführt wird.  In dieser Konfiguration läuft der Daemon
+mit den Berechtigungen des Benutzers @code{guix-daemon}, wovon wir glauben,
+dass es die Auswirkungen mancher Schwachstellen-Kategorien verringert, die ihn
+betreffen könnten.
+
+Fürs Erste bleibt es Ihnen überlassen: Sie müssen @code{guix-configuration}
+anpassen und dort das Feld @code{privileged?} auf @code{#f} setzen.  Wenn Sie
+das tun, wird der Besitzer aller Dateien in @file{/gnu/store},
+@file{/var/guix}, usw. auf den Benutzer @code{guix-daemon} geändert (anstelle
+von @code{root}); das kann eine Weile dauern, besonders wenn der Store groß
+ist.  Um mehr zu erfahren, führen Sie aus:
+
+@example
+info guix --index-search=guix-service-type
+@end example
+
+Schließlich wird das Ausführen von @command{guix-daemon} ohne
+root-Berechtigungen wahrscheinlich die Vorgabe.
+
+Wer Guix auf anderen Distributionen benutzt, kann sich mit dem Handbuch
+informieren, wie man umsteigt:
+
+@example
+info guix --index-search=migration
+@end example")
+         (fr "Sur Guix System, @code{guix-service-type} peut maintenant être
+configuré pour faire tourner le démon de compilation, @command{guix-daemon},
+sans privilèges ``root''.  Dans cette configuration, le démon s'exécute avec
+l'autorité du compte @code{guix-daemon}, ce qui selon nous réduit l'impact de
+certaines classes de vulnérabilités qui pourraient l'affecter.
+
+Pour le moment, c'est à activer explicitement : il faut changer
+@code{guix-configuration} pour mettre le champ @code{privileged?} à @code{#f}.
+Tous les fichiers de @file{/gnu/store}, @file{/var/guix}, etc. voient alors
+leur propriétaire changé pour @code{guix-daemon} (au lieu de @code{root}) ;
+cette opération peut prendre un moment, particulièrement si le dépôt est gros.
+Pour en savoir plus, lancer :
+
+@example
+info guix --index-search=guix-service-type
+@end example
+
+L'exécution de @command{guix-daemon} sans privilèges se fera probablement par
+défaut à l'avenir.
+
+Pour l'utilisation de Guix sur d'autres distributions, des informations sur
+comment migrer se trouver dans le manuel :
+
+@example
+info guix --index-search=migration
+@end example")))
+
  (entry (commit "0e51c6547ffdaf91777f7383da4a52a1a07b7286")
         (title
          (en "Incompatible upgrade of the Syncthing service"))
-- 
2.49.0





Information forwarded to ludo <at> gnu.org, maxim.cournoyer <at> gmail.com, guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Fri, 18 Apr 2025 19:49:05 GMT) Full text and rfc822 format available.

Message #95 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288 <at> debbugs.gnu.org
Cc: Ludovic Courtès <ludo <at> gnu.org>
Subject: [PATCH v3 7/8] services: guix: Allow ‘guix-daemon’ to run without root privileges.
Date: Fri, 18 Apr 2025 21:46:52 +0200
* gnu/services/base.scm (run-with-writable-store)
(guix-ownership-change-program): New procedures.
(<guix-configuration>)[privileged?]: New field.
(guix-shepherd-service): Rename to…
(guix-shepherd-services): … this.   Add the ‘guix-ownership’ service.
Change ‘guix-daemon’ service to depend on it; when unprivileged,
prefix ‘daemon-command’ by ‘run-with-writable-store’ and
omit ‘--build-users-group’; adjust socket activation endpoints.
(guix-accounts): When unprivileged, create the “guix-daemon” user and
group in addition to the others.
(guix-service-type)[extensions]: Adjust to name change.
* gnu/tests/base.scm (run-guix-daemon-test): Add ‘name’ parameter.
(%test-guix-daemon): Adjust accordingly.
(%test-guix-daemon-unprivileged): New test.
* doc/guix.texi (Base Services): Document ‘privileged?’.
(Migrating to the Unprivileged Daemon): Explain that this is automatic
on Guix System.

Change-Id: I28a9a22e617416c551dccb24e43a253b544ba163
---
 doc/guix.texi         |  38 +++++++++
 gnu/services/base.scm | 192 ++++++++++++++++++++++++++++++++++++++----
 gnu/tests/base.scm    |  47 +++++++++--
 3 files changed, 257 insertions(+), 20 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 377cb65326..8243bd0547 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -1037,6 +1037,14 @@ Build Environment Setup
 files to @code{guix-daemon}, and ensuring that the @command{guix-daemon}
 program runs as @code{guix-daemon}.
 
+On Guix System, these steps are carried out automatically when you set
+the @code{privileged?} field of the @code{guix-configuration} record to
+@code{#f} and reconfigure (@pxref{guix-configuration-type,
+@code{guix-configuration}}).
+
+However, on a foreign distribution, the process is manual.  The
+following paragraphs describe what you need to do.
+
 @quotation Warning
 Follow the instructions below only after making sure you have a recent
 version of @command{guix-daemon} with support for unprivileged
@@ -20105,6 +20113,36 @@ Base Services
 The Guix package to use.  @xref{Customizing the System-Wide Guix} to
 learn how to provide a package with a pre-configured set of channels.
 
+@cindex unprivileged @command{guix-daemon}
+@cindex rootless @command{guix-daemon}
+@item @code{privileged?} (default: @code{#t})
+Whether to run @command{guix-daemon} as root.
+
+When true, @command{guix-daemon} runs with root privileges and build
+processes run under unprivileged user accounts as specified by
+@code{build-group} and @code{build-accounts} (see below); when false,
+@command{guix-daemon} run as the @code{guix-daemon} user, which is
+unprivileged, and so do build processes.  The unprivileged or
+``rootless'' mode can reduce the impact of some classes of
+vulnerabilities that could affect the daemon.
+
+The default is currently @code{#t} (@command{guix-daemon} runs with root
+privileges) but may eventually be changed to @code{#f}.
+
+@quotation Warning
+When changing this option, @file{/gnu/store}, @file{/var/guix}, and
+@file{/etc/guix} have their ownership automatically changed by the
+@code{guix-ownership} service to either the @code{guix-daemon} user or
+the @code{root} user (@pxref{unprivileged-daemon-migration}).
+
+This can take a while, especially if @file{/gnu/store} is big; it cannot
+be interrupted and @command{guix-daemon} cannot be used until it has
+completed.
+@end quotation
+
+@xref{Build Environment Setup}, for more information on the two ways to
+run @command{guix-daemon}.
+
 @item @code{build-group} (default: @code{"guixbuild"})
 Name of the group for build user accounts.
 
diff --git a/gnu/services/base.scm b/gnu/services/base.scm
index 490376d446..df44e88b2e 100644
--- a/gnu/services/base.scm
+++ b/gnu/services/base.scm
@@ -1918,6 +1918,100 @@ (define (guix-machines-files-installation machines)
                                    #$machines))
                  machines-file))))
 
+(define (run-with-writable-store)
+  "Return a wrapper that runs the given command under the specified UID and
+GID in a context where the store is writable, even if it was bind-mounted
+read-only via %IMMUTABLE-STORE (this wrapper must run as root)."
+  (program-file "run-with-writable-store"
+                (with-imported-modules (source-module-closure
+                                        '((guix build syscalls)))
+                  #~(begin
+                      (use-modules (guix build syscalls)
+                                   (ice-9 match))
+
+                      (define (ensure-writable-store store)
+                        ;; Create a new mount namespace and remount STORE with
+                        ;; write permissions if it's read-only.
+                        (unshare CLONE_NEWNS)
+                        (let ((fs (statfs store)))
+                          (unless (zero? (logand (file-system-mount-flags fs)
+                                                 ST_RDONLY))
+                            (mount store store "none"
+                                   (logior MS_BIND MS_REMOUNT)))))
+
+                      (match (command-line)
+                        ((_ user group command args ...)
+                         (ensure-writable-store #$(%store-prefix))
+                         (let ((uid (or (string->number user)
+                                        (passwd:uid (getpwnam user))))
+                               (gid (or (string->number group)
+                                        (group:gid (getgrnam group)))))
+                           (setgroups #())
+                           (setgid gid)
+                           (setuid uid)
+                           (apply execl command command args))))))))
+
+(define (guix-ownership-change-program)
+  "Return a program that changes ownership of the store and other data files
+of Guix to the given UID and GID."
+  (program-file "validate-guix-ownership"
+                (with-imported-modules (source-module-closure
+                                        '((guix build utils)))
+                  #~(begin
+                      (use-modules (guix build utils)
+                                   (ice-9 ftw)
+                                   (ice-9 match))
+
+                      (define (lchown file uid gid)
+                        (let ((parent (open (dirname file) O_DIRECTORY)))
+                          (chown-at parent (basename file) uid gid
+                                    AT_SYMLINK_NOFOLLOW)
+                          (close-port parent)))
+
+                      (define (change-ownership directory uid gid)
+                        ;; chown -R UID:GID DIRECTORY
+                        (file-system-fold (const #t)                 ;enter?
+                                          (lambda (file stat result) ;leaf
+                                            (if (eq? 'symlink (stat:type stat))
+                                                (lchown file uid gid)
+                                                (chown file uid gid)))
+                                          (const #t) ;down
+                                          (lambda (directory stat result) ;up
+                                            (chown directory uid gid))
+                                          (const #t) ;skip
+                                          (lambda (file stat errno result)
+                                            (format (current-error-port) "i/o error: ~a: ~a~%"
+                                                    file (strerror errno))
+                                            #f)
+                                          #t      ;seed
+                                          directory
+                                          lstat))
+
+                      (define (claim-data-ownership uid gid)
+                        (format #t "Changing file ownership for /gnu/store \
+and data directories to ~a:~a...~%"
+                                uid gid)
+                        (change-ownership #$(%store-prefix) uid gid)
+                        (let ((excluded '("." ".." "profiles" "userpool")))
+                          (for-each (lambda (directory)
+                                      (change-ownership (in-vicinity "/var/guix" directory)
+                                                        uid gid))
+                                    (scandir "/var/guix"
+                                             (lambda (file)
+                                               (not (member file
+                                                            excluded))))))
+                        (chown "/var/guix" uid gid)
+                        (change-ownership "/etc/guix" uid gid)
+                        (mkdir-p "/var/log/guix")
+                        (change-ownership "/var/log/guix" uid gid))
+
+                      (match (command-line)
+                        ((_ (= string->number (? integer? uid))
+                            (= string->number (? integer? gid)))
+                         (setlocale LC_ALL "C.UTF-8") ;for file name decoding
+                         (setvbuf (current-output-port) 'line)
+                         (claim-data-ownership uid gid)))))))
+
 (define-record-type* <guix-configuration>
   guix-configuration make-guix-configuration
   guix-configuration?
@@ -1959,6 +2053,8 @@ (define-record-type* <guix-configuration>
                     (default #f))
   (tmpdir           guix-tmpdir                   ;string | #f
                     (default #f))
+  (privileged?      guix-configuration-privileged?
+                    (default #t))
   (build-machines   guix-configuration-build-machines ;list of gexps | '()
                     (default '()))
   (environment      guix-configuration-environment  ;list of strings
@@ -2021,7 +2117,7 @@ (define shepherd-discover-action
                     (environ environment)
                     #t)))))
 
-(define (guix-shepherd-service config)
+(define (guix-shepherd-services config)
   "Return a <shepherd-service> for the Guix daemon service with CONFIG."
   (define locales
     (let-system (system target)
@@ -2030,16 +2126,57 @@ (define (guix-shepherd-service config)
           glibc-utf8-locales)))
 
   (match-record config <guix-configuration>
-    (guix build-group build-accounts chroot? authorize-key? authorized-keys
+    (guix privileged?
+          build-group build-accounts chroot? authorize-key? authorized-keys
           use-substitutes? substitute-urls max-silent-time timeout
           log-compression discover? extra-options log-file
           http-proxy tmpdir chroot-directories environment
           socket-directory-permissions socket-directory-group
           socket-directory-user)
     (list (shepherd-service
+           (provision '(guix-ownership))
+           (requirement '(user-processes user-homes))
+           (one-shot? #t)
+           (start #~(lambda ()
+                      (let* ((store #$(%store-prefix))
+                             (stat (lstat store))
+                             (privileged? #$(guix-configuration-privileged?
+                                             config))
+                             (change-ownership #$(guix-ownership-change-program))
+                             (with-writable-store #$(run-with-writable-store)))
+                        ;; Check whether we're switching from privileged to
+                        ;; unprivileged guix-daemon, or vice versa, and adjust
+                        ;; file ownership accordingly.  Spawn a child process
+                        ;; if and only if something needs to be changed.
+                        ;;
+                        ;; Note: This service remains in 'starting' state for
+                        ;; as long as CHANGE-OWNERSHIP is running.  That way,
+                        ;; 'guix-daemon' starts only once we're done.
+                        (cond ((and (not privileged?)
+                                    (or (zero? (stat:uid stat))
+                                        (zero? (stat:gid stat))))
+                               (let ((user (getpwnam "guix-daemon")))
+                                 (format #t "Changing to unprivileged guix-daemon.~%")
+                                 (zero?
+                                  (system* with-writable-store "0" "0"
+                                           change-ownership
+                                           (number->string (passwd:uid user))
+                                           (number->string (passwd:gid user))))))
+                              ((and privileged?
+                                    (and (not (zero? (stat:uid stat)))
+                                         (not (zero? (stat:gid stat)))))
+                               (format #t "Changing to privileged guix-daemon.~%")
+                               (zero? (system* with-writable-store "0" "0"
+                                               change-ownership "0" "0")))
+                              (else #t)))))
+           (documentation "Ensure that the store and other data files used by
+guix-daemon have the right ownership."))
+
+          (shepherd-service
            (documentation "Run the Guix daemon.")
            (provision '(guix-daemon))
            (requirement `(user-processes
+                          guix-ownership
                           ,@(if discover? '(avahi-daemon) '())))
            (actions (list shepherd-set-http-proxy-action
                           shepherd-discover-action))
@@ -2063,8 +2200,15 @@ (define (guix-shepherd-service config)
                     (or (getenv "discover") #$discover?))
 
                   (define daemon-command
-                    (cons* #$(file-append guix "/bin/guix-daemon")
-                           "--build-users-group" #$build-group
+                    (cons* #$@(if privileged?
+                                  #~()
+                                  #~(#$(run-with-writable-store)
+                                     "guix-daemon" "guix-daemon"))
+
+                           #$(file-append guix "/bin/guix-daemon")
+                           #$@(if privileged?
+                                  #~("--build-users-group" #$build-group)
+                                  #~())
                            "--max-silent-time"
                            #$(number->string max-silent-time)
                            "--timeout" #$(number->string timeout)
@@ -2145,9 +2289,11 @@ (define (guix-shepherd-service config)
                                      "/var/guix/daemon-socket/socket")
                                     #:name "socket"
                                     #:socket-owner
-                                    (or #$socket-directory-user 0)
+                                    (or #$socket-directory-user
+                                        #$(if privileged? 0 "guix-daemon"))
                                     #:socket-group
-                                    (or #$socket-directory-group 0)
+                                    (or #$socket-directory-group
+                                        #$(if privileged? 0 "guix-daemon"))
                                     #:socket-directory-permissions
                                     #$socket-directory-permissions)))
                        ((make-systemd-constructor daemon-command
@@ -2162,15 +2308,31 @@ (define (guix-shepherd-service config)
 
 (define (guix-accounts config)
   "Return the user accounts and user groups for CONFIG."
-  (cons (user-group
-         (name (guix-configuration-build-group config))
-         (system? #t)
+  `(,@(if (guix-configuration-privileged? config)
+          '()
+          (list (user-group (name "guix-daemon") (system? #t))
+                (user-account
+                 (name "guix-daemon")
+                 (group "guix-daemon")
+                 (system? #t)
+                 (supplementary-groups '("kvm"))
+                 (comment "Guix Daemon User")
+                 (home-directory "/var/empty")
+                 (shell (file-append shadow "/sbin/nologin")))))
 
-         ;; Use a fixed GID so that we can create the store with the right
-         ;; owner.
-         (id 30000))
-        (guix-build-accounts (guix-configuration-build-accounts config)
-                             #:group (guix-configuration-build-group config))))
+    ;; When reconfiguring from privileged to unprivileged, the running daemon
+    ;; (privileged) relies on the availability of the build accounts and build
+    ;; group until 'guix system reconfigure' has completed.  The simplest way
+    ;; to meet this requirement is to create these accounts unconditionally so
+    ;; they are not removed in the middle of the 'reconfigure' process.
+    ,(user-group
+      (name (guix-configuration-build-group config))
+      (system? #t)
+
+      ;; Use a fixed GID so that we can create the store with the right owner.
+      (id 30000))
+    ,@(guix-build-accounts (guix-configuration-build-accounts config)
+                           #:group (guix-configuration-build-group config))))
 
 (define (guix-activation config)
   "Return the activation gexp for CONFIG."
@@ -2228,7 +2390,7 @@ (define guix-service-type
   (service-type
    (name 'guix)
    (extensions
-    (list (service-extension shepherd-root-service-type guix-shepherd-service)
+    (list (service-extension shepherd-root-service-type guix-shepherd-services)
           (service-extension account-service-type guix-accounts)
           (service-extension activation-service-type guix-activation)
           (service-extension profile-service-type
diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm
index 83e047f7e6..12d4e70ee5 100644
--- a/gnu/tests/base.scm
+++ b/gnu/tests/base.scm
@@ -1,5 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2016-2020, 2022, 2024 Ludovic Courtès <ludo <at> gnu.org>
+;;; Copyright © 2016-2020, 2022, 2024-2025 Ludovic Courtès <ludo <at> gnu.org>
 ;;; Copyright © 2018 Clément Lassieur <clement <at> lassieur.org>
 ;;; Copyright © 2022 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
 ;;; Copyright © 2022 Marius Bakke <marius <at> gnu.org>
@@ -63,7 +63,8 @@ (define-module (gnu tests base)
 
             %hello-dependencies-manifest
             guix-daemon-test-cases
-            %test-guix-daemon))
+            %test-guix-daemon
+            %test-guix-daemon-unprivileged))
 
 (define %simple-os
   (simple-operating-system))
@@ -1121,7 +1122,7 @@ (define (guix-daemon-test-cases marionette)
                               (system-error-errno args)))
                          #$marionette))))
 
-(define (run-guix-daemon-test os)
+(define (run-guix-daemon-test os name)
   (define test-image
     (image (operating-system os)
            (format 'compressed-qcow2)
@@ -1161,6 +1162,12 @@ (define (run-guix-daemon-test os)
             ;; Wait for 'guix-daemon' to be up.
             (marionette-eval '(begin
                                 (use-modules (gnu services herd))
+                                (start-service 'guix-daemon)
+
+                                ;; XXX: Do it a second time to work around
+                                ;; <https://issues.guix.gnu.org/77274> and its
+                                ;; effect on the 'guix-ownership' service.
+                                ;; TODO: Remove when Shepherd 1.0.4 is out.
                                 (start-service 'guix-daemon))
                              marionette))
 
@@ -1168,7 +1175,7 @@ (define (run-guix-daemon-test os)
 
           (test-end))))
 
-  (gexp->derivation "guix-daemon-test" test))
+  (gexp->derivation name test))
 
 (define %test-guix-daemon
   (system-test
@@ -1190,4 +1197,34 @@ (define %test-guix-daemon
                               %base-user-accounts)))
                #:imported-modules '((gnu services herd)
                                     (guix combinators)))))
-      (run-guix-daemon-test os)))))
+      (run-guix-daemon-test os "guix-daemon-test")))))
+
+(define %test-guix-daemon-unprivileged
+  (system-test
+   (name "guix-daemon-unprivileged")
+   (description
+    "Test 'guix-daemon' behavior on a multi-user system, where 'guix-daemon'
+runs unprivileged.")
+   (value
+    (let ((os (marionette-operating-system
+               (let ((base (operating-system-with-gc-roots
+                            %daemon-os
+                            (list (profile
+                                   (name "hello-build-dependencies")
+                                   (content %hello-dependencies-manifest))))))
+                 (operating-system
+                   (inherit base)
+                   (kernel-arguments '("console=ttyS0"))
+                   (users (cons (user-account
+                                 (name "user")
+                                 (group "users"))
+                                %base-user-accounts))
+                   (services
+                    (modify-services (operating-system-user-services base)
+                      (guix-service-type
+                       config => (guix-configuration
+                                  (inherit config)
+                                  (privileged? #f)))))))
+               #:imported-modules '((gnu services herd)
+                                    (guix combinators)))))
+      (run-guix-daemon-test os "guix-daemon-unprivileged-test")))))
-- 
2.49.0





Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Sat, 19 Apr 2025 00:21:03 GMT) Full text and rfc822 format available.

Message #98 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: 77288 <at> debbugs.gnu.org, Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Subject: Re: [bug#77288] [PATCH v3 2/8] doc: Document migration to the
 unprivileged daemon.
Date: Sat, 19 Apr 2025 02:21:03 +0200
Works great now.  I can now with v3 merrily switch back and forth the
(privileged? #f) on Guix System, except ownerships of /gnu/store/ files
are funny when I guix system roll-back to the time before guix-ownership
services existed.

Then I try the

     chown -R root:root                        \
       /gnu                                                  \
       /var/guix/{daemon-socket,db,discover}                 \
       /var/guix/{gcroots,offload,substitute,temproots}      \
       /var/log/guix                                         \
       /etc/guix

from this “Migrating to the Unprivileged Daemon” with root:root instead
of guix-daemon:guix-daemon.  But it does not work unless I mount -o
remount,rw /gnu/store (a command from the subsequent SELinux section).

While I have not tested this “Migrating to the Unprivileged Daemon”
section on a foreign distro, should not etc/gnu-store.mount require
remounting on foreign distros, too?

Regards,
Florian




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Sat, 19 Apr 2025 09:33:02 GMT) Full text and rfc822 format available.

Message #101 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
Cc: 77288 <at> debbugs.gnu.org, Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Subject: Re: [bug#77288] [PATCH v3 2/8] doc: Document migration to the
 unprivileged daemon.
Date: Sat, 19 Apr 2025 11:07:02 +0200
Hey Florian,

"pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de> writes:

> Works great now.  I can now with v3 merrily switch back and forth the
> (privileged? #f) on Guix System,

Good, thanks for testing again!

> except ownerships of /gnu/store/ files are funny when I guix system
> roll-back to the time before guix-ownership services existed.

Uh, right.  There’s little we can do here, except perhaps adding a
warning in the doc?

> Then I try the
>
>      chown -R root:root                        \
>        /gnu                                                  \
>        /var/guix/{daemon-socket,db,discover}                 \
>        /var/guix/{gcroots,offload,substitute,temproots}      \
>        /var/log/guix                                         \
>        /etc/guix
>
> from this “Migrating to the Unprivileged Daemon” with root:root instead
> of guix-daemon:guix-daemon.  But it does not work unless I mount -o
> remount,rw /gnu/store (a command from the subsequent SELinux section).
>
> While I have not tested this “Migrating to the Unprivileged Daemon”
> section on a foreign distro, should not etc/gnu-store.mount require
> remounting on foreign distros, too?

Users are invited to stop the daemon before doing that, which should
stop ‘gnu-store.mount’ as well.  Do you think that needs to be
clarified?

Thanks,
Ludo’.




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Sat, 19 Apr 2025 20:23:02 GMT) Full text and rfc822 format available.

Message #104 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: 77288 <at> debbugs.gnu.org, Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Subject: Re: [bug#77288] [PATCH v3 2/8] doc: Document migration to the
 unprivileged daemon.
Date: Sat, 19 Apr 2025 22:22:43 +0200
Ludovic Courtès <ludo <at> gnu.org> writes:
>> except ownerships of /gnu/store/ files are funny when I guix system
>> roll-back to the time before guix-ownership services existed.
>
> Uh, right.  There’s little we can do here, except perhaps adding a
> warning in the doc?

No, I was just trying to provoke an error.  No warning in the doc is
needed.  Because (privileged #t) remains the default for some time and
guix-ownership already exists, wrong ownership will not affect many
users when they do not provoke it.  Also files owned by a user id, group
id that no longer exists can happen on system roll-backs.  And it can
obviously be fixed by following the “Migrating to the Unprivileged
Daemon” docs with root:root.

What I should have written to you is that I want the command

mount -o remount,rw /gnu/store

to come before the chown for the migrating foreign distro users.

> Users are invited to stop the daemon before doing that, which should
> stop ‘gnu-store.mount’ as well.[…]

No.  I set up a Debian VM now with Guix from the install script and guix
pulled as root with this patch series.  “systemctl stop guix-daemon”
does not change the mount command outputting:

“/dev/sda1 on /gnu/store type ext4 (ro,relatime,errors=remount-ro)”,

And the chown from “Migrating to the Unprivileged Daemon” prints lines
like:

chown: changing ownership of '/gnu/store/4ab…-mpfr-4.2.1-builder':
Read-only file system

Above mount command from the examples in Guix manual section on SELinux
Policy makes chown work.


I would just write down the “mount -o remount,rw /gnu/store” command,
even though it is not needed when a user is not on systemd or has not
set up gnu-store.mount.


Another observation; I get errors
guix shell: error: opening global GC lock '/var/guix/gc.lock':
Permission denied

I had to chown guix-daemon:guix-daemon /var/guix/gc.lock as well.

Other than that your docs work well for me.  Thanks again!

Regards,
Florian




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Sun, 20 Apr 2025 13:57:02 GMT) Full text and rfc822 format available.

Message #107 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 2/8] doc: Document migration to the
 unprivileged daemon.
Date: Sun, 20 Apr 2025 22:56:28 +0900
Hi Ludovic,

Ludovic Courtès <ludo <at> gnu.org> writes:

> * doc/guix.texi (Build Environment Setup): Add “Migrating to the
> Unprivileged Daemon” section.
> (Upgrading Guix): Link to it.

[...]

It looks good to me, but I was wondering if this wouldn't fit better in
a etc/news.scm entry, or blog post, since it's a section which will
eventually be obsolete, as more and more systems are installed out of the
box with the unprivileged daemon.

A couple of related questions: are Guix Systems already automatically
handled to run the daemon unprivileged now?  Is this just for foreign
systems?  Is the guix-install.sh script able to setup the daemon for
unprivileged execution now?

-- 
Thanks,
Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Sun, 20 Apr 2025 14:26:02 GMT) Full text and rfc822 format available.

Message #110 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 7/8] services: guix: Allow
 ‘guix-daemon’
 to run without root privileges.
Date: Sun, 20 Apr 2025 23:24:38 +0900
Hi,

Ludovic Courtès <ludo <at> gnu.org> writes:

> * gnu/services/base.scm (run-with-writable-store)
> (guix-ownership-change-program): New procedures.
> (<guix-configuration>)[privileged?]: New field.
> (guix-shepherd-service): Rename to…
> (guix-shepherd-services): … this.   Add the ‘guix-ownership’ service.
> Change ‘guix-daemon’ service to depend on it; when unprivileged,
> prefix ‘daemon-command’ by ‘run-with-writable-store’ and
> omit ‘--build-users-group’; adjust socket activation endpoints.
> (guix-accounts): When unprivileged, create the “guix-daemon” user and
> group.
> (guix-service-type)[extensions]: Adjust to name change.
> * gnu/tests/base.scm (run-guix-daemon-test): Add ‘name’ parameter.
> (%test-guix-daemon): Adjust accordingly.
> (%test-guix-daemon-unprivileged): New test.
> * doc/guix.texi (Base Services): Document ‘privileged?’.
> (Migrating to the Unprivileged Daemon): Explain that this is automatic
> on Guix System.

About the migrating part: it's currently automatic, unless someone sets
privileged? to #f.

Other than thati, it sounds good, and answers some of my earlier
questions in this thread.

[...]

> +On Guix System, these steps are carried out automatically when you set
> +the @code{privileged?} field of the @code{guix-configuration} record to
> +@code{#f} and reconfigure (@pxref{guix-configuration-type,
> +@code{guix-configuration}}).

So, it's not automatic?  Or is privileged? #f the default?  Perhaps
worth mentioning what the default value is here, to make this clear
without referring to another place.

> +However, on a foreign distribution, the process is manual.  The
> +following paragraphs describe what you need to do.
> +
>  @quotation Warning
>  Follow the instructions below only after making sure you have a recent
>  version of @command{guix-daemon} with support for unprivileged
> @@ -20105,6 +20113,36 @@ Base Services
>  The Guix package to use.  @xref{Customizing the System-Wide Guix} to
>  learn how to provide a package with a pre-configured set of channels.
>  
> +@cindex unprivileged @command{guix-daemon}
> +@cindex rootless @command{guix-daemon}
> +@item @code{privileged?} (default: @code{#t})
> +Whether to run @command{guix-daemon} as root.
> +
> +When true, @command{guix-daemon} runs with root privileges and build
> +processes run under unprivileged user accounts as specified by
> +@code{build-group} and @code{build-accounts} (see below); when false,
> +@command{guix-daemon} run as the @code{guix-daemon} user, which is
> +unprivileged, and so do build processes.  The unprivileged or
> +``rootless'' mode can reduce the impact of some classes of
> +vulnerabilities that could affect the daemon.
> +
> +The default is currently @code{#t} (@command{guix-daemon} runs with root
> +privileges) but may eventually be changed to @code{#f}.

Ah!  It's covered here.

[...]

> +(define (guix-ownership-change-program)
> +  "Return a program that changes ownership of the store and other data files
> +of Guix to the given UID and GID."
> +  (program-file "validate-guix-ownership"
> +                (with-imported-modules (source-module-closure
> +                                        '((guix build utils)))
> +                  #~(begin
> +                      (use-modules (guix build utils)
> +                                   (ice-9 ftw)
> +                                   (ice-9 match))
> +
> +                      (define (lchown file uid gid)
> +                        (let ((parent (open (dirname file) O_DIRECTORY)))
> +                          (chown-at parent (basename file) uid gid
> +                                    AT_SYMLINK_NOFOLLOW)

Why do we need an atomic variant only for symlinks?  Perhaps worth a
comment.

> +                          (close-port parent)))
> +
> +                      (define (change-ownership directory uid gid)
> +                        ;; chown -R UID:GID DIRECTORY
> +                        (file-system-fold (const #t)                 ;enter?
> +                                          (lambda (file stat result) ;leaf
> +                                            (if (eq? 'symlink (stat:type stat))
> +                                                (lchown file uid gid)
> +                                                (chown file uid gid)))
> +                                          (const #t) ;down
> +                                          (lambda (directory stat result) ;up
> +                                            (chown directory uid gid))
> +                                          (const #t) ;skip
> +                                          (lambda (file stat errno result)
> +                                            (format (current-error-port) "i/o error: ~a: ~a~%"

That's too wide for our 80 columns maximum width convention :-).  Easy
to fix by breaking the line either after program-file or
file-system-fold.

> +                                                    file (strerror errno))
> +                                            #f)
> +                                          #t      ;seed
> +                                          directory
> +                                          lstat))
> +
> +                      (define (claim-data-ownership uid gid)
> +                        (format #t "Changing file ownership for /gnu/store \
> +and data directories to ~a:~a...~%"
> +                                uid gid)
> +                        (change-ownership #$(%store-prefix) uid gid)
> +                        (let ((excluded '("." ".." "profiles" "userpool")))
> +                          (for-each (lambda (directory)
> +                                      (change-ownership (in-vicinity "/var/guix" directory)

Likewise.  Also, I never remember why `in-vicinity' is useful, and it's
not documented anywhere.

> +                                                        uid gid))
> +                                    (scandir "/var/guix"
> +                                             (lambda (file)
> +                                               (not (member file
> +                                                            excluded))))))
> +                        (chown "/var/guix" uid gid)
> +                        (change-ownership "/etc/guix" uid gid)
> +                        (mkdir-p "/var/log/guix")
> +                        (change-ownership "/var/log/guix" uid gid))
> +
> +                      (match (command-line)
> +                        ((_ (= string->number (? integer? uid))
> +                            (= string->number (? integer? gid)))
> +                         (setlocale LC_ALL "C.UTF-8") ;for file name decoding

Isn't C.UTF-8 the default locale used in Guile?  Or is there a reason
why it shouldn't be?  I'm still surprised as to why this is needed.

> +                         (setvbuf (current-output-port) 'line)
> +                         (claim-data-ownership uid gid)))))))
> +
>  (define-record-type* <guix-configuration>
>    guix-configuration make-guix-configuration
>    guix-configuration?
> @@ -1959,6 +2053,8 @@ (define-record-type* <guix-configuration>
>                      (default #f))
>    (tmpdir           guix-tmpdir                   ;string | #f
>                      (default #f))
> +  (privileged?      guix-configuration-privileged?
> +                    (default #t))
>    (build-machines   guix-configuration-build-machines ;list of gexps | '()
>                      (default '()))
>    (environment      guix-configuration-environment  ;list of strings
> @@ -2021,7 +2117,7 @@ (define shepherd-discover-action
>                      (environ environment)
>                      #t)))))
>  
> -(define (guix-shepherd-service config)
> +(define (guix-shepherd-services config)
>    "Return a <shepherd-service> for the Guix daemon service with CONFIG."
>    (define locales
>      (let-system (system target)
> @@ -2030,16 +2126,57 @@ (define (guix-shepherd-service config)
>            glibc-utf8-locales)))
>  
>    (match-record config <guix-configuration>
> -    (guix build-group build-accounts chroot? authorize-key? authorized-keys
> +    (guix privileged?
> +          build-group build-accounts chroot? authorize-key? authorized-keys
>            use-substitutes? substitute-urls max-silent-time timeout
>            log-compression discover? extra-options log-file
>            http-proxy tmpdir chroot-directories environment
>            socket-directory-permissions socket-directory-group
>            socket-directory-user)
>      (list (shepherd-service
> +           (provision '(guix-ownership))
> +           (requirement '(user-processes user-homes))
> +           (one-shot? #t)
> +           (start #~(lambda ()
> +                      (let* ((store #$(%store-prefix))
> +                             (stat (lstat store))
> +                             (privileged? #$(guix-configuration-privileged?
> +                                             config))
> +                             (change-ownership #$(guix-ownership-change-program))
> +                             (with-writable-store #$(run-with-writable-store)))
> +                        ;; Check whether we're switching from privileged to
> +                        ;; unprivileged guix-daemon, or vice versa, and adjust
> +                        ;; file ownership accordingly.  Spawn a child process
> +                        ;; if and only if something needs to be changed.
> +                        ;;
> +                        ;; Note: This service remains in 'starting' state for
> +                        ;; as long as CHANGE-OWNERSHIP is running.  That way,
> +                        ;; 'guix-daemon' starts only once we're done.
> +                        (cond ((and (not privileged?)
> +                                    (or (zero? (stat:uid stat))
> +                                        (zero? (stat:gid stat))))
> +                               (let ((user (getpwnam "guix-daemon")))
> +                                 (format #t "Changing to unprivileged guix-daemon.~%")

Too large; did you change your editor settings? :-).

[...]

> -(define (run-guix-daemon-test os)
> +(define (run-guix-daemon-test os name)
>    (define test-image
>      (image (operating-system os)
>             (format 'compressed-qcow2)
> @@ -1161,6 +1162,12 @@ (define (run-guix-daemon-test os)
>              ;; Wait for 'guix-daemon' to be up.
>              (marionette-eval '(begin
>                                  (use-modules (gnu services herd))
> +                                (start-service 'guix-daemon)
> +
> +                                ;; XXX: Do it a second time to work around
> +                                ;; <https://issues.guix.gnu.org/77274> and its
> +                                ;; effect on the 'guix-ownership' service.
> +                                ;; TODO: Remove when Shepherd 1.0.4
> is out.

Shepherd 1.0.4 is out!

>                                  (start-service 'guix-daemon))

Are you sure this translates to 'wait for X to be up?'  In my recent
experience, this returned very quickly, not synchronously to the service
being up.  (I seem to recall for about Shephed 1.0 there's now a
distinction in the state of a service 'starting' and 'running'.).  In
the recently added ngircd-service-type, I've used this in its system
tests:

--8<---------------cut here---------------start------------->8---
+          (test-assert "ngircd service runs"
+            (marionette-eval
+             '(begin
+                (use-modules (gnu services herd))
+                (wait-for-service 'ngircd))
--8<---------------cut here---------------end--------------->8---

With the above nitpicks taken into account,

Reviewed-by: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>

--
Thanks,
Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Sun, 20 Apr 2025 15:48:02 GMT) Full text and rfc822 format available.

Message #113 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Cc: Ludovic Courtès <ludo <at> gnu.org>, 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 2/8] doc: Document migration to the
 unprivileged daemon.
Date: Sun, 20 Apr 2025 17:47:48 +0200
Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:
> It looks good to me, but I was wondering if this wouldn't fit better in
> a etc/news.scm entry, or blog post, since it's a section which will
> eventually be obsolete, as more and more systems are installed out of the
> box with the unprivileged daemon.

Good that you review.

I believe the privileged daemon will remain as a choice, thus the
section remains relevant and the manual is the right place?

Regards,
Florian




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Sun, 20 Apr 2025 15:54:03 GMT) Full text and rfc822 format available.

Message #116 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Cc: 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 2/8] doc: Document migration to the
 unprivileged daemon.
Date: Sun, 20 Apr 2025 16:46:07 +0200
Hi,

Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:

> Ludovic Courtès <ludo <at> gnu.org> writes:
>
>> * doc/guix.texi (Build Environment Setup): Add “Migrating to the
>> Unprivileged Daemon” section.
>> (Upgrading Guix): Link to it.
>
> [...]
>
> It looks good to me, but I was wondering if this wouldn't fit better in
> a etc/news.scm entry, or blog post, since it's a section which will
> eventually be obsolete, as more and more systems are installed out of the
> box with the unprivileged daemon.

I think it’s better this way because it will probably be a long process,
and because it’s good to be able to point people to a page in the
manual.

> A couple of related questions: are Guix Systems already automatically
> handled to run the daemon unprivileged now?  Is this just for foreign
> systems?  Is the guix-install.sh script able to setup the daemon for
> unprivileged execution now?

This patch series is initially about migration on Guix System only, but
following a suggestion by Florian and also requests from others
wondering how to migrate on foreign distros, I thought I’d also include
this here.

Thanks,
Ludo’.




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Sun, 20 Apr 2025 17:01:01 GMT) Full text and rfc822 format available.

Message #119 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Cc: 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 7/8] services: guix: Allow
 ‘guix-daemon’
 to run without root privileges.
Date: Sun, 20 Apr 2025 18:27:03 +0200
Hello,

Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:

>> +  (program-file "validate-guix-ownership"
>> +                (with-imported-modules (source-module-closure
>> +                                        '((guix build utils)))
>> +                  #~(begin
>> +                      (use-modules (guix build utils)
>> +                                   (ice-9 ftw)
>> +                                   (ice-9 match))
>> +
>> +                      (define (lchown file uid gid)
>> +                        (let ((parent (open (dirname file) O_DIRECTORY)))
>> +                          (chown-at parent (basename file) uid gid
>> +                                    AT_SYMLINK_NOFOLLOW)
>
> Why do we need an atomic variant only for symlinks?  Perhaps worth a
> comment.

This procedure emulates lchown(2), for which Guile does not provide
bindings.

>> +                                          (lambda (file stat errno result)
>> +                                            (format (current-error-port) "i/o error: ~a: ~a~%"
>
> That's too wide for our 80 columns maximum width convention :-).  Easy
> to fix by breaking the line either after program-file or
> file-system-fold.

Will do!

>> +                                      (change-ownership (in-vicinity "/var/guix" directory)
>
> Likewise.  Also, I never remember why `in-vicinity' is useful, and it's
> not documented anywhere.

It’s more concise and more accurate than (string-append a "/" b).
I’ve come to use it more.

>> +                         (setlocale LC_ALL "C.UTF-8") ;for file name decoding
>
> Isn't C.UTF-8 the default locale used in Guile?  Or is there a reason
> why it shouldn't be?  I'm still surprised as to why this is needed.

C.UTF-8 is now always available (embedded in our libc), but the default
is always C.

>> +                                ;; XXX: Do it a second time to work around
>> +                                ;; <https://issues.guix.gnu.org/77274> and its
>> +                                ;; effect on the 'guix-ownership' service.
>> +                                ;; TODO: Remove when Shepherd 1.0.4
>> is out.
>
> Shepherd 1.0.4 is out!

Oh right.  :-)  I’ll adjust accordingly.

>>                                  (start-service 'guix-daemon))
>
> Are you sure this translates to 'wait for X to be up?'

Yes, and many system tests use this idiom.  You can experience it,
assuming you have a system that takes a long enough to start, by running
‘herd start X & herd start X’: one client will just wait for the other.

> Reviewed-by: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>

Thank you!

Ludo’.




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Sun, 20 Apr 2025 17:01:02 GMT) Full text and rfc822 format available.

Message #122 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
Cc: 77288 <at> debbugs.gnu.org, Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Subject: Re: [bug#77288] [PATCH v3 2/8] doc: Document migration to the
 unprivileged daemon.
Date: Sun, 20 Apr 2025 18:52:43 +0200
Hi Florian,

"pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de> writes:

> No, I was just trying to provoke an error.  No warning in the doc is
> needed.  Because (privileged #t) remains the default for some time and
> guix-ownership already exists, wrong ownership will not affect many
> users when they do not provoke it.  Also files owned by a user id, group
> id that no longer exists can happen on system roll-backs.  And it can
> obviously be fixed by following the “Migrating to the Unprivileged
> Daemon” docs with root:root.

Right.

> What I should have written to you is that I want the command
>
> mount -o remount,rw /gnu/store
>
> to come before the chown for the migrating foreign distro users.

Indeed.  I’ve now added it.

> Another observation; I get errors
> guix shell: error: opening global GC lock '/var/guix/gc.lock':
> Permission denied
>
> I had to chown guix-daemon:guix-daemon /var/guix/gc.lock as well.

Oh right, added as well.

Thanks a lot for testing all this and suggesting fixes!

Ludo’.




Reply sent to Ludovic Courtès <ludo <at> gnu.org>:
You have taken responsibility. (Sun, 20 Apr 2025 17:01:02 GMT) Full text and rfc822 format available.

Notification sent to Ludovic Courtès <ludo <at> gnu.org>:
bug acknowledged by developer. (Sun, 20 Apr 2025 17:01:03 GMT) Full text and rfc822 format available.

Message #127 received at 77288-done <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: 77288-done <at> debbugs.gnu.org
Cc: Florian Pelz <pelzflorian <at> pelzflorian.de>,
 Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Subject: Re: [bug#77288] [PATCH v3 0/8] Rootless guix-daemon on Guix System
Date: Sun, 20 Apr 2025 18:59:38 +0200
I pushed this series including the latest suggestions by Florian
(migration instructions on foreign distros) and Maxim (long lines,
leftover hack in the system test).

  ba53ff9cc4 * news: Add entry about unprivileged guix-daemon on Guix System.
  e2583b5a17 * services: guix: Allow ‘guix-daemon’ to run without root privileges.
  2c7c059e0b * tests: guix-daemon: Wait for the ‘guix-daemon’ service to be up.
  6367e69f50 * tests: guix-daemon: Send system log output to /dev/console.
  da741d8931 * services: account: Create /var/guix/profiles/per-user/$USER.
  c990405607 * syscalls: Add ‘unshare’.
  78f493dcf8 * doc: Document migration to the unprivileged daemon.
  efcce99acb * self: Install systemd ‘.service’ files.

Thanks for helping out!

Ludo’.




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Mon, 21 Apr 2025 00:13:01 GMT) Full text and rfc822 format available.

Message #130 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
Cc: Ludovic Courtès <ludo <at> gnu.org>, 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 2/8] doc: Document migration to the
 unprivileged daemon.
Date: Mon, 21 Apr 2025 09:11:52 +0900
Hi Florian,

"pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de> writes:

> Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:
>> It looks good to me, but I was wondering if this wouldn't fit better in
>> a etc/news.scm entry, or blog post, since it's a section which will
>> eventually be obsolete, as more and more systems are installed out of the
>> box with the unprivileged daemon.
>
> Good that you review.
>
> I believe the privileged daemon will remain as a choice, thus the
> section remains relevant and the manual is the right place?

Documented as a choice in the manual yes, but I was referring to the
fact that we'll have a full blown 'migrating' section, that will make
less sense in 5 years time when most will already using the privileged
daemon without even knowing it (assuming it becomes the default as
planned).

-- 
Thanks,
Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Mon, 21 Apr 2025 00:14:02 GMT) Full text and rfc822 format available.

Message #133 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 2/8] doc: Document migration to the
 unprivileged daemon.
Date: Mon, 21 Apr 2025 09:13:09 +0900
Hi Ludovic,

Ludovic Courtès <ludo <at> gnu.org> writes:

> Hi,
>
> Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:
>
>> Ludovic Courtès <ludo <at> gnu.org> writes:
>>
>>> * doc/guix.texi (Build Environment Setup): Add “Migrating to the
>>> Unprivileged Daemon” section.
>>> (Upgrading Guix): Link to it.
>>
>> [...]
>>
>> It looks good to me, but I was wondering if this wouldn't fit better in
>> a etc/news.scm entry, or blog post, since it's a section which will
>> eventually be obsolete, as more and more systems are installed out of the
>> box with the unprivileged daemon.
>
> I think it’s better this way because it will probably be a long process,
> and because it’s good to be able to point people to a page in the
> manual.

OK.  We can always remove it if/when it becomes outdated/irrelevant.

>> A couple of related questions: are Guix Systems already automatically
>> handled to run the daemon unprivileged now?  Is this just for foreign
>> systems?  Is the guix-install.sh script able to setup the daemon for
>> unprivileged execution now?
>
> This patch series is initially about migration on Guix System only, but
> following a suggestion by Florian and also requests from others
> wondering how to migrate on foreign distros, I thought I’d also include
> this here.

OK, thanks for explaining.

-- 
Thanks,
Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Mon, 21 Apr 2025 01:15:02 GMT) Full text and rfc822 format available.

Message #136 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 7/8] services: guix: Allow
 ‘guix-daemon’
 to run without root privileges.
Date: Mon, 21 Apr 2025 10:14:31 +0900
Hi Ludovic,

Ludovic Courtès <ludo <at> gnu.org> writes:

> Hello,
>
> Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:
>
>>> +  (program-file "validate-guix-ownership"
>>> +                (with-imported-modules (source-module-closure
>>> +                                        '((guix build utils)))
>>> +                  #~(begin
>>> +                      (use-modules (guix build utils)
>>> +                                   (ice-9 ftw)
>>> +                                   (ice-9 match))
>>> +
>>> +                      (define (lchown file uid gid)
>>> +                        (let ((parent (open (dirname file) O_DIRECTORY)))
>>> +                          (chown-at parent (basename file) uid gid
>>> +                                    AT_SYMLINK_NOFOLLOW)
>>
>> Why do we need an atomic variant only for symlinks?  Perhaps worth a
>> comment.
>
> This procedure emulates lchown(2), for which Guile does not provide
> bindings.

OK.  Perhaps it should?  We could report to bug-guile and here add a
comment pointing to the issue to remind us to export it in Guile and use
it when it's there in the future.

>>> +                                      (change-ownership (in-vicinity "/var/guix" directory)
>>
>> Likewise.  Also, I never remember why `in-vicinity' is useful, and it's
>> not documented anywhere.
>
> It.s more concise and more accurate than (string-append a "/" b).
> I.ve come to use it more.

It's problematic that it's not documented though.  Grepping the guile
source shouldn't be needed to understand the code, ideally.  The
'in-vicinity' name also suggests it might do something cleverer than
just concatenating strings.

>>> +                         (setlocale LC_ALL "C.UTF-8") ;for file name decoding
>>
>> Isn't C.UTF-8 the default locale used in Guile?  Or is there a reason
>> why it shouldn't be?  I'm still surprised as to why this is needed.
>
> C.UTF-8 is now always available (embedded in our libc), but the default
> is always C.

Uh.  Are there plans to change this in the future?  It seems we're well
into the Unicode age :-).

>>> +                                ;; XXX: Do it a second time to work around
>>> +                                ;; <https://issues.guix.gnu.org/77274> and its
>>> +                                ;; effect on the 'guix-ownership' service.
>>> +                                ;; TODO: Remove when Shepherd 1.0.4
>>> is out.
>>
>> Shepherd 1.0.4 is out!
>
> Oh right.  :-)  I.ll adjust accordingly.
>
>>>                                  (start-service 'guix-daemon))
>>
>> Are you sure this translates to 'wait for X to be up?'
>
> Yes, and many system tests use this idiom.  You can experience it,
> assuming you have a system that takes a long enough to start, by running
> .herd start X & herd start X.: one client will just wait for the other.

That surprises me, because I thought I recently observed some problem
with the use of 'start-service' in our test suite.  Perhaps the issue
was that simply asserting against 'start-service', without checking that
the returned service object 'running' slot is set to #t doesn't guard
against when the service fails to start, which is what is currently done
in many places.  Right?

-- 
Thanks,
Maxim




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Mon, 21 Apr 2025 10:13:02 GMT) Full text and rfc822 format available.

Message #139 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Ludovic Courtès <ludo <at> gnu.org>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Cc: 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 7/8] services: guix: Allow
 ‘guix-daemon’
 to run without root privileges.
Date: Mon, 21 Apr 2025 12:11:03 +0200
Hello,

Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:

>> This procedure emulates lchown(2), for which Guile does not provide
>> bindings.
>
> OK.  Perhaps it should?  We could report to bug-guile and here add a
> comment pointing to the issue to remind us to export it in Guile and use
> it when it's there in the future.

Yes, we should do that.

>>> Likewise.  Also, I never remember why `in-vicinity' is useful, and it's
>>> not documented anywhere.
>>
>> It.s more concise and more accurate than (string-append a "/" b).
>> I.ve come to use it more.
>
> It's problematic that it's not documented though.  Grepping the guile
> source shouldn't be needed to understand the code, ideally.  The
> 'in-vicinity' name also suggests it might do something cleverer than
> just concatenating strings.

It’s “clever” in that it appends a slash only if needed.  That’s the
extent of its cleverness :-) but it’s nice.

>> C.UTF-8 is now always available (embedded in our libc), but the default
>> is always C.
>
> Uh.  Are there plans to change this in the future?  It seems we're well
> into the Unicode age :-).

It’s a POSIX issue though: processes always start out under the C locale
and have to call setlocale(3) to change that.

>>>>                                  (start-service 'guix-daemon))
>>>
>>> Are you sure this translates to 'wait for X to be up?'
>>
>> Yes, and many system tests use this idiom.  You can experience it,
>> assuming you have a system that takes a long enough to start, by running
>> .herd start X & herd start X.: one client will just wait for the other.
>
> That surprises me, because I thought I recently observed some problem
> with the use of 'start-service' in our test suite.  Perhaps the issue
> was that simply asserting against 'start-service', without checking that
> the returned service object 'running' slot is set to #t doesn't guard
> against when the service fails to start, which is what is currently done
> in many places.  Right?

Waiting for ‘start-service’ to complete guarantees that the attempt to
start the service has completed, and checking its return value lets you
know whether it has succeeded.

Here’s an example with my user shepherd, where ‘redshift’ was already
running and ‘failing’ was stopped but its ‘start’ method is (const #f):

--8<---------------cut here---------------start------------->8---
scheme@(gnu services herd)> (parameterize ((%shepherd-socket-file "/run/user/1000/shepherd/socket")) (start-service 'redshift))
shepherd: Service redshift is already running.
$7 = (service (version 0) (provides (redshift)) (requires (x11-display)) (respawn? #t) (docstring "Redshift program.") (enabled? #t) (running (process (version 0) (id 2691) (command ("/gnu/store/arj5gvqdi3j9kbpk9vjdh5l0gk1gaqsh-redshift-1.12/bin/redshift" "-c" "/gnu/store/lj6ajc15a3i7q198hb69gsda8dcxzh6f-redshift.conf")))) (conflicts ()) (last-respawns ()) (status-changes (…)) (startup-failures ()) (status running) (one-shot? #f) (transient? #f) (respawn-limit (5 . 7)) (respawn-delay 0.1) (actions (configuration)) (exit-statuses ((0 . 1745228665) …)) (recent-messages ((1745228689 . "Location: …") (1745228689 . "Waiting for initial location to become available..."))) (log-files ()) (pending-replacement? #f))
scheme@(gnu services herd)> (parameterize ((%shepherd-socket-file "/run/user/1000/shepherd/socket")) (start-service 'failing))
shepherd: Starting service failing...
shepherd: Service failing failed to start.
shepherd: Service failing could not be started.
$8 = #f
--8<---------------cut here---------------end--------------->8---

If we get the full-blown service sexp, as in the ‘redshift’ example, it
means that the service is ‘started’.

Ludo’.




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Mon, 21 Apr 2025 11:08:02 GMT) Full text and rfc822 format available.

Message #142 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: "pelzflorian (Florian Pelz)" <pelzflorian <at> pelzflorian.de>
To: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
Cc: Ludovic Courtès <ludo <at> gnu.org>, 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 2/8] doc: Document migration to the
 unprivileged daemon.
Date: Mon, 21 Apr 2025 13:07:57 +0200
Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:
> Documented as a choice in the manual yes, but I was referring to the
> fact that we'll have a full blown 'migrating' section, that will make
> less sense in 5 years time when most will already using the privileged
> daemon without even knowing it (assuming it becomes the default as
> planned).

We can never fully remove the root-privileged option on less-capable
init systems, or when someone decides to put Guix in complicated chroot,
WSL, Docker environments or perhaps semi-virtualized other operating
systems?

Let’s see, maybe there’s no longer a need to migrate then.  But I would
expect the Setting up the Daemon instructions will remain.

Regards,
Florian




Information forwarded to guix-patches <at> gnu.org:
bug#77288; Package guix-patches. (Tue, 22 Apr 2025 01:09:02 GMT) Full text and rfc822 format available.

Message #145 received at 77288 <at> debbugs.gnu.org (full text, mbox):

From: Maxim Cournoyer <maxim.cournoyer <at> gmail.com>
To: Ludovic Courtès <ludo <at> gnu.org>
Cc: 77288 <at> debbugs.gnu.org
Subject: Re: [bug#77288] [PATCH v2 7/8] services: guix: Allow
 ‘guix-daemon’
 to run without root privileges.
Date: Tue, 22 Apr 2025 10:07:47 +0900
Hi Ludovic,

Ludovic Courtès <ludo <at> gnu.org> writes:

> Hello,
>
> Maxim Cournoyer <maxim.cournoyer <at> gmail.com> writes:
>
>>> This procedure emulates lchown(2), for which Guile does not provide
>>> bindings.
>>
>> OK.  Perhaps it should?  We could report to bug-guile and here add a
>> comment pointing to the issue to remind us to export it in Guile and use
>> it when it's there in the future.
>
> Yes, we should do that.

OK, done; see bug#77977.

>>>> Likewise.  Also, I never remember why `in-vicinity' is useful, and it's
>>>> not documented anywhere.
>>>
>>> It.s more concise and more accurate than (string-append a "/" b).
>>> I.ve come to use it more.
>>
>> It's problematic that it's not documented though.  Grepping the guile
>> source shouldn't be needed to understand the code, ideally.  The
>> 'in-vicinity' name also suggests it might do something cleverer than
>> just concatenating strings.
>
> It’s “clever” in that it appends a slash only if needed.  That’s the
> extent of its cleverness :-) but it’s nice.

OK, I've created another issue to have that documented in Guile, see
bug#77978.

>>> C.UTF-8 is now always available (embedded in our libc), but the default
>>> is always C.
>>
>> Uh.  Are there plans to change this in the future?  It seems we're well
>> into the Unicode age :-).
>
> It’s a POSIX issue though: processes always start out under the C locale
> and have to call setlocale(3) to change that.

Maybe we can in some places, like GNU advises, choose a slightly
different path than POSIX, where it improves the life of our users.
Patch bug#71262 from Tomas appears to take that route.

[...]

> Waiting for ‘start-service’ to complete guarantees that the attempt to
> start the service has completed, and checking its return value lets you
> know whether it has succeeded.
>
> Here’s an example with my user shepherd, where ‘redshift’ was already
> running and ‘failing’ was stopped but its ‘start’ method is (const #f):
>
> scheme@(gnu services herd)> (parameterize ((%shepherd-socket-file "/run/user/1000/shepherd/socket")) (start-service 'redshift))
> shepherd: Service redshift is already running.
> $7 = (service (version 0) (provides (redshift)) (requires (x11-display)) (respawn? #t) (docstring "Redshift program.") (enabled? #t) (running (process (version 0) (id 2691) (command ("/gnu/store/arj5gvqdi3j9kbpk9vjdh5l0gk1gaqsh-redshift-1.12/bin/redshift" "-c" "/gnu/store/lj6ajc15a3i7q198hb69gsda8dcxzh6f-redshift.conf")))) (conflicts ()) (last-respawns ()) (status-changes (…)) (startup-failures ()) (status running) (one-shot? #f) (transient? #f) (respawn-limit (5 . 7)) (respawn-delay 0.1) (actions (configuration)) (exit-statuses ((0 . 1745228665) …)) (recent-messages ((1745228689 . "Location: …") (1745228689 . "Waiting for initial location to become available..."))) (log-files ()) (pending-replacement? #f))
> scheme@(gnu services herd)> (parameterize ((%shepherd-socket-file "/run/user/1000/shepherd/socket")) (start-service 'failing))
> shepherd: Starting service failing...
> shepherd: Service failing failed to start.
> shepherd: Service failing could not be started.
> $8 = #f
>
> If we get the full-blown service sexp, as in the ‘redshift’ example, it
> means that the service is ‘started’.

Thanks for the demonstration, it does indeed look like it works as
expected/used.

Maybe the last thing that could go wrong is if there's a syntax error in
the start slot and shepherd is not even able to run it, but then
something much worst would likely happen (like the system failing to
boot).  If I again encounter a problem with it, I'll make sure to keep
notes/the exact context in which it happened.

-- 
Thanks,
Maxim




bug archived. Request was from Debbugs Internal Request <help-debbugs <at> gnu.org> to internal_control <at> debbugs.gnu.org. (Tue, 20 May 2025 11:24:24 GMT) Full text and rfc822 format available.

This bug report was last modified 47 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.