aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2014-05-11 22:41:01 +0200
committerLudovic Courtès <ludo@gnu.org>2014-05-11 22:41:58 +0200
commitab6a279abbfa39b1e1bec0e363744d241972f844 (patch)
tree658055e4ecf33226094c9b506251d0715827203b
parent057d6ce5e42d813b9d5e49ddae5d88e6581cc1d8 (diff)
downloadguix-ab6a279abbfa39b1e1bec0e363744d241972f844.tar
guix-ab6a279abbfa39b1e1bec0e363744d241972f844.tar.gz
system: Make accounts and groups at activation time.
* gnu/services/base.scm (guix-build-accounts): Remove #:gid parameter; add #:group. Remove 'password' and 'gid' fields in 'user-account' form, and add 'group'. (guix-service): Remove #:build-user-gid parameter. Remove 'id' field in 'user-group' form. * gnu/system.scm (etc-directory): Remove #:groups and #:accounts. No longer produce files "passwd", "shadow", and "group". Adjust caller accordingly. (%root-account): New variable. (operating-system-accounts): Add 'users' variable. Add %ROOT-ACCOUNT only of 'operating-system-users' doesn't already contain a root account. (user-group->gexp, user-account->gexp): New procedures. (operating-system-boot-script): Add calls to 'setenv' and 'activate-users+groups' in gexp. * gnu/system/linux.scm (base-pam-services): Add PAM services for "user{add,del,mode}" and "group{add,del,mod}". * gnu/system/shadow.scm (<user-account>)[gid]: Rename to... [group]: ... this. [supplementary-groups]: New field. [uid, password]: Default to #f. (<user-group>)[id]: Default to #f. (group-file, passwd-file): Remove. * gnu/system/vm.scm (operating-system-default-contents)[user-directories]: Remove. Add "/home" to the directives. * guix/build/activation.scm (add-group, add-user, activate-users+groups): New procedures.
-rw-r--r--build-aux/hydra/demo-os.scm3
-rw-r--r--gnu/services/base.scm10
-rw-r--r--gnu/system.scm95
-rw-r--r--gnu/system/linux.scm14
-rw-r--r--gnu/system/shadow.scm61
-rw-r--r--gnu/system/vm.scm15
-rw-r--r--guix/build/activation.scm97
7 files changed, 186 insertions, 109 deletions
diff --git a/build-aux/hydra/demo-os.scm b/build-aux/hydra/demo-os.scm
index 03449abda2..4116c063f4 100644
--- a/build-aux/hydra/demo-os.scm
+++ b/build-aux/hydra/demo-os.scm
@@ -45,7 +45,8 @@
(locale "en_US.UTF-8")
(users (list (user-account
(name "guest")
- (uid 1000) (gid 100)
+ (group "wheel")
+ (password "")
(comment "Guest of GNU")
(home-directory "/home/guest"))))
(groups (list (user-group (name "root") (id 0))
diff --git a/gnu/services/base.scm b/gnu/services/base.scm
index 6431a3aaba..1f5ff3e4cb 100644
--- a/gnu/services/base.scm
+++ b/gnu/services/base.scm
@@ -237,8 +237,8 @@ stopped before 'kill' is called."
(stop #~(make-kill-destructor))))))
(define* (guix-build-accounts count #:key
+ (group "guixbuild")
(first-uid 30001)
- (gid 30000)
(shadow shadow))
"Return a list of COUNT user accounts for Guix build users, with UIDs
starting at FIRST-UID, and under GID."
@@ -247,9 +247,8 @@ starting at FIRST-UID, and under GID."
(lambda (n)
(user-account
(name (format #f "guixbuilder~2,'0d" n))
- (password "!")
(uid (+ first-uid n -1))
- (gid gid)
+ (group group)
(comment (format #f "Guix Build User ~2d" n))
(home-directory "/var/empty")
(shell #~(string-append #$shadow "/sbin/nologin"))))
@@ -257,11 +256,11 @@ starting at FIRST-UID, and under GID."
1))))
(define* (guix-service #:key (guix guix) (builder-group "guixbuild")
- (build-user-gid 30000) (build-accounts 10))
+ (build-accounts 10))
"Return a service that runs the build daemon from GUIX, and has
BUILD-ACCOUNTS user accounts available under BUILD-USER-GID."
(mlet %store-monad ((accounts (guix-build-accounts build-accounts
- #:gid build-user-gid)))
+ #:group builder-group)))
(return (service
(provision '(guix-daemon))
(requirement '(user-processes))
@@ -274,7 +273,6 @@ BUILD-ACCOUNTS user accounts available under BUILD-USER-GID."
(user-accounts accounts)
(user-groups (list (user-group
(name builder-group)
- (id build-user-gid)
(members (map user-account-name
user-accounts)))))))))
diff --git a/gnu/system.scm b/gnu/system.scm
index d76c3670f0..bd69532a89 100644
--- a/gnu/system.scm
+++ b/gnu/system.scm
@@ -224,17 +224,12 @@ explicitly appear in OS."
(define* (etc-directory #:key
(locale "C") (timezone "Europe/Paris")
- (accounts '())
- (groups '())
(pam-services '())
(profile "/var/run/current-system/profile")
(sudoers ""))
"Return a derivation that builds the static part of the /etc directory."
(mlet* %store-monad
- ((passwd (passwd-file accounts))
- (shadow (passwd-file accounts #:shadow? #t))
- (group (group-file groups))
- (pam.d (pam-services->directory pam-services))
+ ((pam.d (pam-services->directory pam-services))
(sudoers (text-file "sudoers" sudoers))
(login.defs (text-file "login.defs" "# Empty for now.\n"))
(shells (text-file "shells" ; used by xterm and others
@@ -278,10 +273,6 @@ alias ll='ls -l'
("profile" ,#~#$bashrc)
("localtime" ,#~(string-append #$tzdata "/share/zoneinfo/"
#$timezone))
- ("passwd" ,#~#$passwd)
- ("shadow" ,#~#$shadow)
- ("group" ,#~#$group)
-
("sudoers" ,#~#$sudoers)))))
(define (operating-system-profile os)
@@ -290,18 +281,28 @@ alias ll='ls -l'
(union (operating-system-packages os)
#:name "default-profile"))
+(define %root-account
+ ;; Default root account.
+ (user-account
+ (name "root")
+ (password "")
+ (uid 0) (group "root")
+ (comment "System administrator")
+ (home-directory "/root")))
+
(define (operating-system-accounts os)
"Return the user accounts for OS, including an obligatory 'root' account."
+ (define users
+ ;; Make sure there's a root account.
+ (if (find (lambda (user)
+ (and=> (user-account-uid user) zero?))
+ (operating-system-users os))
+ (operating-system-users os)
+ (cons %root-account (operating-system-users os))))
+
(mlet %store-monad ((services (operating-system-services os)))
- (return (cons (user-account
- (name "root")
- (password "")
- (uid 0) (gid 0)
- (comment "System administrator")
- (home-directory "/root"))
- (append (operating-system-users os)
- (append-map service-user-accounts
- services))))))
+ (return (append users
+ (append-map service-user-accounts services)))))
(define (operating-system-etc-directory os)
"Return that static part of the /etc directory of OS."
@@ -312,12 +313,8 @@ alias ll='ls -l'
(delete-duplicates
(append (operating-system-pam-services os)
(append-map service-pam-services services))))
- (accounts (operating-system-accounts os))
- (profile-drv (operating-system-profile os))
- (groups -> (append (operating-system-groups os)
- (append-map service-user-groups services))))
- (etc-directory #:accounts accounts #:groups groups
- #:pam-services pam-services
+ (profile-drv (operating-system-profile os)))
+ (etc-directory #:pam-services pam-services
#:locale (operating-system-locale os)
#:timezone (operating-system-timezone os)
#:sudoers (operating-system-sudoers os)
@@ -339,6 +336,25 @@ alias ll='ls -l'
"root ALL=(ALL) ALL
%wheel ALL=(ALL) ALL\n")
+(define (user-group->gexp group)
+ "Turn GROUP, a <user-group> object, into a list-valued gexp suitable for
+'active-groups'."
+ #~(list #$(user-group-name group)
+ #$(user-group-password group)
+ #$(user-group-id group)))
+
+(define (user-account->gexp account)
+ "Turn ACCOUNT, a <user-account> object, into a list-valued gexp suitable for
+'activate-users'."
+ #~`(#$(user-account-name account)
+ #$(user-account-uid account)
+ #$(user-account-group account)
+ #$(user-account-supplementary-groups account)
+ #$(user-account-comment account)
+ #$(user-account-home-directory account)
+ ,#$(user-account-shell account) ; this one is a gexp
+ #$(user-account-password account)))
+
(define (operating-system-boot-script os)
"Return the boot script for OS---i.e., the code started by the initrd once
we're running in the final root."
@@ -346,15 +362,25 @@ we're running in the final root."
'((guix build activation)
(guix build utils)))
- (mlet* %store-monad
- ((services (operating-system-services os))
- (etc (operating-system-etc-directory os))
- (modules (imported-modules %modules))
- (compiled (compiled-modules %modules))
- (dmd-conf (dmd-configuration-file services)))
+ (mlet* %store-monad ((services (operating-system-services os))
+ (etc (operating-system-etc-directory os))
+ (modules (imported-modules %modules))
+ (compiled (compiled-modules %modules))
+ (dmd-conf (dmd-configuration-file services))
+ (accounts (operating-system-accounts os)))
(define setuid-progs
(operating-system-setuid-programs os))
+ (define user-specs
+ (map user-account->gexp accounts))
+
+ (define groups
+ (append (operating-system-groups os)
+ (append-map service-user-groups services)))
+
+ (define group-specs
+ (map user-group->gexp groups))
+
(gexp->file "boot"
#~(begin
(eval-when (expand load eval)
@@ -368,6 +394,13 @@ we're running in the final root."
;; Populate /etc.
(activate-etc #$etc)
+ ;; Add users and user groups.
+ (setenv "PATH"
+ (string-append #$(@ (gnu packages admin) shadow)
+ "/sbin"))
+ (activate-users+groups (list #$@user-specs)
+ (list #$@group-specs))
+
;; Activate setuid programs.
(activate-setuid-programs (list #$@setuid-progs))
diff --git a/gnu/system/linux.scm b/gnu/system/linux.scm
index 3a43eb45e3..5440f5852f 100644
--- a/gnu/system/linux.scm
+++ b/gnu/system/linux.scm
@@ -154,11 +154,13 @@ should be the name of a file used as the message-of-the-day."
(define* (base-pam-services #:key allow-empty-passwords?)
"Return the list of basic PAM services everyone would want."
- (list %pam-other-services
- (unix-pam-service "su" #:allow-empty-passwords? allow-empty-passwords?)
- (unix-pam-service "passwd"
- #:allow-empty-passwords? allow-empty-passwords?)
- (unix-pam-service "sudo"
- #:allow-empty-passwords? allow-empty-passwords?)))
+ (cons %pam-other-services
+ (map (cut unix-pam-service <>
+ #:allow-empty-passwords? allow-empty-passwords?)
+ '("su" "passwd" "sudo"
+ "useradd" "userdel" "usermod"
+ "groupadd" "groupdel" "groupmod"
+ ;; TODO: Add other Shadow programs?
+ ))))
;;; linux.scm ends here
diff --git a/gnu/system/shadow.scm b/gnu/system/shadow.scm
index 52242ee4e0..8745ddb876 100644
--- a/gnu/system/shadow.scm
+++ b/gnu/system/shadow.scm
@@ -30,9 +30,10 @@
#:export (user-account
user-account?
user-account-name
- user-account-pass
+ user-account-password
user-account-uid
- user-account-gid
+ user-account-group
+ user-account-supplementary-groups
user-account-comment
user-account-home-directory
user-account-shell
@@ -42,11 +43,7 @@
user-group-name
user-group-password
user-group-id
- user-group-members
-
- passwd-file
- group-file
- guix-build-accounts))
+ user-group-members))
;;; Commentary:
;;;
@@ -58,9 +55,11 @@
user-account make-user-account
user-account?
(name user-account-name)
- (password user-account-pass (default ""))
- (uid user-account-uid)
- (gid user-account-gid)
+ (password user-account-password (default #f))
+ (uid user-account-uid (default #f))
+ (group user-account-group) ; number | string
+ (supplementary-groups user-account-supplementary-groups
+ (default '())) ; list of strings
(comment user-account-comment (default ""))
(home-directory user-account-home-directory)
(shell user-account-shell ; gexp
@@ -71,47 +70,7 @@
user-group?
(name user-group-name)
(password user-group-password (default #f))
- (id user-group-id)
+ (id user-group-id (default #f))
(members user-group-members (default '())))
-(define (group-file groups)
- "Return a /etc/group file for GROUPS, a list of <user-group> objects."
- (define contents
- (let loop ((groups groups)
- (result '()))
- (match groups
- ((($ <user-group> name _ gid (users ...)) rest ...)
- ;; XXX: Ignore the group password.
- (loop rest
- (cons (string-append name "::" (number->string gid)
- ":" (string-join users ","))
- result)))
- (()
- (string-join (reverse result) "\n" 'suffix)))))
-
- (text-file "group" contents))
-
-(define* (passwd-file accounts #:key shadow?)
- "Return a password file for ACCOUNTS, a list of <user-account> objects. If
-SHADOW? is true, then it is a /etc/shadow file, otherwise it is a /etc/passwd
-file."
- ;; XXX: The resulting file is world-readable, so beware when SHADOW? is #t!
- (define account-exp
- (match-lambda
- (($ <user-account> name pass uid gid comment home-dir shell)
- (if shadow? ; XXX: use (crypt PASS …)?
- #~(format #t "~a::::::::~%" #$name)
- #~(format #t "~a:x:~a:~a:~a:~a:~a~%"
- #$name #$(number->string uid) #$(number->string gid)
- #$comment #$home-dir #$shell)))))
-
- (define builder
- #~(begin
- (with-output-to-file #$output
- (lambda ()
- #$@(map account-exp accounts)
- #t))))
-
- (gexp->derivation (if shadow? "shadow" "passwd") builder))
-
;;; shadow.scm ends here
diff --git a/gnu/system/vm.scm b/gnu/system/vm.scm
index 2520853205..ede7ea7726 100644
--- a/gnu/system/vm.scm
+++ b/gnu/system/vm.scm
@@ -267,16 +267,6 @@ such as /etc files."
(define (operating-system-default-contents os)
"Return a list of directives suitable for 'system-qemu-image' describing the
basic contents of the root file system of OS."
- (define (user-directories user)
- (let ((home (user-account-home-directory user))
- ;; XXX: Deal with automatically allocated ids.
- (uid (or (user-account-uid user) 0))
- (gid (or (user-account-gid user) 0))
- (root (string-append "/var/guix/profiles/per-user/"
- (user-account-name user))))
- #~((directory #$root #$uid #$gid)
- (directory #$home #$uid #$gid))))
-
(mlet* %store-monad ((os-drv (operating-system-derivation os))
(build-gid (operating-system-build-gid os))
(profile (operating-system-profile os)))
@@ -293,9 +283,8 @@ basic contents of the root file system of OS."
(directory "/tmp")
(directory "/var/guix/profiles/per-user/root" 0 0)
- (directory "/root" 0 0) ; an exception
- #$@(append-map user-directories
- (operating-system-users os))))))
+ (directory "/root" 0 0) ; an exception
+ (directory "/home" 0 0)))))
(define* (system-qemu-image os
#:key
diff --git a/guix/build/activation.scm b/guix/build/activation.scm
index f9d9ba5cbd..895f2bca5b 100644
--- a/guix/build/activation.scm
+++ b/guix/build/activation.scm
@@ -19,8 +19,11 @@
(define-module (guix build activation)
#:use-module (guix build utils)
#:use-module (ice-9 ftw)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
- #:export (activate-etc
+ #:export (activate-users+groups
+ activate-etc
activate-setuid-programs))
;;; Commentary:
@@ -31,6 +34,98 @@
;;;
;;; Code:
+(define* (add-group name #:key gid password
+ (log-port (current-error-port)))
+ "Add NAME as a user group, with the given numeric GID if specified."
+ ;; Use 'groupadd' from the Shadow package.
+ (format log-port "adding group '~a'...~%" name)
+ (let ((args `(,@(if gid `("-g" ,(number->string gid)) '())
+ ,@(if password `("-p" ,password) '())
+ ,name)))
+ (zero? (apply system* "groupadd" args))))
+
+(define* (add-user name group
+ #:key uid comment home shell password
+ (supplementary-groups '())
+ (log-port (current-error-port)))
+ "Create an account for user NAME part of GROUP, with the specified
+properties. Return #t on success."
+ (format log-port "adding user '~a'...~%" name)
+
+ (if (and uid (zero? uid))
+
+ ;; 'useradd' fails with "Cannot determine your user name" if the root
+ ;; account doesn't exist. Thus, for bootstrapping purposes, create that
+ ;; one manually.
+ (begin
+ (call-with-output-file "/etc/shadow"
+ (cut format <> "~a::::::::~%" name))
+ (call-with-output-file "/etc/passwd"
+ (cut format <> "~a:x:~a:~a:~a:~a:~a~%"
+ name "0" "0" comment home shell))
+ (chmod "/etc/shadow" #o600)
+ #t)
+
+ ;; Use 'useradd' from the Shadow package.
+ (let ((args `(,@(if uid `("-u" ,(number->string uid)) '())
+ "-g" ,(if (number? group) (number->string group) group)
+ ,@(if (pair? supplementary-groups)
+ `("-G" ,(string-join supplementary-groups ","))
+ '())
+ ,@(if comment `("-c" ,comment) '())
+ ,@(if home `("-d" ,home "--create-home") '())
+ ,@(if shell `("-s" ,shell) '())
+ ,@(if password `("-p" ,password) '())
+ ,name)))
+ (zero? (apply system* "useradd" args)))))
+
+(define (activate-users+groups users groups)
+ "Make sure the accounts listed in USERS and the user groups listed in GROUPS
+are all available.
+
+Each item in USERS is a list of all the characteristics of a user account;
+each item in GROUPS is a tuple with the group name, group password or #f, and
+numeric gid or #f."
+ (define (touch file)
+ (call-with-output-file file (const #t)))
+
+ (define activate-user
+ (match-lambda
+ ((name uid group supplementary-groups comment home shell password)
+ (unless (false-if-exception (getpwnam name))
+ (let ((profile-dir (string-append "/var/guix/profiles/per-user/"
+ name)))
+ (add-user name group
+ #:uid uid
+ #:supplementary-groups supplementary-groups
+ #:comment comment
+ #:home home
+ #:shell shell
+ #:password password)
+
+ ;; Create the profile directory for the new account.
+ (let ((pw (getpwnam name)))
+ (mkdir-p profile-dir)
+ (chown profile-dir (passwd:uid pw) (passwd:gid pw))))))))
+
+ ;; 'groupadd' aborts if the file doesn't already exist.
+ (touch "/etc/group")
+
+ ;; Create the root account so we can use 'useradd' and 'groupadd'.
+ (activate-user (find (match-lambda
+ ((name (? zero?) _ ...) #t)
+ (_ #f))
+ users))
+
+ ;; Then create the groups.
+ (for-each (match-lambda
+ ((name password gid)
+ (add-group name #:gid gid #:password password)))
+ groups)
+
+ ;; Finally create the other user accounts.
+ (for-each activate-user users))
+
(define (activate-etc etc)
"Install ETC, a directory in the store, as the source of static files for
/etc."