The more I think about it the more it seems we should re-enter our initramfs on shutdown. Unfortunately, the Linux kernel frees it when switching to the real root fs. Not sure how to get Linux to find it again (or how to get Linux to not free it in the first place) and to pivot_root to it again (with some arg that we can use to indicate to us that we are shutting the system down).
See also <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=778849>. Regardless, my current non-initramfs attempt is: (define %root-file-system-shepherd-service (shepherd-service (documentation "Take care of the root file system.") (provision '(root-file-system)) (start #~(const #t)) (stop #~(lambda _ ;;; stop: To return #f if successfully stopped. (define (call-with-mounted-filesystem source mountpoint filesystem-type options proc) (mount source mountpoint file-system-type options #:update-mtab? #f) (catch #t proc (lambda args (umount mountpoint)))) (sync) (let* ((null (%make-void-port "w")) (call-with-io-file (lambda (file-name proc) (let ((port (open file-name O_RDWR))) (set-current-input-port port) (set-current-output-port port) (set-current-error-port port) (catch #t proc (lambda args (set-current-input-port null) (set-current-output-port null) (set-current-error-port null) (close port)))))) (with-mounted-filesystem (syntax-rules () ((source filesystem-type mountpoint options . exps) (call-with-mounted-filesystem source filesystem-type mountpoint options (lambda () . exps)))))) ;; Redirect the default output ports. (set-current-output-port null) (set-current-error-port null) ;; Close /dev/console. (for-each close-fdes '(0 1 2)) ;; At this point, there should be no open files left so the ;; root file system can be re-mounted read-only. (let loop ((n 10)) (unless (catch 'system-error (lambda () (mount #f "/" #f (logior MS_REMOUNT MS_RDONLY) #:update-mtab? #f) #t) (const #f)) ;;; Note: Alternatively, if we still had it, we could try pivot_root into the initrd. ;;; (where we'd have a magic file to tell it that we are shutting down instead of booting). ;;; Note: dracut can do it, see <https://www.kernel.org/pub/linux/utils/boot/dracut/dracut.html#_dracut_on_shutdown>. ;;; See also <https://unix.stackexchange.com/questions/215169/get-back-to-initramfs-on-shutdown>. (when (zero? n) (with-mounted-filesystem "none" "/proc" "proc" 0 (with-mounted-filesystem "none" "/dev" "devtmpfs" 0 (catch 'system-error (mknod "/dev/tty" 'char-special #o600 (+ (* 5 256) 0)) (const #f)) ;; TODO: Or make fuser (in package psmisc) static and don't put it in the store either. ;; although we'd still need to resolve the symlink /run/booted-system and so on. ;; We could also traverse /proc ourselves, reimplementing fuser. (with-mounted-filesystem "/" #t "/gnu/store" (logior MS_BIND MS_READONLY) (call-with-io-file "/dev/tty" (lambda () (chvt 12) ; we don't have that (it would need to use ioctl VT_ACTIVATE int on /dev/tty) ;; The "w" option only finds writers. ;; The "i" option asks the user interactively what to kill. (system* "/run/booted-system/profile/bin/fuser" "-vikwm" "/"))))))) ;;; If we had a rescue container, we'd have to had stopped it at this point. (unless (catch 'system-error (lambda () (mount #f "/" #f (logior MS_REMOUNT MS_RDONLY) #:update-mtab? #f) #t) (const #f)) (catch 'system-error (mknod "/tty" 'char-special #o600 (+ (* 5 256) 0)) (const #f)) (with-io-file "/tty" (display "umount / still not good\n") (force-output))) ((@ (fibers) sleep) 10)) (unless (zero? n) ;; Yield to the other fibers. That gives logging fibers ;; an opportunity to close log files so the 'mount' call ;; doesn't fail with EBUSY. ((@ (fibers) sleep) 1) (loop (- n 1))))) #f))) (respawn? #f)))