at 25.11-pre 6.8 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 9 pkg = config.hardware.sane.backends-package.override { 10 scanSnapDriversUnfree = config.hardware.sane.drivers.scanSnap.enable; 11 scanSnapDriversPackage = config.hardware.sane.drivers.scanSnap.package; 12 }; 13 14 sanedConf = pkgs.writeTextFile { 15 name = "saned.conf"; 16 destination = "/etc/sane.d/saned.conf"; 17 text = '' 18 localhost 19 ${config.services.saned.extraConfig} 20 ''; 21 }; 22 23 netConf = pkgs.writeTextFile { 24 name = "net.conf"; 25 destination = "/etc/sane.d/net.conf"; 26 text = '' 27 ${lib.optionalString config.services.saned.enable "localhost"} 28 ${config.hardware.sane.netConf} 29 ''; 30 }; 31 32 env = { 33 SANE_CONFIG_DIR = "/etc/sane-config"; 34 LD_LIBRARY_PATH = [ "/etc/sane-libs" ]; 35 }; 36 37 backends = 38 [ 39 pkg 40 netConf 41 ] 42 ++ lib.optional config.services.saned.enable sanedConf 43 ++ config.hardware.sane.extraBackends; 44 saneConfig = pkgs.mkSaneConfig { 45 paths = backends; 46 inherit (config.hardware.sane) disabledDefaultBackends; 47 }; 48 49 enabled = config.hardware.sane.enable || config.services.saned.enable; 50 51in 52 53{ 54 55 ###### interface 56 57 options = { 58 59 hardware.sane.enable = lib.mkOption { 60 type = lib.types.bool; 61 default = false; 62 description = '' 63 Enable support for SANE scanners. 64 65 ::: {.note} 66 Users in the "scanner" group will gain access to the scanner, or the "lp" group if it's also a printer. 67 ::: 68 ''; 69 }; 70 71 hardware.sane.backends-package = lib.mkOption { 72 type = lib.types.package; 73 default = pkgs.sane-backends; 74 defaultText = lib.literalExpression "pkgs.sane-backends"; 75 description = "Backends driver package to use."; 76 }; 77 78 hardware.sane.snapshot = lib.mkOption { 79 type = lib.types.bool; 80 default = false; 81 description = "Use a development snapshot of SANE scanner drivers."; 82 }; 83 84 hardware.sane.extraBackends = lib.mkOption { 85 type = lib.types.listOf lib.types.path; 86 default = [ ]; 87 description = '' 88 Packages providing extra SANE backends to enable. 89 90 ::: {.note} 91 The example contains the package for HP scanners, and the package for 92 Apple AirScan and Microsoft WSD support (supports many 93 vendors/devices). 94 ::: 95 ''; 96 example = lib.literalExpression "[ pkgs.hplipWithPlugin pkgs.sane-airscan ]"; 97 }; 98 99 hardware.sane.disabledDefaultBackends = lib.mkOption { 100 type = lib.types.listOf lib.types.str; 101 default = [ ]; 102 example = [ "v4l" ]; 103 description = '' 104 Names of backends which are enabled by default but should be disabled. 105 See `$SANE_CONFIG_DIR/dll.conf` for the list of possible names. 106 ''; 107 }; 108 109 hardware.sane.configDir = lib.mkOption { 110 type = lib.types.str; 111 internal = true; 112 description = "The value of SANE_CONFIG_DIR."; 113 }; 114 115 hardware.sane.netConf = lib.mkOption { 116 type = lib.types.lines; 117 default = ""; 118 example = "192.168.0.16"; 119 description = '' 120 Network hosts that should be probed for remote scanners. 121 ''; 122 }; 123 124 hardware.sane.drivers.scanSnap.enable = lib.mkOption { 125 type = lib.types.bool; 126 default = false; 127 example = true; 128 description = '' 129 Whether to enable drivers for the Fujitsu ScanSnap scanners. 130 131 The driver files are unfree and extracted from the Windows driver image. 132 ''; 133 }; 134 135 hardware.sane.drivers.scanSnap.package = lib.mkPackageOption pkgs [ "sane-drivers" "epjitsu" ] { 136 extraDescription = '' 137 Useful if you want to extract the driver files yourself. 138 139 The process is described in the {file}`/etc/sane.d/epjitsu.conf` file in 140 the `sane-backends` package. 141 ''; 142 }; 143 144 hardware.sane.openFirewall = lib.mkOption { 145 type = lib.types.bool; 146 default = false; 147 description = '' 148 Open ports needed for discovery of scanners on the local network, e.g. 149 needed for Canon scanners (BJNP protocol). 150 ''; 151 }; 152 153 services.saned.enable = lib.mkOption { 154 type = lib.types.bool; 155 default = false; 156 description = '' 157 Enable saned network daemon for remote connection to scanners. 158 159 saned would be run from `scanner` user; to allow 160 access to hardware that doesn't have `scanner` group 161 you should add needed groups to this user. 162 ''; 163 }; 164 165 services.saned.extraConfig = lib.mkOption { 166 type = lib.types.lines; 167 default = ""; 168 example = "192.168.0.0/24"; 169 description = '' 170 Extra saned configuration lines. 171 ''; 172 }; 173 174 }; 175 176 ###### implementation 177 178 config = lib.mkMerge [ 179 (lib.mkIf enabled { 180 hardware.sane.configDir = lib.mkDefault "${saneConfig}/etc/sane.d"; 181 182 environment.systemPackages = backends; 183 environment.sessionVariables = env; 184 environment.etc."sane-config".source = config.hardware.sane.configDir; 185 environment.etc."sane-libs".source = "${saneConfig}/lib/sane"; 186 services.udev.packages = backends; 187 # sane sets up udev rules that tag scanners with `uaccess`. This way, physically logged in users 188 # can access them without belonging to the `scanner` group. However, the `scanner` user used by saned 189 # does not have a real logind seat, so `uaccess` is not enough. 190 services.udev.extraRules = '' 191 ENV{DEVNAME}!="", ENV{libsane_matched}=="yes", RUN+="${pkgs.acl}/bin/setfacl -m g:scanner:rw $env{DEVNAME}" 192 ''; 193 194 users.groups.scanner.gid = config.ids.gids.scanner; 195 networking.firewall.allowedUDPPorts = lib.mkIf config.hardware.sane.openFirewall [ 8612 ]; 196 197 systemd.tmpfiles.rules = [ 198 "d /var/lock/sane 0770 root scanner - -" 199 ]; 200 }) 201 202 (lib.mkIf config.services.saned.enable { 203 networking.firewall.connectionTrackingModules = [ "sane" ]; 204 205 systemd.services."saned@" = { 206 description = "Scanner Service"; 207 environment = lib.mapAttrs (name: val: toString val) env; 208 serviceConfig = { 209 User = "scanner"; 210 Group = "scanner"; 211 ExecStart = "${pkg}/bin/saned"; 212 }; 213 }; 214 215 systemd.sockets.saned = { 216 description = "saned incoming socket"; 217 wantedBy = [ "sockets.target" ]; 218 listenStreams = [ 219 "0.0.0.0:6566" 220 "[::]:6566" 221 ]; 222 socketConfig = { 223 # saned needs to distinguish between IPv4 and IPv6 to open matching data sockets. 224 BindIPv6Only = "ipv6-only"; 225 Accept = true; 226 MaxConnections = 64; 227 }; 228 }; 229 230 users.users.scanner = { 231 uid = config.ids.uids.scanner; 232 group = "scanner"; 233 extraGroups = [ "lp" ] ++ lib.optionals config.services.avahi.enable [ "avahi" ]; 234 }; 235 }) 236 ]; 237 238}