summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.dir-locals.el1
-rw-r--r--.gitignore1
-rw-r--r--doc/guix.texi31
-rw-r--r--gnu/packages/gnome.scm146
-rw-r--r--gnu/packages/xml.scm33
-rw-r--r--gnu/system.scm4
-rw-r--r--gnu/system/examples/bare-bones.tmpl5
-rw-r--r--gnu/system/examples/desktop.tmpl5
-rw-r--r--gnu/system/shadow.scm46
-rw-r--r--guix/scripts/package.scm14
-rw-r--r--guix/scripts/system.scm28
-rw-r--r--guix/ui.scm103
-rw-r--r--po/guix/POTFILES.in1
-rw-r--r--tests/guix-package.sh18
-rw-r--r--tests/guix-system.sh65
15 files changed, 462 insertions, 39 deletions
diff --git a/.dir-locals.el b/.dir-locals.el
index eb3da94da4..7ac7e13ff1 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -13,6 +13,7 @@
   .
   ((indent-tabs-mode . nil)
    (eval . (put 'eval-when 'scheme-indent-function 1))
+   (eval . (put 'call-with-prompt 'scheme-indent-function 1))
    (eval . (put 'test-assert 'scheme-indent-function 1))
    (eval . (put 'test-assertm 'scheme-indent-function 1))
    (eval . (put 'test-equal 'scheme-indent-function 1))
diff --git a/.gitignore b/.gitignore
index 47cb39584e..7db4a9f9aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,6 +62,7 @@ config.cache
 /pre-inst-env
 /doc/.dirstamp
 /doc/guix.info
+/doc/guix.info-[0-9]
 /doc/guix.pdf
 /doc/stamp-vti
 /doc/version.texi
diff --git a/doc/guix.texi b/doc/guix.texi
index cfb626c705..a97436cc0c 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -4413,7 +4413,7 @@ configuration (@pxref{Using the Configuration System}).
 
 @table @asis
 @item @code{kernel} (default: @var{linux-libre})
-The package object of the operating system to use@footnote{Currently
+The package object of the operating system kernel to use@footnote{Currently
 only the Linux-libre kernel is supported.  In the future, it will be
 possible to use the GNU@tie{}Hurd.}.
 
@@ -4453,7 +4453,7 @@ A list of strings identifying devices to be used for ``swap space''
 (@pxref{Memory Concepts,,, libc, The GNU C Library Reference Manual}).
 For example, @code{'("/dev/sda3")}.
 
-@item @code{users} (default: @code{'()})
+@item @code{users} (default: @code{%base-user-accounts})
 @itemx @code{groups} (default: @var{%base-groups})
 List of user accounts and groups.  @xref{User Accounts}.
 
@@ -4832,6 +4832,14 @@ to be present on the system.  This includes groups such as ``root'',
 specific devices such as ``audio'', ``disk'', and ``cdrom''.
 @end defvr
 
+@defvr {Scheme Variable} %base-user-accounts
+This is the list of basic system accounts that programs may expect to
+find on a GNU/Linux system, such as the ``nobody'' account.
+
+Note that the ``root'' account is not included here.  It is a
+special-case and is automatically added whether or not it is specified.
+@end defvr
+
 @node Locales
 @subsection Locales
 
@@ -5987,6 +5995,25 @@ For the @code{vm-image} and @code{disk-image} actions, create an image
 of the given @var{size}.  @var{size} may be a number of bytes, or it may
 include a unit as a suffix (@pxref{Block size, size specifications,,
 coreutils, GNU Coreutils}).
+
+@item --on-error=@var{strategy}
+Apply @var{strategy} when an error occurs when reading @var{file}.
+@var{strategy} may be one of the following:
+
+@table @code
+@item nothing-special
+Report the error concisely and exit.  This is the default strategy.
+
+@item backtrace
+Likewise, but also display a backtrace.
+
+@item debug
+Report the error and enter Guile's debugger.  From there, you can run
+commands such as @code{,bt} to get a backtrace, @code{,locals} to
+display local variable values, and more generally inspect the program's
+state.  @xref{Debug Commands,,, guile, GNU Guile Reference Manual}, for
+a list of available debugging commands.
+@end table
 @end table
 
 Note that all the actions above, except @code{build} and @code{init},
diff --git a/gnu/packages/gnome.scm b/gnu/packages/gnome.scm
index eefe75de1e..35af94f4fc 100644
--- a/gnu/packages/gnome.scm
+++ b/gnu/packages/gnome.scm
@@ -7,6 +7,7 @@
 ;;; Copyright © 2015 Sou Bunnbu <iyzsong@gmail.com>
 ;;; Copyright © 2015 Andy Wingo <wingo@igalia.com>
 ;;; Copyright © 2015 David Hashe <david.hashe@dhashe.com>
+;;; Copyright © 2015 Ricardo Wurmus <rekado@elephly.net>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -60,6 +61,7 @@
   #:use-module (gnu packages pulseaudio)
   #:use-module (gnu packages python)
   #:use-module (gnu packages scanner)
+  #:use-module (gnu packages ssh)
   #:use-module (gnu packages xml)
   #:use-module (gnu packages gl)
   #:use-module (gnu packages compression)
@@ -181,6 +183,50 @@ Gnome project.  It includes xml2po tool which makes it easier to translate
 and keep up to date translations of documentation.")
     (license license:gpl2+))) ; xslt under lgpl
 
+(define-public gcr
+  (package
+    (name "gcr")
+    (version "3.16.0")
+    (source (origin
+             (method url-fetch)
+             (uri (string-append "mirror://gnome/sources/" name "/"
+                                 (version-major+minor version)  "/"
+                                 name "-" version ".tar.xz"))
+             (sha256
+              (base32
+               "0xfhi0w358lvca1jjx24x2gm67mif33dsnmi9cv5i0f83ks8vzpc"))))
+    (build-system gnu-build-system)
+    (arguments
+     '(#:tests? #f ;25 of 598 tests fail because /var/lib/dbus/machine-id does
+                   ;not exist
+       #:phases (modify-phases %standard-phases
+                  (add-before
+                   'check 'pre-check
+                   (lambda* (#:key inputs #:allow-other-keys)
+                     (substitute* "build/tap-driver"
+                       (("/usr/bin/env python") (which "python"))))))))
+    (inputs
+     `(("dbus" ,dbus)
+       ("gnupg" ,gnupg) ;called as a child process during tests
+       ("libgcrypt" ,libgcrypt)))
+    (native-inputs
+     `(("python" ,python-2) ;for tests
+       ("pkg-config" ,pkg-config)
+       ("glib" ,glib "bin")
+       ("intltool" ,intltool)))
+    ;; mentioned in gck.pc, gcr.pc and gcr-ui.pc
+    (propagated-inputs
+     `(("p11-kit" ,p11-kit)
+       ("glib" ,glib)
+       ("gtk+" ,gtk+)))
+    (home-page "http://www.gnome.org")
+    (synopsis "Libraries for displaying certificates and accessing key stores")
+    (description
+     "The GCR package contains libraries used for displaying certificates and
+accessing key stores.  It also provides the viewer for crypto files on the
+GNOME Desktop.")
+    (license license:lgpl2.1+)))
+
 (define-public libgnome-keyring
   (package
     (name "libgnome-keyring")
@@ -212,6 +258,73 @@ and keep up to date translations of documentation.")
     ;; Though a couple of files are LGPLv2.1+.
     (license license:lgpl2.0+)))
 
+(define-public gnome-keyring
+  (package
+    (name "gnome-keyring")
+    (version "3.16.0")
+    (source (origin
+             (method url-fetch)
+             (uri (string-append "mirror://gnome/sources/" name "/"
+                                 (version-major+minor version)  "/"
+                                 name "-" version ".tar.xz"))
+             (sha256
+              (base32
+               "1xg1xha3x3hzlmvdq2zm90hc61pj7pnf9yxxvgq4ynl5af6bp8qm"))))
+    (build-system gnu-build-system)
+    (arguments
+     `(#:tests? #f ;48 of 603 tests fail because /var/lib/dbus/machine-id does
+                   ;not exist
+       #:configure-flags
+       (list
+        (string-append "--with-pkcs11-config="
+                       (assoc-ref %outputs "out") "/share/p11-kit/modules/")
+        (string-append "--with-pkcs11-modules="
+                       (assoc-ref %outputs "out") "/share/p11-kit/modules/"))
+       #:phases
+       (modify-phases %standard-phases
+         (add-before
+          'check 'pre-check
+          (lambda* (#:key inputs #:allow-other-keys)
+            (substitute* "build/tap-driver"
+              (("/usr/bin/env python") (which "python")))))
+         (add-before
+          'configure 'fix-docbook
+          (lambda* (#:key inputs #:allow-other-keys)
+            (substitute* "docs/Makefile.am"
+              (("http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl")
+               (string-append (assoc-ref inputs "docbook-xsl")
+                              "/xml/xsl/docbook-xsl-"
+                              ,(package-version docbook-xsl)
+                              "/manpages/docbook.xsl")))
+            (setenv "XML_CATALOG_FILES"
+                    (string-append (assoc-ref inputs "docbook-xml")
+                                   "/xml/dtd/docbook/catalog.xml")))))))
+    (inputs
+     `(("libgcrypt" ,libgcrypt)
+       ("dbus" ,dbus)
+       ("gcr" ,gcr)))
+    (native-inputs
+     `(("pkg-config" ,pkg-config)
+       ("glib" ,glib "bin")
+       ("python" ,python-2) ;for tests
+       ("intltool" ,intltool)
+       ("autoconf" ,autoconf)
+       ("automake" ,automake)
+       ("libxslt" ,libxslt) ;for documentation
+       ("docbook-xml" ,docbook-xml-4.2)
+       ("docbook-xsl" ,docbook-xsl)))
+    (home-page "http://www.gnome.org")
+    (synopsis "Daemon to store passwords and encryption keys")
+    (description
+     "gnome-keyring is a program that keeps passwords and other secrets for
+users.  It is run as a daemon in the session, similar to ssh-agent, and other
+applications locate it via an environment variable or D-Bus.
+
+The program can manage several keyrings, each with its own master password,
+and there is also a session keyring which is never stored to disk, but
+forgotten when the session ends.")
+    (license license:lgpl2.1+)))
+
 (define-public evince
   (package
     (name "evince")
@@ -1412,6 +1525,39 @@ engineering.")
      "The default GNOME 3 themes (Adwaita and some accessibility themes).")
     (license license:lgpl2.1+)))
 
+(define-public seahorse
+  (package
+    (name "seahorse")
+    (version "3.16.0")
+    (source
+     (origin
+       (method url-fetch)
+       (uri (string-append "mirror://gnome/sources/" name "/"
+                           (version-major+minor version) "/" name "-"
+                           version ".tar.xz"))
+       (sha256
+        (base32
+         "0cg1grgpwbfkiny5148n17rzpc8kswyr5yff0kpm8l3lp01my2kp"))))
+    (build-system glib-or-gtk-build-system)
+    (inputs
+     `(("gtk+" ,gtk+)
+       ("gcr" ,gcr)
+       ("gnupg" ,gnupg-1)
+       ("gpgme" ,gpgme)
+       ("openssh" ,openssh)
+       ("libsecret" ,libsecret)))
+    (native-inputs
+     `(("intltool" ,intltool)
+       ("glib:bin" ,glib "bin")
+       ("itstool" ,itstool)
+       ("pkg-config" ,pkg-config)))
+    (home-page "https://launchpad.net/gnome-themes-standard")
+    (synopsis "Manage encryption keys and passwords in the GNOME keyring")
+    (description
+     "Seahorse is a GNOME application for managing encryption keys and
+passwords in the GNOME keyring.")
+    (license license:gpl2+)))
+
 (define-public vala
   (package
     (name "vala")
diff --git a/gnu/packages/xml.scm b/gnu/packages/xml.scm
index d30c40ce1a..8a4d2fbb5b 100644
--- a/gnu/packages/xml.scm
+++ b/gnu/packages/xml.scm
@@ -1,6 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2013, 2014, 2015 Ludovic Courtès <ludo@gnu.org>
-;;; Copyright © 2013 Andreas Enge <andreas@enge.fr>
+;;; Copyright © 2013, 2015 Andreas Enge <andreas@enge.fr>
 ;;; Copyright © 2015 Eric Bavier <bavier@member.fsf.org>
 ;;;
 ;;; This file is part of GNU Guix.
@@ -20,8 +20,10 @@
 
 (define-module (gnu packages xml)
   #:use-module (gnu packages)
+  #:use-module (gnu packages autotools)
   #:use-module (gnu packages compression)
   #:use-module (gnu packages gnupg)
+  #:use-module (gnu packages gnutls)
   #:use-module (gnu packages perl)
   #:use-module (gnu packages python)
   #:use-module (gnu packages web)
@@ -387,3 +389,32 @@ that conforms to the API of the Document Object Model.")
 stylesheet for the conversion you want and applies it using an external
 XSL-T processor.  It also performs any necessary post-processing.")
     (license license:gpl2+)))
+
+(define-public xmlsec
+  (package
+    (name "xmlsec")
+    (version "1.2.20")
+    (source (origin
+             (method url-fetch)
+             (uri (string-append "https://www.aleksey.com/xmlsec/download/"
+                                 name "1-" version ".tar.gz"))
+             (sha256
+              (base32
+               "01bkbv2y3x8d1sf4dcln1x3y2jyj391s3208d9a2ndhglly5j89j"))))
+    (build-system gnu-build-system)
+    (propagated-inputs ; according to xmlsec1.pc
+     `(("libxml2" ,libxml2)
+       ("libxslt" ,libxslt)))
+    (inputs
+     `(("gnutls" ,gnutls)
+       ("libgcrypt" ,libgcrypt)
+       ("libltdl" ,libltdl)))
+    (home-page "http://www.libexpat.org/")
+    (synopsis "XML Security Library")
+    (description
+     "The XML Security Library is a C library based on Libxml2.  It
+supports XML security standards such as XML Signature, XML Encryption,
+Canonical XML (part of Libxml2) and Exclusive Canonical XML (part of
+Libxml2).")
+    (license (license:x11-style "file://COPYING"
+                                "See 'COPYING' in the distribution."))))
diff --git a/gnu/system.scm b/gnu/system.scm
index b8d0e62f60..c4a3bee0eb 100644
--- a/gnu/system.scm
+++ b/gnu/system.scm
@@ -119,7 +119,7 @@
                 (default '()))
 
   (users operating-system-users                   ; list of user accounts
-         (default '()))
+         (default %base-user-accounts))
   (groups operating-system-groups                 ; list of user groups
           (default %base-groups))
 
@@ -686,6 +686,8 @@ etc."
     (define group-specs
       (map user-group->gexp groups))
 
+    (assert-valid-users/groups accounts groups)
+
     (gexp->file "activate"
                 #~(begin
                     (eval-when (expand load eval)
diff --git a/gnu/system/examples/bare-bones.tmpl b/gnu/system/examples/bare-bones.tmpl
index 8f4faca2d3..dc5cfc81a4 100644
--- a/gnu/system/examples/bare-bones.tmpl
+++ b/gnu/system/examples/bare-bones.tmpl
@@ -23,7 +23,7 @@
   ;; This is where user accounts are specified.  The "root"
   ;; account is implicit, and is initially created with the
   ;; empty password.
-  (users (list (user-account
+  (users (cons (user-account
                 (name "alice")
                 (comment "Bob's sister")
                 (group "users")
@@ -34,7 +34,8 @@
                 ;; and access the webcam.
                 (supplementary-groups '("wheel"
                                         "audio" "video"))
-                (home-directory "/home/alice"))))
+                (home-directory "/home/alice"))
+               %base-user-accounts))
 
   ;; Globally-installed packages.
   (packages (cons tcpdump %base-packages))
diff --git a/gnu/system/examples/desktop.tmpl b/gnu/system/examples/desktop.tmpl
index c78188eb61..988b8f937f 100644
--- a/gnu/system/examples/desktop.tmpl
+++ b/gnu/system/examples/desktop.tmpl
@@ -20,13 +20,14 @@
                         (type "ext4"))
                       %base-file-systems))
 
-  (users (list (user-account
+  (users (cons (user-account
                 (name "bob")
                 (comment "Alice's brother")
                 (group "users")
                 (supplementary-groups '("wheel" "netdev"
                                         "audio" "video"))
-                (home-directory "/home/bob"))))
+                (home-directory "/home/bob"))
+               %base-user-accounts))
 
   ;; Add Xfce and Ratpoison; that allows us to choose
   ;; sessions using either of these at the log-in screen.
diff --git a/gnu/system/shadow.scm b/gnu/system/shadow.scm
index 16b9e4b555..aa97652678 100644
--- a/gnu/system/shadow.scm
+++ b/gnu/system/shadow.scm
@@ -21,12 +21,17 @@
   #:use-module (guix gexp)
   #:use-module (guix store)
   #:use-module (guix monads)
+  #:use-module (guix sets)
+  #:use-module (guix ui)
   #:use-module ((gnu system file-systems)
                 #:select (%tty-gid))
   #:use-module ((gnu packages admin)
                 #:select (shadow))
   #:use-module (gnu packages bash)
   #:use-module (gnu packages guile-wm)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-34)
+  #:use-module (srfi srfi-35)
   #:export (user-account
             user-account?
             user-account-name
@@ -48,7 +53,9 @@
 
             default-skeletons
             skeleton-directory
-            %base-groups))
+            %base-groups
+            %base-user-accounts
+            assert-valid-users/groups))
 
 ;;; Commentary:
 ;;;
@@ -107,6 +114,16 @@
           (system-group (name "tape"))
           (system-group (name "kvm")))))             ; for /dev/kvm
 
+(define %base-user-accounts
+  ;; List of standard user accounts.  Note that "root" is a special case, so
+  ;; it's not listed here.
+  (list (user-account
+         (name "nobody")
+         (uid 65534)
+         (group "nogroup")
+         (home-directory "/var/empty")
+         (system? #t))))
+
 (define (default-skeletons)
   "Return the default skeleton files for /etc/skel.  These files are copied by
 'useradd' in the home directory of newly created user accounts."
@@ -176,4 +193,31 @@ set debug-file-directory ~/.guix-profile/lib/debug\n")))
                                   '#$skeletons)
                         #t)))
 
+(define (assert-valid-users/groups users groups)
+  "Raise an error if USERS refer to groups not listed in GROUPS."
+  (let ((groups (list->set (map user-group-name groups))))
+    (define (validate-supplementary-group user group)
+      (unless (set-contains? groups group)
+        (raise (condition
+                (&message
+                 (message
+                  (format #f (_ "supplementary group '~a' \
+of user '~a' is undeclared")
+                          group
+                          (user-account-name user))))))))
+
+    (for-each (lambda (user)
+                (unless (set-contains? groups (user-account-group user))
+                  (raise (condition
+                          (&message
+                           (message
+                            (format #f (_ "primary group '~a' \
+of user '~a' is undeclared")
+                                    (user-account-group user)
+                                    (user-account-name user)))))))
+
+                (for-each (cut validate-supplementary-group user <>)
+                          (user-account-supplementary-groups user)))
+              users)))
+
 ;;; shadow.scm ends here
diff --git a/guix/scripts/package.scm b/guix/scripts/package.scm
index 06ee441799..9da6b9ec1e 100644
--- a/guix/scripts/package.scm
+++ b/guix/scripts/package.scm
@@ -886,14 +886,16 @@ more information.~%"))
                (alist-delete 'delete-generations opts)))
              (_ #f))
             opts))
-          ((and (assoc-ref opts 'manifest)
-                (not dry-run?))
-           (let* ((file-name (assoc-ref opts 'manifest))
+          ((assoc-ref opts 'manifest)
+           (let* ((file-name   (assoc-ref opts 'manifest))
                   (user-module (make-user-module '((guix profiles)
                                                    (gnu))))
-                  (manifest (load* file-name user-module)))
-             (format #t (_ "installing new manifest from ~a with ~d entries.~%")
-                     file-name (length (manifest-entries manifest)))
+                  (manifest    (load* file-name user-module)))
+             (if (assoc-ref opts 'dry-run?)
+                 (format #t (_ "would install new manifest from '~a' with ~d entries~%")
+                         file-name (length (manifest-entries manifest)))
+                 (format #t (_ "installing new manifest from '~a' with ~d entries~%")
+                         file-name (length (manifest-entries manifest))))
              (build-and-use-profile manifest)))
           (else
            (let* ((manifest    (profile-manifest profile))
diff --git a/guix/scripts/system.scm b/guix/scripts/system.scm
index 1feb821389..b6d7d0d045 100644
--- a/guix/scripts/system.scm
+++ b/guix/scripts/system.scm
@@ -128,8 +128,9 @@ TARGET, and register them."
 (define* (install os-drv target
                   #:key (log-port (current-output-port))
                   grub? grub.cfg device)
-  "Copy the output of OS-DRV and its dependencies to directory TARGET.  TARGET
-must be an absolute directory name since that's what 'guix-register' expects.
+  "Copy the closure of GRUB.CFG, which includes the output of OS-DRV, to
+directory TARGET.  TARGET must be an absolute directory name since that's what
+'guix-register' expects.
 
 When GRUB? is true, install GRUB on DEVICE, using GRUB.CFG."
   (define (maybe-copy to-copy)
@@ -160,7 +161,9 @@ the ownership of '~a' may be incorrect!~%")
         (populate (lift2 populate-root-file-system %store-monad)))
 
     (mbegin %store-monad
-      (maybe-copy os-dir)
+      ;; Copy the closure of GRUB.CFG, which includes OS-DIR, GRUB's
+      ;; background image and so on.
+      (maybe-copy grub.cfg)
 
       ;; Create a bunch of additional files.
       (format log-port "populating '~a'...~%" target)
@@ -284,10 +287,6 @@ it atomically, and then run OS's activation script."
     ((disk-image)
      (system-disk-image os #:disk-image-size image-size))))
 
-(define (grub.cfg os)
-  "Return the GRUB configuration file for OS."
-  (operating-system-grub.cfg os (previous-grub-entries)))
-
 (define* (maybe-build drvs
                       #:key dry-run? use-substitutes?)
   "Show what will/would be built, and actually build DRVS, unless DRY-RUN? is
@@ -317,7 +316,10 @@ boot directly to the kernel or to the bootloader."
                                                 #:full-boot? full-boot?
                                                 #:mappings mappings))
        (grub      (package->derivation grub))
-       (grub.cfg  (grub.cfg os))
+       (grub.cfg  (operating-system-grub.cfg os
+                                             (if (eq? 'init action)
+                                                 '()
+                                                 (previous-grub-entries))))
        (drvs   -> (if (and grub? (memq action '(init reconfigure)))
                       (list sys grub grub.cfg)
                       (list sys)))
@@ -381,6 +383,9 @@ Build the operating system declared in FILE according to ACTION.\n"))
 
   (show-build-options-help)
   (display (_ "
+      --on-error=STRATEGY
+                         apply STRATEGY when an error occurs while reading FILE"))
+  (display (_ "
       --image-size=SIZE  for 'vm-image', produce an image of SIZE"))
   (display (_ "
       --no-grub          for 'init', do not install GRUB"))
@@ -420,6 +425,10 @@ Build the operating system declared in FILE according to ACTION.\n"))
          (option '(#\V "version") #f #f
                  (lambda args
                    (show-version-and-exit "guix system")))
+         (option '("on-error") #t #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'on-error (string->symbol arg)
+                               result)))
          (option '("image-size") #t #f
                  (lambda (opt name arg result)
                    (alist-cons 'image-size (size->number arg)
@@ -512,7 +521,8 @@ Build the operating system declared in FILE according to ACTION.\n"))
            (action   (assoc-ref opts 'action))
            (system   (assoc-ref opts 'system))
            (os       (if file
-                         (read-operating-system file)
+                         (load* file %user-module
+                                #:on-error (assoc-ref opts 'on-error))
                          (leave (_ "no configuration file specified~%"))))
 
            (dry?     (assoc-ref opts 'dry-run?))
diff --git a/guix/ui.scm b/guix/ui.scm
index 9bab7c51dd..7490de080c 100644
--- a/guix/ui.scm
+++ b/guix/ui.scm
@@ -35,6 +35,7 @@
   #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-19)
   #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-31)
   #:use-module (srfi srfi-34)
   #:use-module (srfi srfi-35)
   #:use-module (srfi srfi-37)
@@ -42,6 +43,8 @@
   #:use-module (ice-9 match)
   #:use-module (ice-9 format)
   #:use-module (ice-9 regex)
+  #:autoload   (system repl repl)  (start-repl)
+  #:autoload   (system repl debug) (make-debug stack->vector)
   #:replace (symlink)
   #:export (_
             N_
@@ -50,7 +53,6 @@
             leave
             make-user-module
             load*
-            report-load-error
             warn-about-load-error
             show-version-and-exit
             show-bug-report-information
@@ -145,35 +147,91 @@ messages."
               modules)
     module))
 
-(define (load* file user-module)
+(define* (load* file user-module
+                #:key (on-error 'nothing-special))
   "Load the user provided Scheme source code FILE."
+  (define (frame-with-source frame)
+    ;; Walk from FRAME upwards until source location information is found.
+    (let loop ((frame    frame)
+               (previous frame))
+      (if (not frame)
+          previous
+          (if (frame-source frame)
+              frame
+              (loop (frame-previous frame) frame)))))
+
+  (define (error-string frame args)
+    (call-with-output-string
+     (lambda (port)
+       (apply display-error frame port (cdr args)))))
+
+  (define tag
+    (make-prompt-tag "user-code"))
+
   (catch #t
     (lambda ()
+      ;; XXX: Force a recompilation to avoid ABI issues.
       (set! %fresh-auto-compile #t)
+      (set! %load-should-auto-compile #t)
 
       (save-module-excursion
        (lambda ()
          (set-current-module user-module)
-         (primitive-load file))))
-    (lambda args
-      (report-load-error file args))))
 
-(define (report-load-error file args)
-  "Report the failure to load FILE, a user-provided Scheme file, and exit.
+         ;; Hide the "auto-compiling" messages.
+         (parameterize ((current-warning-port (%make-void-port "w")))
+           (call-with-prompt tag
+             (lambda ()
+               ;; Give 'load' an absolute file name so that it doesn't try to
+               ;; search for FILE in %LOAD-PATH.  Note: use 'load', not
+               ;; 'primitive-load', so that FILE is compiled, which then allows us
+               ;; to provide better error reporting with source line numbers.
+               (load (canonicalize-path file)))
+             (const #f))))))
+    (lambda _
+      ;; XXX: Errors are reported from the pre-unwind handler below, but
+      ;; calling 'exit' from there has no effect, so we call it here.
+      (exit 1))
+    (rec (handle-error . args)
+         ;; Capture the stack up to this procedure call, excluded, and pass
+         ;; the faulty stack frame to 'report-load-error'.
+         (let* ((stack (make-stack #t handle-error tag))
+                (depth (stack-length stack))
+                (last  (and (> depth 0) (stack-ref stack 0)))
+                (frame (frame-with-source
+                        (if (> depth 1)
+                            (stack-ref stack 1)   ;skip the 'throw' frame
+                            last))))
+
+           (report-load-error file args frame)
+
+           (case on-error
+             ((debug)
+              (newline)
+              (display (_ "entering debugger; type ',bt' for a backtrace\n"))
+              (start-repl #:debug (make-debug (stack->vector stack) 0
+                                              (error-string frame args)
+                                              #f)))
+             ((backtrace)
+              (newline (current-error-port))
+              (display-backtrace stack (current-error-port)))
+             (else
+              #t))))))
+
+(define* (report-load-error file args #:optional frame)
+  "Report the failure to load FILE, a user-provided Scheme file.
 ARGS is the list of arguments received by the 'throw' handler."
   (match args
     (('system-error . _)
      (let ((err (system-error-errno args)))
-       (leave (_ "failed to load '~a': ~a~%") file (strerror err))))
+       (report-error (_ "failed to load '~a': ~a~%") file (strerror err))))
     (('syntax-error proc message properties form . rest)
      (let ((loc (source-properties->location properties)))
        (format (current-error-port) (_ "~a: error: ~a~%")
-               (location->string loc) message)
-       (exit 1)))
+               (location->string loc) message)))
     ((error args ...)
      (report-error (_ "failed to load '~a':~%") file)
-     (apply display-error #f (current-error-port) args)
-     (exit 1))))
+     (apply display-error frame (current-error-port) args))))
 
 (define (warn-about-load-error file args)         ;FIXME: factorize with ↑
   "Report the failure to load FILE, a user-provided Scheme file, without
@@ -243,8 +301,25 @@ General help using GNU software: <http://www.gnu.org/gethelp/>"))
           ;; information is missing as of Guile 2.0.11, making the exception
           ;; uninformative.)
           (apply throw key proc "~A: ~S"
-                 (append args (list link))
-                 errno))))))
+                 (list (strerror (car errno)) link)
+                 (list errno)))))))
+
+(set! copy-file
+  ;; Note: here we use 'set!', not #:replace, because UIs typically use
+  ;; 'copy-recursively', which doesn't use (guix ui).
+  (let ((real-copy-file (@ (guile) copy-file)))
+    (lambda (source target)
+      "This is a 'copy-file' replacement that provides proper error reporting."
+      (catch 'system-error
+        (lambda ()
+          (real-copy-file source target))
+        (lambda (key proc fmt args errno)
+          ;; Augment the FMT and ARGS with information about TARGET (this
+          ;; information is missing as of Guile 2.0.11, making the exception
+          ;; uninformative.)
+          (apply throw key proc "~A: ~S"
+                 (list (strerror (car errno)) target)
+                 (list errno)))))))
 
 (define (string->number* str)
   "Like `string->number', but error out with an error message on failure."
diff --git a/po/guix/POTFILES.in b/po/guix/POTFILES.in
index 30ce28b712..59f353e427 100644
--- a/po/guix/POTFILES.in
+++ b/po/guix/POTFILES.in
@@ -3,6 +3,7 @@
 gnu/packages.scm
 gnu/system.scm
 gnu/services/dmd.scm
+gnu/system/shadow.scm
 guix/scripts/build.scm
 guix/scripts/download.scm
 guix/scripts/package.scm
diff --git a/tests/guix-package.sh b/tests/guix-package.sh
index 26a5e9d1a2..b361b1ba00 100644
--- a/tests/guix-package.sh
+++ b/tests/guix-package.sh
@@ -245,7 +245,7 @@ guix package -I
 
 unset GUIX_BUILD_OPTIONS
 
-# Applying a manifest file
+# Applying a manifest file.
 cat > "$module_dir/manifest.scm"<<EOF
 (use-package-modules bootstrap)
 
@@ -254,3 +254,19 @@ EOF
 guix package --bootstrap -m "$module_dir/manifest.scm"
 guix package -I | grep guile
 test `guix package -I | wc -l` -eq 1
+
+# Error reporting.
+cat > "$module_dir/manifest.scm"<<EOF
+(use-package-modules bootstrap)
+(packages->manifest
+  (list %bootstrap-guile
+        wonderful-package-that-does-not-exist))
+EOF
+if guix package --bootstrap -n -m "$module_dir/manifest.scm" \
+	2> "$module_dir/stderr"
+then false
+else
+    cat "$module_dir/stderr"
+    grep "manifest.scm:[1-3]:.*[Uu]nbound variable.*wonderful-package" \
+	 "$module_dir/stderr"
+fi
diff --git a/tests/guix-system.sh b/tests/guix-system.sh
index 1b77d1a0db..4289db2390 100644
--- a/tests/guix-system.sh
+++ b/tests/guix-system.sh
@@ -45,6 +45,32 @@ else
 fi
 
 
+# Reporting of unbound variables.
+
+cat > "$tmpfile" <<EOF
+(use-modules (gnu))                                   ; 1
+(use-service-modules networking)                      ; 2
+
+(operating-system                                     ; 4
+  (host-name "antelope")                              ; 5
+  (timezone "Europe/Paris")                           ; 6
+  (locale "en_US.UTF-8")                              ; 7
+
+  (bootloader (GRUB-config (device "/dev/sdX")))      ; 9
+  (file-systems (cons (file-system
+                        (device "root")
+                        (title 'label)
+                        (mount-point "/")
+                        (type "ext4"))
+                      %base-file-systems)))
+EOF
+
+if guix system build "$tmpfile" -n 2> "$errorfile"
+then false
+else
+    grep "$tmpfile:9:.*[Uu]nbound variable.*GRUB-config" "$errorfile"
+fi
+
 # Reporting of duplicate service identifiers.
 
 cat > "$tmpfile" <<EOF
@@ -76,3 +102,42 @@ then
 else
     grep "service 'networking'.*more than once" "$errorfile"
 fi
+
+make_user_config ()
+{
+    cat > "$tmpfile" <<EOF
+(use-modules (gnu))
+(use-service-modules networking)
+
+(operating-system
+  (host-name "antelope")
+  (timezone "Europe/Paris")
+  (locale "en_US.UTF-8")
+
+  (bootloader (grub-configuration (device "/dev/sdX")))
+  (file-systems (cons (file-system
+                        (device "root")
+                        (title 'label)
+                        (mount-point "/")
+                        (type "ext4"))
+                      %base-file-systems))
+  (users (list (user-account
+                 (name "dave")
+                 (home-directory "/home/dave")
+                 (group "$1")
+                 (supplementary-groups '("$2"))))))
+EOF
+}
+
+make_user_config "users" "wheel"
+guix system build "$tmpfile" -n       # succeeds
+
+make_user_config "group-that-does-not-exist" "users"
+if guix system build "$tmpfile" -n 2> "$errorfile"
+then false
+else grep "primary group.*group-that-does-not-exist.*undeclared" "$errorfile"; fi
+
+make_user_config "users" "group-that-does-not-exist"
+if guix system build "$tmpfile" -n 2> "$errorfile"
+then false
+else grep "supplementary group.*group-that-does-not-exist.*undeclared" "$errorfile"; fi