Subject: [PATCH v3 07/10] system: Factorize bashrc default configuration.

From: Maxim Cournoyer <[email-masked]>
Date: Mon, 19 May 2025 09:29:05 +0900

This factorizes out the remaining bashrc bits from /etc/skel/.bashrc to a the
template used for both /etc/bashrc on Guix System and ~/.bashrc for
home-bash-service-type.

Rationale: The use of /etc/skel introduce state: the file is only copied
originally when the user account is created, and never (automatically)
refreshed again.

* gnu/system.scm (operating-system-etc-service):
<profile>: Guard against souring /etc/bashrc in non-interactive, SSH case.
<bashrc>: Use %default-bashrc, having migrated the remaining definitions to...
* gnu/system/shadow.scm (%default-bashrc): ... here.  Factorize aliases to...
* gnu/services.scm (%default-bash-aliases): ... here.
(%default-bashrc-d-aliases): New variable.
(%default-etc-bashrc-d-files): Include it in the default configuration.
* gnu/services/base.scm (%base-services): Register etc-bashrc-d-service-type.
* gnu/home/services/shells.scm (add-bash-configuration): Do not set PS1, now
part of %default-bashrc.
(home-bash-configuration) [guix-defaults?]: Update doc.
[aliases]: Set %default-bash-aliases as the default value.  Update doc.
* doc/guix.texi (Shells Home Services): Update documentation.
(Service Reference): Update example.

Change-Id: I340c614983a78fd20a9c4a9705e7fc542ae9b513
---
 doc/guix.texi                | 19 +++++++++++--------
 gnu/home/services/shells.scm | 14 +++++++-------
 gnu/services.scm             | 23 +++++++++++++++++++++--
 gnu/services/base.scm        |  5 ++++-
 gnu/system.scm               | 24 +++++++-----------------
 gnu/system/shadow.scm        | 18 ++++++++++--------
 gnu/tests/base.scm           | 17 ++++++++++-------
 7 files changed, 70 insertions(+), 50 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 746722caa61..80a109e2bb3 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -47654,15 +47654,15 @@ Service Reference
 and are silently dropped.  The default value is made available via the
 @var{%default-etc-bashrc-d-files} variable for users to extended.
 Package objects can also be provided directly to have their
-@file{etc/bashrc.d/*.sh} prefixed files added.  An example usage may
-look like:
+@file{etc/bashrc.d/*.sh} prefixed files added.  Since the service is
+part @code{%base-services}, it can be extended via @code{simple-service}
+like so:
 
 @example
 (use-package-modules gnome)     ;for the `vte' package
 
-(service etc-bashrc-d-service-type
-         (append (list (file-append vte "/etc/profile.d/vte.sh"))
-                 %default-etc-bashrc-d-files))
+(simple-service 'vte-integration etc-bashrc-d-service-type
+                (list (file-append vte "/etc/profile.d/vte.sh")))
 @end example
 @end defvar
 
@@ -49288,8 +49288,11 @@ Shells Home Services
 The Bash package to use.
 
 @item @code{guix-defaults?} (default: @code{#t}) (type: boolean)
-Add sane defaults like reading @file{/etc/bashrc} and coloring the output of
-@command{ls} to the top of the @file{.bashrc} file.
+Add sane defaults like setting @env{PS1}, @env{SHELL}, and ensuring
+@file{/etc/profile} is sourced for non-interactive SSH shells.  If you
+use Guix System, is it safe to set this to @code{#f}, as in this case
+this is already taken care of by the globally installed
+@file{/etc/bashrc}.
 
 @item @code{environment-variables} (default: @code{'()}) (type: alist)
 Association list of environment variables to set for the Bash session.  The
@@ -49297,7 +49300,7 @@ Shells Home Services
 here (@pxref{Essential Home Services}).  The contents of this field will be
 added after the contents of the @code{bash-profile} field.
 
-@item @code{aliases} (default: @code{'()}) (type: alist)
+@item @code{aliases} (default: @code{%default-bash-aliases}) (type: alist)
 Association list of aliases to set for the Bash session.  The aliases
 will be defined after the contents of the @code{bashrc} field has been
 put in the @file{.bashrc} file.  The alias will automatically be quoted,
diff --git a/gnu/home/services/shells.scm b/gnu/home/services/shells.scm
index bab5730c3d6..969a3eb8dcf 100644
--- a/gnu/home/services/shells.scm
+++ b/gnu/home/services/shells.scm
@@ -20,6 +20,7 @@
 
 (define-module (gnu home services shells)
   #:use-module (gnu services configuration)
+  #:use-module ((gnu services) #:select (%default-bash-aliases))
   #:autoload   (gnu system shadow) (%default-bashrc %default-zprofile)
   #:use-module (gnu home services utils)
   #:use-module (gnu home services)
@@ -333,9 +334,12 @@ (define-configuration home-bash-configuration
    (package bash)
    "The Bash package to use.")
   (guix-defaults?
+   ;; TODO: Set to #f when the target system is determined to be Guix System.
    (boolean #t)
-   "Add sane defaults like reading @file{/etc/bashrc} and coloring the output of
-@command{ls} to the top of the @file{.bashrc} file.")
+   "Add sane defaults like setting @env{PS1}, @env{SHELL}, and ensuring
+@file{/etc/profile} is sourced for non-interactive SSH shells.  If you use
+Guix System, is it safe to set this to @code{#f}, as in this case this is
+already taken care of by the globally installed @file{/etc/bashrc}.")
   (environment-variables
    (alist '())
    "Association list of environment variables to set for the Bash session.  The
@@ -344,7 +348,7 @@ (define-configuration home-bash-configuration
 added after the contents of the @code{bash-profile} field."
    (serializer serialize-posix-env-vars))
   (aliases
-   (alist '())
+   (alist %default-bash-aliases)
    "Association list of aliases to set for the Bash session.  The aliases will be
 defined after the contents of the @code{bashrc} field has been put in the
 @file{.bashrc} file.  The alias will automatically be quoted, so something line
@@ -423,10 +427,6 @@ (define (add-bash-configuration config)
               'bashrc
               (if (home-bash-configuration-guix-defaults? config)
                   (list (plain-file-content %default-bashrc) "\n"
-                        ;; The host distro might provide a bad 'PS1'
-                        ;; default--e.g., not taking $GUIX_ENVIRONMENT into
-                        ;; account.  Provide a good default here when asked.
-                        "PS1='\\u@\\h \\w${GUIX_ENVIRONMENT:+ [env]}\\$ '\n"
                         (serialize-field 'aliases))
                   (list (serialize-field 'aliases))))
              (file-if-not-empty 'bash-logout)))))
diff --git a/gnu/services.scm b/gnu/services.scm
index 5dbda176b80..159d357c3e7 100644
--- a/gnu/services.scm
+++ b/gnu/services.scm
@@ -125,6 +125,7 @@ (define-module (gnu services)
             etc-profile-d-service-type
             etc-bashrc-d-service-type
             %default-etc-bashrc-d-files
+            %default-bash-aliases
             etc-directory
             privileged-program-service-type
             setuid-program-service-type ; deprecated
@@ -993,9 +994,27 @@ (define etc-profile-d-service-type
 (define files->bashrc-d-directory
   (make-files->etc-directory "bashrc.d"))
 
+;;; Use an alist to be compatible with <home-bash-configuration>.
+(define %default-bash-aliases
+  '(("ls" . "ls -p --color=auto")
+    ("ll" . "ls -l")
+    ("grep" . "grep --color=auto")
+    ("ip" . "ip -color=auto")))
+
+;;; ... but avoid the full blown bash-serialize-aliases, which depends on
+;;; other 'guix home' definitions such as `shell-double-quote'.
+(define %default-bashrc-d-aliases
+  (plain-file "aliases.sh"
+              (string-join
+               (map (match-lambda
+                      ((alias . value)
+                       (format #f "~a=~s~%" alias value)))
+                    %default-bash-aliases)
+               "")))
+
 (define %default-etc-bashrc-d-files
-  (list (file-append bash-completion
-                     "/etc/profile.d/bash_completion.sh")))
+  (list (file-append bash-completion "/etc/profile.d/bash_completion.sh")
+        %default-bashrc-d-aliases))
 
 (define etc-bashrc-d-service-type
   (service-type
diff --git a/gnu/services/base.scm b/gnu/services/base.scm
index 5a3dd2f555d..1f5da97f0a7 100644
--- a/gnu/services/base.scm
+++ b/gnu/services/base.scm
@@ -4138,7 +4138,10 @@ (define %base-services
         (service mingetty-service-type (mingetty-configuration
                                          (tty "tty5")))
         (service mingetty-service-type (mingetty-configuration
-                                         (tty "tty6")))
+                                        (tty "tty6")))
+
+        ;; Extra Bash configuration including Bash completion and aliases.
+        (service etc-bashrc-d-service-type)
 
         (service static-networking-service-type
                  (list %loopback-static-networking))
diff --git a/gnu/system.scm b/gnu/system.scm
index 50f739c17c7..0b251bd3a08 100644
--- a/gnu/system.scm
+++ b/gnu/system.scm
@@ -1132,23 +1132,13 @@ (define* (operating-system-etc-service os)
 done
 unset i
 
-if [ -n \"$BASH_VERSION\" -a -f /etc/bashrc ]
-then
-  # Load Bash-specific initialization code.
-  . /etc/bashrc
+if [ -n \"$BASH_VERSION\" -a -f /etc/bashrc ]; then
+  # Load Bash-specific initialization code, taking care to not source
+  # /etc/bashrc when invoked from a non-interactive SSH shell,
+  # to avoid recursion (/etc/bashrc also sources /etc/profile
+  # in the non-login, non-interactive SSH case).
+  [[ $- != *i* && -n $SSH_CLIENT ]] || source /etc/bashrc
 fi
-"))
-
-        (bashrc    (plain-file "bashrc" "\
-# Bash-specific initialization.
-
-# Provide a default prompt.  The user's ~/.bashrc can override it.
-PS1='\\u@\\h \\w${GUIX_ENVIRONMENT:+ [env]}\\$ '
-
-for i in /etc/bashrc.d/*.sh; do
-    [[ -r $i ]] && source \"$i\"
-done
-unset i
 ")))
     (service etc-service-type
      `(("os-release" ,os-release)
@@ -1159,7 +1149,7 @@ (define* (operating-system-etc-service os)
        ("issue" ,issue)
        ,@(if nsswitch `(("nsswitch.conf" ,nsswitch)) '())
        ("profile" ,profile)
-       ("bashrc" ,bashrc)
+       ("bashrc" ,%default-bashrc)
        ;; Write the operating-system-host-name to /etc/hostname to prevent
        ;; NetworkManager from changing the system's hostname when connecting
        ;; to certain networks.  Some discussion at
diff --git a/gnu/system/shadow.scm b/gnu/system/shadow.scm
index b9a5a6134bc..6443fd9922d 100644
--- a/gnu/system/shadow.scm
+++ b/gnu/system/shadow.scm
@@ -147,8 +147,11 @@ (define %base-user-accounts
 
 (define %default-bashrc
   (plain-file "bashrc" "\
-# Bash initialization for interactive non-login shells and
-# for remote shells (info \"(bash) Bash Startup Files\").
+# Bash-specific initialization, including for non-login and remote
+# shells (info \"(bash) Bash Startup Files\").
+
+# Provide a default prompt.
+PS1='\\u@\\h \\w${GUIX_ENVIRONMENT:+ [env]}\\$ '
 
 # Export 'SHELL' to child processes.  Programs such as 'screen'
 # honor it and otherwise use /bin/sh.
@@ -165,10 +168,11 @@ (define %default-bashrc
     return
 fi
 
-alias ls='ls -p --color=auto'
-alias ll='ls -l'
-alias grep='grep --color=auto'
-alias ip='ip -color=auto'\n"))
+for i in /etc/bashrc.d/*.sh; do
+    [[ -r $i ]] && source \"$i\"
+done
+unset i
+"))
 
 (define %default-bash-profile
   (plain-file "bash_profile" "\
@@ -289,12 +293,10 @@ (define (default-skeletons)
 'useradd' in the home directory of newly created user accounts."
 
   (let ((profile   %default-bash-profile)
-        (bashrc    %default-bashrc)
         (zprofile  %default-zprofile)
         (xdefaults %default-xdefaults)
         (gdbinit   %default-gdbinit))
     `((".bash_profile" ,profile)
-      (".bashrc" ,bashrc)
       ;; Zsh sources ~/.zprofile before ~/.zshrc, and it sources ~/.zlogin
       ;; after ~/.zshrc.  To avoid interfering with any customizations a user
       ;; may have made in their ~/.zshrc, put this in .zprofile, not .zlogin.
diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm
index 0f31c152e0f..db904d7f102 100644
--- a/gnu/tests/base.scm
+++ b/gnu/tests/base.scm
@@ -187,6 +187,8 @@ (define* (run-basic-test os command #:optional (name "basic")
                                             "\
 . /etc/bashrc
 set -e -x
+test -f /etc/bashrc.d/bash_completion.sh
+test -f /etc/bashrc.d/aliases.sh
 test -f /etc/bashrc.d/test_bashrc_d.sh
 test \"$BASHRC_D_OK\" = yes"))
                     marionette)))
@@ -606,14 +608,15 @@ (define* (test-basic-os #:optional (kernel linux-libre))
                                            (plain-file
                                             "invalid-name"
                                             "not a POSIX script -- ignore me")))
-                                    (service
+                                    (simple-service
+                                     'extra-bashrc-d-files
                                      etc-bashrc-d-service-type
-                                     (list (plain-file
-                                            "test_bashrc_d.sh"
-                                            "export BASHRC_D_OK=yes\n")
-                                           (plain-file
-                                            "invalid-name"
-                                            "not a Bash script -- ignore me")))
+                                     (list  (plain-file
+                                             "test_bashrc_d.sh"
+                                             "export BASHRC_D_OK=yes\n")
+                                            (plain-file
+                                             "invalid-name"
+                                             "not a Bash script -- ignore me")))
                                     %base-services)))
                  #:imported-modules '((gnu services herd)
                                       (guix combinators))))
-- 
2.49.0