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