diff options
author | Edouard Klein <edk@beaver-labs.com> | 2025-07-25 11:01:36 +0200 |
---|---|---|
committer | Maxim Cournoyer <maxim@guixotic.coop> | 2025-07-25 23:36:10 +0900 |
commit | f05f8fb6b4e6982cd12db4c943deae95e5692924 (patch) | |
tree | ac84ee77530f452cba26defac67a4d03c0ef42a5 | |
parent | 8636c0910fa201f5f9f10cd10cfe8e98abf707a5 (diff) |
services: Add vfs-mapping-service-type.
* gnu/services/linux.scm (vfs-mapping-service-type, vfs-mapping-configuration,
vfs-mapping-binding): New variables.
* doc/guix.texi: (Vfs Mapping Service): New subsubsection under "Linux Services".
Change-Id: I7ebd48afb809ded9fa6fe9eb80c618accb856716
Signed-off-by: Maxim Cournoyer <maxim@guixotic.coop>
-rw-r--r-- | doc/guix.texi | 143 | ||||
-rw-r--r-- | gnu/services/linux.scm | 164 |
2 files changed, 305 insertions, 2 deletions
diff --git a/doc/guix.texi b/doc/guix.texi index 500f6e78e9..98b388886b 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -142,6 +142,7 @@ Copyright @copyright{} 2025 Sergio Pastor Pérez@* Copyright @copyright{} 2024 Evgeny Pisemsky@* Copyright @copyright{} 2025 jgart@* Copyright @copyright{} 2025 Artur Wroblewski@* +Copyright @copyright{} 2025 Edouard Klein@* Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or @@ -43401,6 +43402,148 @@ this value will override the @code{ttl} when used for narinfo requests. @node Linux Services @subsection Linux Services +@subsubheading VFS Mapping Service +@cindex VFS mapping +@cindex Virtual File System mapping +@cindex bind mounts, service +@cindex overlay mounts, service + +The VFS (Virtual File System) Mapping service allows you to bind a file +name under a different name in the global namespace. For example, this +is used by the Shared Cache Service (@pxref{Guix Services}) to bind the +@samp{root} user's cache over another user's cache. Another use case +may be to expose game data from the store over your home directory, and +apply modifications there. + +The service has three @emph{policies} to choose from with respect to +access rights on the bound over path: + +@table @code +@item 'bind +Use a bind mount; the ownership and file +permissions of the destination are the same as the source. + +@item 'translate +Use bindfs to make the destination appear as owned by the target user +and group. Writes will be propagated back to the source as if made by +the original owner. + +@item 'overlay +Use an overlay to make the destination appear as owned by the target +user and group. Writes will not appear in the source, but will be stored +in a target-user-owned @samp{-overlay} suffixed directory near the +destination. +@end table + +Here is an example configuration that exposes the default data directory +of @command{tuxpaint} under the home directory of @code{alice}. Any +modification made by Alice to the brushes or stamps will not propagate +back to the store (which is read-only anyway). + +@lisp +(operating-system + ;; @dots{} + (services + (cons* + (service vfs-mapping-service-type + (vfs-mapping-configuration + (bindings (list (vfs-mapping + (source #~(string-append #$tuxpaint + "/share/tuxpaint")) + (destination "/home/alice/.tuxpaint") + (user "alice") + (policy 'overlay) + (name "alice-tuxpaint-overlay")))))) + %desktop-services))) +@end lisp + +The service can also be extended by providing a list of +@code{vfs-mapping}, allowing for easier splitting of configuration. + +@lisp +(operating-system + ;; @dots{} + (services + (cons* + (simple-service 'bob-too-wants-to-hack-on-tuxpaint + vfs-mapping-service-type + (list (vfs-mapping + (source #~(string-append #$tuxpaint "/share/tuxpaint")) + (destination "/home/bob/.tuxpaint") + (user "bob") + (policy 'overlay) + (name "bob-tuxpaint-overlay")))) + %desktop-services))) +@end lisp + +The @code{source} file name must exist for the service to start. The +@code{destination} directory will be created if it does not exist. + +@defvar vfs-mapping-service-type +Service type for binding a directory in multiple places on the file +system. + +The access rights are either the same in source and destination +(@code{'bind}), or writes are translated back to the sources as if made +by the destination's owner (@code{'translate}), or kept in an overlay +directory near the destination (@code{'overlay}). The service's value +must be a @code{vfs-mapping-configuration} object. +@end defvar + +@deftp {Data Type} vfs-mapping-configuration +Data type representing the configuration of the vfs-mapping service. + +@table @asis +@item @code{bindfs} (default: @code{#~(string-append #$bindfs "/bin/bindfs")}) +The @command{bindfs} command to use for mounting. + +@item @code{fusermount} (default: @code{#~(string-append #$fuse-2 "/bin/fusermount")}) +The @command{fusermount} command to use. + +@item @code{umount} (default: @code{#~(string-append #$util-linux+udev "/bin/umount")}) +The @command{umount} command to use. + +@item @code{bindings} (default: @code{'()}) +A list of @code{vfs-mapping} records. + +@end table +@end deftp + +@deftp {Data Type} vfs-mapping +Data type representing the configuration for a single shared directory. + +@table @asis +@item @code{source} +The source file name to be shared. + +@item @code{destination} +The destination at which the contents of @code{source} will be exposed. + +@item @code{policy} (default: @code{'translate}) +Either @code{'bind} (same ownership and access rights for @code{source} +and @code{destination}), @code{'translate} (read-write, with writes +translated as if made by @code{source}'s owner), or @code{'overlay} +(read-only with @code{user}-owned writable overlay). + +@item @code{user} (default: @code{#f}) +The user that will own the @code{destination} directory, and appear to +own its content. It must be kept at its default @code{#f} value when +using the @code{'bind} policy. + +@item @code{group} (default: @code{"users"}) +The group that will own the @code{destination} directory, and appear to +own its content. + +@item @code{name} (default: @code{"<src>-[<policy>]-><dst>"}) +The name of the shepherd service to mount and unmount the binding. + +@item @code{requirement} (default: @code{'(file-systems user-homes)}) +The list of services that Shepherd ought to provision before trying to +mount. + +@end table +@end deftp + @cindex oom @cindex out of memory killer @cindex earlyoom diff --git a/gnu/services/linux.scm b/gnu/services/linux.scm index d7aee1b82e..80c35717a9 100644 --- a/gnu/services/linux.scm +++ b/gnu/services/linux.scm @@ -7,6 +7,7 @@ ;;; Copyright © 2022 Josselin Poiret <dev@jpoiret.xyz> ;;; Copyright © 2023 Bruno Victal <mirai@makinata.eu> ;;; Copyright © 2023 Felix Lechner <felix.lechner@lease-up.com> +;;; Copyright © 2025 Edouard Klein <edk@beaver-labs.com> ;;; ;;; This file is part of GNU Guix. ;;; @@ -36,6 +37,7 @@ #:use-module (gnu services configuration) #:use-module (gnu services shepherd) #:use-module (gnu packages linux) + #:use-module (gnu packages file-systems) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) #:use-module (srfi srfi-34) @@ -101,7 +103,11 @@ zram-device-configuration-compression-algorithm zram-device-configuration-memory-limit zram-device-configuration-priority - zram-device-service-type)) + zram-device-service-type + + vfs-mapping-service-type + vfs-mapping-configuration + vfs-mapping)) ;;; @@ -547,7 +553,6 @@ the Linux @code{cachefiles} module.") ;;; ;;; Zram device ;;; - (define-record-type* <zram-device-configuration> zram-device-configuration make-zram-device-configuration zram-device-configuration? @@ -628,3 +633,158 @@ placed in a udev rules file." (service-extension udev-service-type (compose list zram-device-udev-rule)))) (description "Creates a zram swap device."))) + + +;;; +;;; VFS Mapping. +;;; + +(define-record-type* <vfs-mapping> + vfs-mapping make-vfs-mapping + vfs-mapping? + (source vfs-mapping-source) + (destination vfs-mapping-destination) + (policy vfs-mapping-policy + (default 'translate)) + (user vfs-mapping-user + (default #f)) + (group vfs-mapping-group + (default "users")) + (name vfs-mapping-name + (default (string-append + (vfs-mapping-source this-record) "-[" + (vfs-mapping-policy this-record) "]->" + (vfs-mapping-destination this-record))) + (thunked)) + (requirement vfs-mapping-requirement + (default '(file-systems user-homes)))) + +(define (vfs-mapping-policy? x) + (and (symbol? x) + (or (memq x '(bind translate overlay))))) + +(define (path-like? x) + (or (string? x) + (file-like? x) + (gexp? x))) + +(define (valid-vfs-mapping? x) + ;; User must be set iff we are going to use it + (and (vfs-mapping? x) + (path-like? (vfs-mapping-source x)) + (path-like? (vfs-mapping-destination x)) + (string? (vfs-mapping-name x)) + (vfs-mapping-policy? (vfs-mapping-policy x)) + (cond + ((eq? (vfs-mapping-policy x) 'bind) + (not (vfs-mapping-user x))) + (#t + (and (string? (vfs-mapping-user x)) + (string? (vfs-mapping-group x))))))) + +(define list-of-vfs-mapping? (list-of valid-vfs-mapping?)) + +(define-configuration/no-serialization vfs-mapping-configuration + (bindfs (gexp #~(string-append #$bindfs "/bin/bindfs")) + "The bindfs command to use.") + (fusermount (gexp #~(string-append #$fuse-2 "/bin/fusermount")) + "The fusermount command to use.") + (umount (gexp #~(string-append #$util-linux+udev "/bin/umount")) + "The umount command to use.") + (bindings (list-of-vfs-mapping '()) + "The list of bindings to mount")) + +(define vfs-mapping-shepherd-services + (match-record-lambda <vfs-mapping-configuration> + (fusermount bindfs umount bindings) + (map + (match-record-lambda <vfs-mapping> + (source destination policy user group name requirement) + (shepherd-service + ;; Each binding has its own service + (provision (list (string->symbol name))) + ;; Make sure the homes are already present + (requirement requirement) + (stop + #~(lambda args + (match (quote #$policy) + ('bind (invoke #$umount #$destination)) + ('translate (invoke #$fusermount "-u" #$destination)) + ('overlay (begin + ;; First the bindfs + (invoke #$fusermount "-u" #$destination) + ;; then the overlay + (invoke #$umount #$destination)))) + #f)) + (start + #~(lambda args + (define (mkdir-recursively dir user group) + ;; Like mkdir-p, but chown all created directories to the + ;; specified user. + (unless (eq? dir "/") + (when (not (file-exists? dir)) + (mkdir-recursively (dirname dir) user group) + (mkdir dir) + (let* ((pw (getpw user)) + (uid (passwd:uid pw)) + (gid (passwd:gid pw))) + (chown dir uid gid))))) + (mkdir-recursively #$destination #$user #$group) + (let* ((stat (stat #$source)) + (uid (stat:uid stat)) + (gid (stat:gid stat)) + (source-user (passwd:name (getpwuid uid))) + (source-group (group:name (getgrgid gid)))) + (match (quote #$policy) + ('bind + (mount #$source #$destination + #f ;type + MS_BIND)) ;flags (bind mount) + ('translate + (invoke + #$bindfs + (string-append "--create-for-group=" source-group) + (string-append "--create-for-user=" source-user) + (string-append "--force-user=" #$user) + (string-append "--force-group=" #$group) + "-o" "nonempty" + #$source #$destination)) + ('overlay + (let ((overlay (string-append #$destination "-overlay")) + (workdir (string-append #$destination "-workdir"))) + (mkdir-recursively overlay #$user #$group) + (mkdir-recursively workdir #$user #$group) + (mount "overlay" ;source + #$destination + "overlay" ;type + 0 ;flags + (string-append ;options + "lowerdir=" #$source "," + "upperdir=" overlay "," + "workdir=" workdir)) + ;; Remount the target over itself to make it appear as if + ;; owned by user-name and user-group. + (invoke + #$bindfs + (string-append "--create-for-group=" source-group) + (string-append "--create-for-user=" source-user) + (string-append "--force-user=" #$user) + (string-append "--force-group=" #$group) + #$destination #$destination))))) + #t)))) + bindings))) + +(define vfs-mapping-service-type + (service-type + (name 'vfs-mapping) + (extensions (list + (service-extension shepherd-root-service-type + vfs-mapping-shepherd-services))) + (compose concatenate) + (extend (lambda (original extensions) + (vfs-mapping-configuration + (inherit original) + (bindings (append (vfs-mapping-configuration-bindings original) + extensions))))) + (default-value (vfs-mapping-configuration)) + (description "Share or expose a file name under a different name."))) |