diff -pruN 1.0.3-1/consfigurator.asd 1.1.0-1/consfigurator.asd
--- 1.0.3-1/consfigurator.asd	2022-06-30 00:38:29.000000000 +0000
+++ 1.1.0-1/consfigurator.asd	2022-08-02 22:03:30.000000000 +0000
@@ -1,6 +1,6 @@
 (defsystem "consfigurator"
   :description "Lisp declarative configuration management system"
-  :version "1.0.3"
+  :version "1.1.0"
   :author "Sean Whitton <spwhitton@spwhitton.name>"
   :licence "GPL-3+"
   :serial t
@@ -102,7 +102,7 @@
 (defsystem "consfigurator/tests"
   :description
   "Tests for Consfigurator, Lisp declarative configuration management system"
-  :version "1.0.3"
+  :version "1.1.0"
   :author "Sean Whitton <spwhitton@spwhitton.name>"
   :licence "GPL-3+"
   :serial t
diff -pruN 1.0.3-1/debian/changelog 1.1.0-1/debian/changelog
--- 1.0.3-1/debian/changelog	2022-06-30 00:38:28.000000000 +0000
+++ 1.1.0-1/debian/changelog	2022-08-02 22:03:28.000000000 +0000
@@ -1,3 +1,9 @@
+consfigurator (1.1.0-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Sean Whitton <spwhitton@spwhitton.name>  Tue, 02 Aug 2022 15:03:28 -0700
+
 consfigurator (1.0.3-1) unstable; urgency=medium
 
   * New upstream release.
diff -pruN 1.0.3-1/doc/conf.py 1.1.0-1/doc/conf.py
--- 1.0.3-1/doc/conf.py	2022-06-30 00:38:29.000000000 +0000
+++ 1.1.0-1/doc/conf.py	2022-08-02 22:03:30.000000000 +0000
@@ -22,7 +22,7 @@ copyright = '2020-2022, Sean Whitton'
 author = 'Sean Whitton'
 
 # The full version, including alpha/beta/rc tags
-release = '1.0.3'
+release = '1.1.0'
 
 
 # -- General configuration ---------------------------------------------------
diff -pruN 1.0.3-1/doc/news.rst 1.1.0-1/doc/news.rst
--- 1.0.3-1/doc/news.rst	2022-06-30 00:38:29.000000000 +0000
+++ 1.1.0-1/doc/news.rst	2022-08-02 22:03:30.000000000 +0000
@@ -23,6 +23,34 @@ In summary, you should always be able to
 increments ``patch``, but if either of the other two components have changed,
 you should review this document and see if your consfig needs updating.
 
+1.1.0 (2022-08-02)
+------------------
+
+- API change: DISK:HOST-VOLUMES-CREATED has been removed in favour of new
+  properties DISK:FIRST-DISK-INSTALLED-FOR and DISK:VOLUMES-INSTALLED-FOR.
+
+- API change: INSTALLED:CHROOT-INSTALLED-TO-VOLUMES-FOR has been renamed to
+  INSTALLER:FILES-INSTALLED-TO-VOLUMES-FOR, and will now bootstrap a root
+  filesystem directly to the volumes if not supplied a chroot.  The CHROOT
+  parameter has become a keyword parameter, and the required parameters have
+  changed from ``(HOST CHROOT VOLUMES)`` to ``(OPTIONS HOST VOLUMES)`` for
+  consistency with other property lambda lists.
+
+  The new property also includes a bugfix: we now rebuild the initramfs after
+  populating the crypttab.
+
+- API change: DISK:WITH-OPENED-VOLUMES now includes volumes that were already
+  open, and their parents, in the connattrs.
+
+- DISK:LUKS-CONTAINER: Add support for passing arbitrary options to
+  cryptsetup(8) when creating volumes, such as ``--cipher``.
+
+- DISK:WITH-OPENED-VOLUMES, INSTALLER:FILES-INSTALLED-TO-VOLUMES-FOR and
+  DISK:VOLUMES-INSTALLED-FOR support a new ``LEAVE-OPEN`` argument to request
+  that opened volumes are not closed.  This is useful for inspecting the
+  result of an installation, but must be used with caution: the next
+  deployment will assume the volumes have been manually closed.
+
 1.0.3 (2022-06-29)
 ------------------
 
diff -pruN 1.0.3-1/doc/tutorial/disk_image.rst 1.1.0-1/doc/tutorial/disk_image.rst
--- 1.0.3-1/doc/tutorial/disk_image.rst	2022-06-30 00:38:29.000000000 +0000
+++ 1.1.0-1/doc/tutorial/disk_image.rst	2022-08-02 22:03:30.000000000 +0000
@@ -15,6 +15,7 @@ Here is a minimal definition of the host
     (os:debian-stable "bullseye" :amd64)
     (disk:has-volumes
      (physical-disk
+      :device-file #P"/dev/sda"
       :boots-with '(grub:grub :target "x86_64-efi" :force-extra-removable t)
       (partitioned-volume
        ((partition
diff -pruN 1.0.3-1/doc/tutorial/os_installation.rst 1.1.0-1/doc/tutorial/os_installation.rst
--- 1.0.3-1/doc/tutorial/os_installation.rst	2022-06-30 00:38:29.000000000 +0000
+++ 1.1.0-1/doc/tutorial/os_installation.rst	2022-08-02 22:03:30.000000000 +0000
@@ -1,8 +1,7 @@
 Tutorial: OS installation
 =========================
 
-Consfigurator implements at least the basic elements of a number of methods
-for installing operating systems.
+Consfigurator implements a number of methods for installing operating systems.
 
 .. include:: conventions.rst
 
@@ -17,6 +16,17 @@ expand the partitions to fill the whole
 successful boot, so the disk image has to be the same size as the target disk,
 which can be unwieldy.
 
+Installing directly to target host's primary storage
+----------------------------------------------------
+
+This is similar to the previous method, but it avoids the problem of having to
+expand filesystems upon first boot.  Suppose we have connected the target
+host's hard disk to our laptop, or transferred its boot SD card to our
+laptop's SD card reader, etc..  Then we can install to it directly:::
+
+  CONSFIG> (hostdeploy-these laptop.example.com
+             (disk:first-disk-installed-for nil test.example.com "/dev/mmcblk0"))
+
 Live replacement of provider cloud images
 -----------------------------------------
 
@@ -29,36 +39,43 @@ has been taught how to bootstrap them.
 Build a specialised live image
 ------------------------------
 
-This third approach is more experimental; Consfigurator has all the necessary
-capabilities, at least for Debian, but at present you'll need to string them
-together yourself in your consfig.  With this approach you build a live image
-containing everything you need to run Consfigurator on the hardware to which
-you want to install.  After booting up the live system, you can either run
-Consfigurator manually, or you can set things up to have it run automatically
-upon boot.
+This fourth approach requires more work in your consfig.  With this approach
+you build a live image containing everything you need to run Consfigurator on
+the hardware to which you want to install.  After booting up the live system,
+you can either run Consfigurator manually, or you can set things up to have it
+run automatically upon boot.
 
 Consfigurator's ability to bootstrap fresh root filesystems typically requires
 Internet access, but an alternative is to build and customise a chroot
 corresponding to the root filesystem of the target system, and include that in
 the live image, such that after boot Consfigurator just needs to partition the
 disk, copy in the contents of the prebuilt chroot, and update /etc/fstab and
-/etc/crypttab with UUIDs.  Here is a sketch of how to do something like that::
+/etc/crypttab with UUIDs.  Here is a minimal version of something like that::
 
     (try-register-data-source
      :git-snapshot :name "consfig"
-     :repo #P"src/cl/consfig/" :depth 1 :branch "master")
+     :repo #P"common-lisp/consfig/" :depth 1 :branch "master")
 
     (defproplist live-installer-built-for :lisp (with-chroot-for)
       "Build a custom Debian Live system at /srv/live/installer.iso.
 
     Typically this property is not applied in a DEFHOST form, but rather run as
     needed at the REPL.  The reason for this is that otherwise the whole image will
-    get rebuilt each time a commit is made to ~/src/cl/consfig/."
+    get rebuilt each time a commit is made to ~/common-lisp/consfig/."
       (:desc "Debian Live system image built")
       (disk:debian-live-iso-built. nil "/srv/live/installer.iso"
         (os:debian-stable "bullseye" :amd64)
-        (apt:installed "task-english" "live-config" "lvm2" "cryptsetup")
-        (git:snapshot-extracted "consfig" "/etc/skel/src/cl")
+	(hostname:configured "debian")
+        (apt:installed "sudo" "task-english" "emacs"
+	               "sbcl" "slime" "cl-consfigurator" "build-essential"
+	               "lvm2" "cryptsetup" "gdisk" "kpartx" "dosfstools")
+
+	(user:has-account "user")
+	(user:has-enabled-password "user" :initial-password "live")
+	(file:exists-with-content "/etc/sudoers.d/user"
+	  '("user  ALL=(ALL:ALL) NOPASSWD: ALL") :mode #o600)
+        (as "user" (git:snapshot-extracted "consfig" "common-lisp/" :replace t))
+
         (chroot:os-bootstrapped-for
          nil
          (merge-pathnames (get-hostname with-chroot-for) "/srv/chroot/")
@@ -70,9 +87,16 @@ could then use::
   CONSFIG> (hostdeploy-these laptop.example.com
              (live-installer-built-for test.example.com))
 
-Then once the live system has booted on the target host, you'd use the
-DISK:HOST-VOLUMES-CREATED and INSTALLER:CHROOT-INSTALLED-TO-VOLUMES-FOR
-properties to complete the installation.
+Then once the live system has booted on the target host, you'd complete the
+installation using something like this:::
+
+  CONSFIG> (deploy-these (:sudo :sbcl) "debian"
+             (os:debian-stable "bullseye" :amd64)
+             (disk:volumes-installed-for nil test.example.com :leave-open t
+					 :chroot "/srv/chroot/test.example.com")
+
+This approach requires that a ``:DEVICE-FILE`` is specified for each of the
+host's physical disks.
 
 To prepare a live image that is capable of installing more than one system
 without an Internet connection, you'd probably need to investigate including
diff -pruN 1.0.3-1/emacs/consfigurator.el.in 1.1.0-1/emacs/consfigurator.el.in
--- 1.0.3-1/emacs/consfigurator.el.in	2022-06-30 00:38:29.000000000 +0000
+++ 1.1.0-1/emacs/consfigurator.el.in	2022-08-02 22:03:30.000000000 +0000
@@ -1,7 +1,7 @@
 ;;; consfigurator.el --- Utilities for working with Consfigurator consfigs
 
 ;; Author: Sean Whitton <spwhitton@spwhitton.name>
-;; Version: 1.0.3
+;; Version: 1.1.0
 
 ;; Copyright (C) 2021  Sean Whitton
 
diff -pruN 1.0.3-1/src/data/files-tree.lisp 1.1.0-1/src/data/files-tree.lisp
--- 1.0.3-1/src/data/files-tree.lisp	2022-06-30 00:38:29.000000000 +0000
+++ 1.1.0-1/src/data/files-tree.lisp	2022-08-02 22:03:30.000000000 +0000
@@ -26,11 +26,18 @@ LOCATION is either a designator for a pa
 tree of files or a symbol which designates an ASDF package where the tree is
 contained in the subdirectory 'data/'.
 
-IDEN1 specifies a (possibly nested) subdirectory under LOCATION and IDEN2 a
-relative path within that subdirectory.  For convenience IDEN1 and IDEN2 may
-be passed as absolute and will be converted to relative paths.  The usual
-cases of IDEN1 as a hostname, IDEN1 as an underscore-prefixed identifier, and
-IDEN2 an an absolute or relative path are all supported."
+LOCATION, IDEN1 and IDEN2 are concatenated to locate files.  Thus, IDEN1
+specifies a (possibly nested) subdirectory under LOCATION and IDEN2 a relative
+path within that subdirectory.
+
+Special characters in IDEN1 and IDEN2 are not encoded.  This means that each
+character in IDEN1 and IDEN2 must be permitted in filenames on this system,
+and that any slashes in IDEN1 and IDEN2 will probably act as path separators.
+
+For convenience IDEN1 and IDEN2 may be passed as absolute and will be
+converted to relative paths.  The usual cases of IDEN1 as a hostname, IDEN1 as
+an underscore-prefixed identifier, and IDEN2 an an absolute or relative path
+are all supported."
   (let ((base-path (if (symbolp location)
                         (asdf:system-relative-pathname location "data/")
                         (ensure-directory-pathname location))))
diff -pruN 1.0.3-1/src/package.lisp 1.1.0-1/src/package.lisp
--- 1.0.3-1/src/package.lisp	2022-06-30 00:38:29.000000000 +0000
+++ 1.1.0-1/src/package.lisp	2022-08-02 22:03:30.000000000 +0000
@@ -651,9 +651,10 @@
 
                     #:has-volumes
                     #:raw-image-built-for
+                    #:first-disk-installed-for
+                    #:volumes-installed-for
                     #:debian-live-iso-built
                     #:debian-live-iso-built.
-                    #:host-volumes-created
                     #:host-logical-volumes-exist
 
                     #:volumes))
@@ -741,7 +742,7 @@
                              (#:disk      #:consfigurator.property.disk))
            (:export #:install-bootloader-propspec
                     #:install-bootloader-binaries-propspec
-                    #:chroot-installed-to-volumes-for
+                    #:files-installed-to-volumes-for
                     #:bootloader-binaries-installed
                     #:bootloaders-installed
                     #:cleanly-installed-once
diff -pruN 1.0.3-1/src/property/disk.lisp 1.1.0-1/src/property/disk.lisp
--- 1.0.3-1/src/property/disk.lisp	2022-06-30 00:38:29.000000000 +0000
+++ 1.1.0-1/src/property/disk.lisp	2022-08-02 22:03:30.000000000 +0000
@@ -1,6 +1,6 @@
 ;;; Consfigurator -- Lisp declarative configuration management system
 
-;;; Copyright (C) 2021  Sean Whitton <spwhitton@spwhitton.name>
+;;; Copyright (C) 2021-2022  Sean Whitton <spwhitton@spwhitton.name>
 
 ;;; This file is free software; you can redistribute it and/or modify
 ;;; it under the terms of the GNU General Public License as published by
@@ -611,6 +611,11 @@ possible.  Ignored if VOLUME-SIZE is als
     "The value of the --type parameter to cryptsetup luksFormat.
 Note that GRUB2 older than 2.06 cannot open the default LUKS2 format, so
 specify \"luks1\" if this is needed.")
+   (cryptsetup-options
+    :type list :initform nil :initarg :cryptsetup-options
+    :documentation
+    "Extra arguments to pass to cryptsetup(8) when creating the volume, such as
+'--cipher'.  Use the LUKS-TYPE slot for '--type'.")
    (crypttab-options
     :type list :initform '("luks" "discard" "initramfs")
     :initarg :crypttab-options :accessor crypttab-options)
@@ -632,12 +637,15 @@ specify \"luks1\" if this is needed.")
                         (merge-pathnames volume-label #P"/dev/mapper/"))))
 
 (defmethod create-volume ((volume luks-container) (file pathname))
-  (with-slots (luks-passphrase-iden1 volume-label luks-type) volume
+  (with-slots
+        (luks-passphrase-iden1 volume-label luks-type cryptsetup-options)
+      volume
     (mrun :inform
           :input (get-data-string luks-passphrase-iden1 (volume-label volume))
           "cryptsetup" "--type" luks-type
           (and (member luks-type '("luks" "luks2") :test #'string=)
                `("--label" ,volume-label))
+          cryptsetup-options
           "luksFormat" file "-")))
 
 (defmethod close-volume ((volume opened-luks-container))
@@ -667,12 +675,16 @@ order in which they should be closed, an
 list of the immediate parents of each opened volume.  MOUNT-BELOW specifies a
 pathname to prefix to mount points when opening FILESYSTEM volumes.
 
+Also return as third and fourth values a list of volumes encountered that were
+already open and a corresponding list of their immediate parents.
+
 Calling this function can be useful for testing at the REPL, but code should
 normally use WITH-OPEN-VOLUMES or WITH-OPENED-VOLUMES.
 
 If an error is signalled while the attempt to open volumes is in progress, a
 single attempt will be made to close all volumes opened up to that point."
-  (let (opened-volumes all-parents filesystems)
+  (let
+      (opened-volumes opened-parents already-open already-parents filesystems)
     (handler-case
         (labels
             ((open-volume-and-contents (volume file parent)
@@ -690,14 +702,19 @@ single attempt will be made to close all
                        (if (subtypep (class-of volume) 'opened-volume)
                            (setq opened-volumes
                                  (append opened-contents opened-volumes)
-                                 all-parents
-                                 (nconc opened-contents-parents all-parents))
+                                 opened-parents
+                                 (nconc opened-contents-parents
+                                        opened-parents)
+                                 already-open
+                                 (cons volume already-open)
+                                 already-parents
+                                 (cons parent already-parents))
                            (setq opened-volumes
                                  (append opened-contents
                                          (cons opened opened-volumes))
-                                 all-parents
+                                 opened-parents
                                  (nconc opened-contents-parents
-                                        (cons parent all-parents)))))
+                                        (cons parent opened-parents)))))
                      (dolist (opened-volume (or opened-contents `(,opened)))
                        (when (slot-boundp opened-volume 'volume-contents)
                          (open-volume-and-contents
@@ -713,9 +730,9 @@ single attempt will be made to close all
                               :key (compose #'ensure-directory-pathname
                                             #'mount-point
                                             #'cadr))))
-                (push (pop filesystem) all-parents)
+                (push (pop filesystem) opened-parents)
                 (push (apply #'open-volume filesystem) opened-volumes)))
-          (values opened-volumes all-parents))
+          (values opened-volumes opened-parents already-open already-parents))
       (serious-condition (condition)
         (unwind-protect (mapc #'close-volume opened-volumes)
           (error condition))))))
@@ -743,24 +760,26 @@ populate /etc/fstab and /etc/crypttab.
          (mapc #'close-volume ,opened-volumes)))))
 
 (defmacro with-opened-volumes
-    ((volumes &key (mount-below nil mount-below-supplied-p)) &body propapps)
+    ((volumes &key (mount-below nil mount-below-supplied-p) leave-open)
+     &body propapps)
   "Macro property combinator.  Where each of VOLUMES is a VOLUME which may be
 opened by calling OPEN-VOLUME with NIL as the second argument, recursively
-open each of VOLUMES and any contents thereof, apply PROPAPPS, and close all
-volumes that were opened.
+open each of VOLUMES and any contents thereof, apply PROPAPPS, and, unless
+LEAVE-OPEN, close all volumes that were opened.
 
 MOUNT-BELOW specifies a pathname to prefix to mount points when opening
 FILESYSTEM volumes.  During the application of PROPAPPS, all
 'DISK:OPENED-VOLUMES and 'DISK:OPENED-VOLUME-PARENTS connattrs are replaced
-with lists of the volumes that were opened and corresponding immediate parent
-volumes; the former must not be modified."
+with lists of the volumes that were opened/already open and corresponding
+immediate parent volumes."
   `(with-opened-volumes*
      ',volumes
      ,(if (cdr propapps) `(eseqprops ,@propapps) (car propapps))
-     ,@(and mount-below-supplied-p `(:mount-below ,mount-below))))
+     ,@(and mount-below-supplied-p `(:mount-below ,mount-below))
+     :leave-open ,leave-open))
 
 (define-function-property-combinator with-opened-volumes*
-    (volumes propapp &key (mount-below nil mount-below-supplied-p))
+    (volumes propapp &key (mount-below nil mount-below-supplied-p) leave-open)
   (:retprop
    :type (propapp-type propapp)
    :hostattrs (lambda-ignoring-args
@@ -768,15 +787,17 @@ volumes; the former must not be modified
                 (propapp-attrs propapp))
    :apply
    (lambda-ignoring-args
-     (multiple-value-bind (opened-volumes parents)
+     (multiple-value-bind
+           (opened-volumes opened-parents already-open already-parents)
          (apply #'open-volumes-and-contents
                 `(,volumes ,@(and mount-below-supplied-p
                                   `(:mount-below ,mount-below))))
-       (with-connattrs ('opened-volumes opened-volumes
-                        'opened-volume-parents parents)
+       (with-connattrs
+           ('opened-volumes (append opened-volumes already-open)
+            'opened-volume-parents (append opened-parents already-parents))
          (unwind-protect (apply-propapp propapp)
            (mrun "sync")
-           (mapc #'close-volume (get-connattr 'opened-volumes))))))
+           (unless leave-open (mapc #'close-volume opened-volumes))))))
    :args (cdr propapp)))
 
 (defun create-volumes-and-contents (volumes &optional files)
@@ -907,6 +928,21 @@ the LVM physical volumes corresponding t
   (ensure-directory-pathname
    (strcat (unix-namestring image-pathname) ".chroot")))
 
+(defun host-volumes-just-one-physical-disk (host fun)
+  (loop
+    with found
+    for volume in (get-hostattrs :volumes (preprocess-host host))
+    for physical-disk-p = (subtypep (type-of volume) 'physical-disk)
+    if (and physical-disk-p (not found) (slot-boundp volume 'volume-contents))
+      do (setq found t)
+      and collect (aprog1 (copy-volume-and-contents volume) (funcall fun it))
+    else unless physical-disk-p
+           collect volume
+    finally
+       (unless found
+         (inapplicable-property
+          "Volumes list for host has no DISK:PHYSICAL-DISK with contents."))))
+
 (defpropspec raw-image-built-for :lisp
     (options host image-pathname &key rebuild)
   "Build a raw disk image for HOST at IMAGE-PATHNAME.
@@ -933,30 +969,117 @@ of the host's volumes changes, although
 filesystems will be incrementally updated when other properties change."
   (:desc #?"Built image for ${(get-hostname host)} @ ${image-pathname}")
   (let ((chroot (image-chroot image-pathname))
-        (volumes
-          (loop
-            with found
-            for volume in (get-hostattrs :volumes (preprocess-host host))
-            for physical-disk-p = (subtypep (type-of volume) 'physical-disk)
-            if (and physical-disk-p (not found)
-                    (slot-boundp volume 'volume-contents))
-              do (setq found t)
-              and collect (aprog1 (copy-volume-and-contents volume)
-                            (change-class it 'raw-disk-image)
-                            (setf (image-file it) image-pathname))
-            else unless physical-disk-p
-                   collect volume
-            finally
-               (unless found
-                 (inapplicable-property
-                  "Volumes list for host has no DISK:PHYSICAL-DISK with contents.")))))
+        (volumes (host-volumes-just-one-physical-disk
+                  host (lambda (volume)
+                         (change-class volume 'raw-disk-image)
+                         (setf (image-file volume) image-pathname)))))
     `(on-change (eseqprops
                  ,(propapp (chroot:os-bootstrapped-for. options chroot host
                              (caches-cleaned)))
                  (%raw-image-created
                   ,volumes :chroot ,chroot :rebuild ,rebuild))
-       (consfigurator.property.installer:chroot-installed-to-volumes-for
-        ,host ,chroot ,volumes))))
+       (consfigurator.property.installer:files-installed-to-volumes-for
+        nil ,host ,volumes :chroot ,chroot))))
+
+(defprop %volumes-created :lisp (volumes)
+  (:desc "Created host volumes")
+  (:hostattrs (os:required 'os:linux) (require-volumes-data volumes))
+  (:apply
+   (dolist (volume volumes)
+     (when (subtypep (type-of volume) 'physical-disk)
+       (setf (volume-size volume)
+             (floor (/ (parse-integer
+                        (stripln
+                         (run "blockdev" "--getsize64" (device-file volume))))
+                       1048576)))))
+   (create-volumes-and-contents volumes)))
+
+(defpropspec first-disk-installed-for :lisp
+    (options host device-file &key chroot)
+  "Install HOST to the DISK:PHYSICAL-DISK accessible at DEVICE-FILE.
+**THIS PROPERTY UNCONDITIONALLY FORMATS DISKS, POTENTIALLY DESTROYING DATA,
+  EACH TIME IT IS APPLIED.**
+
+Do not apply in DEFHOST.  Apply with DEPLOY-THESE/HOSTDEPLOY-THESE.
+
+The DISK:VOLUME-CONTENTS of the first DISK:PHYSICAL-DISK entry in the host's
+volumes, as specified using DISK:HAS-VOLUMES, will be created at DEVICE-FILE;
+there must be at least one such DISK:PHYSICAL-DISK entry.  Other
+DISK:PHYSICAL-DISK entries will be ignored, so ensure that none of the
+properties of the host will write to areas of the filesystem where filesystems
+stored on other physical disks would normally be mounted.
+
+OPTIONS will be passed on to CHROOT:OS-BOOTSTRAPPED-FOR, which see.
+
+In most cases you will need to ensure that HOST has properties which do at
+least the following:
+
+  - declare the host's OS
+
+  - install a kernel
+
+  - install the binaries/packages needed to install the host's bootloader to
+    its volumes (usually with INSTALLER:BOOTLOADER-BINARIES-INSTALLED).
+
+If CHROOT, an intermediate chroot is bootstrapped at CHROOT, and HOST's
+properties are applied to that.  Otherwise, HOST's OS is bootstrapped directly
+to DEVICE-FILE.  It's useful to supply CHROOT when you expect to install the
+same HOST to a number of physical disks.
+
+Applying this property is similar to applying DISK:RAW-IMAGE-BUILT-FOR and
+then immediately dd'ing out the image to DEVICE-FILE.  The advantage of this
+property is that there is no need to resize filesystems to fill the size of
+the host's actual physical disk upon first boot."
+  (:desc #?"Installed ${(get-hostname host)} to ${device-file}")
+  (let ((volumes (host-volumes-just-one-physical-disk
+                  host (lambda (v)
+                         (setf (device-file v)
+                               (parse-unix-namestring device-file))))))
+    `(eseqprops
+      (%volumes-created ,volumes)
+      ,@(and chroot
+             (list (propapp (chroot:os-bootstrapped-for. options chroot host
+                              (caches-cleaned)))))
+      (consfigurator.property.installer:files-installed-to-volumes-for
+       ,options ,host ,volumes :chroot ,chroot))))
+
+(defpropspec volumes-installed-for :lisp (options host &key chroot leave-open)
+  "Install HOST to its volumes, as specified using DISK:HAS-VOLUMES.
+**THIS PROPERTY UNCONDITIONALLY FORMATS DISKS, POTENTIALLY DESTROYING DATA,
+  EACH TIME IT IS APPLIED.**
+
+Do not apply in DEFHOST.  Apply with DEPLOY-THESE/HOSTDEPLOY-THESE.
+
+OPTIONS will be passed on to CHROOT:OS-BOOTSTRAPPED-FOR, which see.
+
+In most cases you will need to ensure that HOST has properties which do at
+least the following:
+
+  - declare the host's OS
+
+  - install a kernel
+
+  - install the binaries/packages needed to install the host's bootloader to
+    its volumes (usually with INSTALLER:BOOTLOADER-BINARIES-INSTALLED).
+
+If CHROOT, an intermediate chroot is bootstrapped at CHROOT, and HOST's
+properties are applied to that.  Otherwise, HOST's OS is bootstrapped directly
+to its volumes.  This parameter is useful for the case of installing HOST from
+a live system which might not have network access.  See \"Tutorial: OS
+installation\" in the Consfigurator user's manual.
+
+If LEAVE-OPEN, HOST's volumes will not be closed.  This allows you to inspect
+the result of the installation.  If you want to run this property again, you
+should first manually close all the volumes."
+  (:desc #?"Installed ${(get-hostname host)}")
+  (let ((volumes (get-hostattrs :volumes host)))
+    `(eseqprops
+      (%volumes-created ,volumes)
+      ,@(and chroot
+             (list (propapp (chroot:os-bootstrapped-for. options chroot host
+                              (caches-cleaned)))))
+      (consfigurator.property.installer:files-installed-to-volumes-for
+       ,options ,host ,volumes :chroot ,chroot :leave-open ,leave-open))))
 
 (defprop %squashfsed :posix (chroot image &optional (compression "xz"))
   (:apply
@@ -1063,15 +1186,6 @@ Currently only BIOS boot is implemented.
 
          ,iso-root)))))
 
-(defprop host-volumes-created :lisp ()
-  "Recursively create the volumes as specified by DISK:HAS-VOLUMES.
-**THIS PROPERTY UNCONDITIONALLY FORMATS DISKS, POTENTIALLY DESTROYING DATA,
-  EACH TIME IT IS APPLIED.**
-
-Do not apply in DEFHOST.  Apply with DEPLOY-THESE/HOSTDEPLOY-THESE."
-  (:desc "Host volumes created")
-  (:apply (create-volumes-and-contents (get-hostattrs :volumes))))
-
 ;; TODO Possibly we want (a version of) this to not fail, but just do nothing,
 ;; if the relevant volume groups etc. are inactive?
 (defproplist host-logical-volumes-exist :lisp ()
diff -pruN 1.0.3-1/src/property/grub.lisp 1.1.0-1/src/property/grub.lisp
--- 1.0.3-1/src/property/grub.lisp	2022-06-30 00:38:29.000000000 +0000
+++ 1.1.0-1/src/property/grub.lisp	2022-08-02 22:03:30.000000000 +0000
@@ -48,7 +48,7 @@
        (mrun :inform "update-grub")
        (when os-prober (file:has-mode "/etc/grub.d/30_os-prober" #o755)))
      (mrun :inform "grub-install" (strcat "--target=" target)
-           (and (string-suffix-p target "-efi") running-on-target
+           (and (string-suffix-p target "-efi") (not running-on-target)
                 "--no-nvram")
            (and force-extra-removable "--force-extra-removable")
            (device-file volume)))))
diff -pruN 1.0.3-1/src/property/installer.lisp 1.1.0-1/src/property/installer.lisp
--- 1.0.3-1/src/property/installer.lisp	2022-06-30 00:38:29.000000000 +0000
+++ 1.1.0-1/src/property/installer.lisp	2022-08-02 22:03:30.000000000 +0000
@@ -59,11 +59,11 @@ BOOTLOADER-TYPE to VOLUME."))
 
 ;; At :HOSTATTRS time we don't have the OPENED-VOLUME values required by the
 ;; :APPLY subroutines which actually install the bootloaders.  So we call
-;; GET-PROPSPECS twice: (in CHROOT-INSTALLED-TO-VOLUMES-FOR) at :HOSTATTRS
-;; time to generate propspecs for the sake of running :HOSTATTRS subroutines,
-;; and then at :APPLY time where we can get at the OPENED-VOLUME values, we
-;; ignore the previously generated propspecs and call GET-PROPSPECS again.
-;; This approach should work for any sensible VOLUME<->OPENED-VOLUME pairs.
+;; GET-PROPSPECS twice: (in FILES-INSTALLED-TO-VOLUMES-FOR) at :HOSTATTRS time
+;; to generate propspecs for the sake of running :HOSTATTRS subroutines, and
+;; then at :APPLY time where we can get at the OPENED-VOLUME values, we ignore
+;; the previously generated propspecs and call GET-PROPSPECS again.  This
+;; approach should work for any sensible VOLUME<->OPENED-VOLUME pairs.
 (define-function-property-combinator %install-bootloaders (&rest propapps)
   (:retprop
    :type :lisp
@@ -89,40 +89,52 @@ BOOTLOADER-TYPE to VOLUME."))
         (strcat (unix-namestring chroot) "/")
         (strcat (unix-namestring target) "/"))))
 
-(defpropspec chroot-installed-to-volumes-for :lisp (host chroot volumes)
+(defun chroot-target (chroot)
+  (ensure-directory-pathname
+   (strcat
+    (drop-trailing-slash (unix-namestring (ensure-directory-pathname chroot)))
+    ".target")))
+
+(defpropspec files-installed-to-volumes-for :lisp
+    (options host volumes &key chroot leave-open
+             (mount-below (if chroot (chroot-target chroot) #P"/target/")))
   "Where CHROOT contains the root filesystem of HOST and VOLUMES is a list of
 volumes, recursively open the volumes and rsync in the contents of CHROOT.
-Also update the fstab and crypttab, and try to install bootloader(s)."
-  (:desc #?"${chroot} installed to volumes")
-  (let ((target
-          (ensure-directory-pathname
-           (strcat
-            (drop-trailing-slash
-             (unix-namestring (ensure-directory-pathname chroot)))
-            ".target"))))
-    `(with-opened-volumes (,volumes :mount-below ,target)
-       (%update-target-from-chroot ,chroot ,target)
-       (chroot:deploys-these
-        ,target ,host
-        ,(make-propspec
-          :propspec
-          `(eseqprops
-            ,(propapp
-              (os:etypecase
-                  (debianlike
-                   (file:lacks-lines
-                    "/etc/fstab" "# UNCONFIGURED FSTAB FOR BASE SYSTEM")
-                   ;; These will overwrite any custom mount options, etc.,
-                   ;; with values from VOLUMES.  Possibly it would be better
-                   ;; to use properties which only update the fs-spec/source
-                   ;; fields.  However, given that VOLUMES ultimately comes
-                   ;; from the volumes the user has declared for the host, it
-                   ;; is unlikely there are other properties setting mount
-                   ;; options etc. which are in conflict with VOLUMES.
-                   (fstab:has-entries-for-opened-volumes)
-                   (crypttab:has-entries-for-opened-volumes))))
-            (%install-bootloaders
-             ,@(get-propspecs (get-hostattrs :volumes)))))))))
+Also update the fstab and crypttab, and try to install bootloader(s).
+
+If CHROOT is NIL, bootstrap a root filesystem for HOST directly to VOLUMES.
+In that case, OPTIONS is passed on to CHROOT:OS-BOOTSTRAPPED-FOR, which see.
+
+MOUNT-BELOW and LEAVE-OPEN are passed on to WITH-OPENED-VOLUMES, which see."
+  (:desc (format nil "~A installed to volumes"
+                 (or chroot (get-hostname host))))
+  `(with-opened-volumes
+       (,volumes :mount-below ,mount-below :leave-open ,leave-open)
+     ,(if chroot
+          `(%update-target-from-chroot ,chroot ,mount-below)
+          `(chroot:os-bootstrapped-for ,options ,mount-below ,host))
+     (chroot:deploys-these
+      ,mount-below ,host
+      ,(make-propspec
+        :propspec
+        `(eseqprops
+          ,(propapp
+            (os:etypecase
+              (debianlike
+               (file:lacks-lines
+                "/etc/fstab" "# UNCONFIGURED FSTAB FOR BASE SYSTEM")
+               ;; These will overwrite any custom mount options, etc., with
+               ;; values from VOLUMES.  Possibly it would be better to use
+               ;; properties which only update the fs-spec/source fields.
+               ;; However, given that VOLUMES ultimately comes from the
+               ;; volumes the user has declared for the host, it is unlikely
+               ;; there are other properties setting mount options etc. which
+               ;; are in conflict with VOLUMES.
+               (fstab:has-entries-for-opened-volumes)
+               (on-change (crypttab:has-entries-for-opened-volumes)
+                 (cmd:single "update-initramfs" "-u")))))
+          (%install-bootloaders
+           ,@(get-propspecs (get-hostattrs :volumes))))))))
 
 (defpropspec bootloader-binaries-installed :posix ()
   "Install whatever binaries/packages need to be available to install the host's
