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