1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 pkg = pkgs.sane-backends;
8
9 sanedConf = pkgs.writeTextFile {
10 name = "saned.conf";
11 destination = "/etc/sane.d/saned.conf";
12 text = ''
13 localhost
14 ${config.services.saned.extraConfig}
15 '';
16 };
17
18 netConf = pkgs.writeTextFile {
19 name = "net.conf";
20 destination = "/etc/sane.d/net.conf";
21 text = ''
22 ${lib.optionalString config.services.saned.enable "localhost"}
23 ${config.hardware.sane.netConf}
24 '';
25 };
26
27 env = {
28 SANE_CONFIG_DIR = config.hardware.sane.configDir;
29 LD_LIBRARY_PATH = [ "${saneConfig}/lib/sane" ];
30 };
31
32 backends = [ pkg netConf ] ++ optional config.services.saned.enable sanedConf ++ config.hardware.sane.extraBackends;
33 saneConfig = pkgs.mkSaneConfig { paths = backends; inherit (config.hardware.sane) disabledDefaultBackends; };
34
35 enabled = config.hardware.sane.enable || config.services.saned.enable;
36
37in
38
39{
40
41 ###### interface
42
43 options = {
44
45 hardware.sane.enable = mkOption {
46 type = types.bool;
47 default = false;
48 description = ''
49 Enable support for SANE scanners.
50
51 <note><para>
52 Users in the "scanner" group will gain access to the scanner, or the "lp" group if it's also a printer.
53 </para></note>
54 '';
55 };
56
57 hardware.sane.snapshot = mkOption {
58 type = types.bool;
59 default = false;
60 description = "Use a development snapshot of SANE scanner drivers.";
61 };
62
63 hardware.sane.extraBackends = mkOption {
64 type = types.listOf types.path;
65 default = [];
66 description = ''
67 Packages providing extra SANE backends to enable.
68
69 <note><para>
70 The example contains the package for HP scanners.
71 </para></note>
72 '';
73 example = literalExample "[ pkgs.hplipWithPlugin ]";
74 };
75
76 hardware.sane.disabledDefaultBackends = mkOption {
77 type = types.listOf types.str;
78 default = [];
79 example = [ "v4l" ];
80 description = ''
81 Names of backends which are enabled by default but should be disabled.
82 See <literal>$SANE_CONFIG_DIR/dll.conf</literal> for the list of possible names.
83 '';
84 };
85
86 hardware.sane.configDir = mkOption {
87 type = types.str;
88 internal = true;
89 description = "The value of SANE_CONFIG_DIR.";
90 };
91
92 hardware.sane.netConf = mkOption {
93 type = types.lines;
94 default = "";
95 example = "192.168.0.16";
96 description = ''
97 Network hosts that should be probed for remote scanners.
98 '';
99 };
100
101 services.saned.enable = mkOption {
102 type = types.bool;
103 default = false;
104 description = ''
105 Enable saned network daemon for remote connection to scanners.
106
107 saned would be runned from <literal>scanner</literal> user; to allow
108 access to hardware that doesn't have <literal>scanner</literal> group
109 you should add needed groups to this user.
110 '';
111 };
112
113 services.saned.extraConfig = mkOption {
114 type = types.lines;
115 default = "";
116 example = "192.168.0.0/24";
117 description = ''
118 Extra saned configuration lines.
119 '';
120 };
121
122 };
123
124
125 ###### implementation
126
127 config = mkMerge [
128 (mkIf enabled {
129 hardware.sane.configDir = mkDefault "${saneConfig}/etc/sane.d";
130
131 environment.systemPackages = backends;
132 environment.sessionVariables = env;
133 services.udev.packages = backends;
134
135 users.groups.scanner.gid = config.ids.gids.scanner;
136 })
137
138 (mkIf config.services.saned.enable {
139 networking.firewall.connectionTrackingModules = [ "sane" ];
140
141 systemd.services."saned@" = {
142 description = "Scanner Service";
143 environment = mapAttrs (name: val: toString val) env;
144 serviceConfig = {
145 User = "scanner";
146 Group = "scanner";
147 ExecStart = "${pkg}/bin/saned";
148 };
149 };
150
151 systemd.sockets.saned = {
152 description = "saned incoming socket";
153 wantedBy = [ "sockets.target" ];
154 listenStreams = [ "0.0.0.0:6566" "[::]:6566" ];
155 socketConfig = {
156 # saned needs to distinguish between IPv4 and IPv6 to open matching data sockets.
157 BindIPv6Only = "ipv6-only";
158 Accept = true;
159 MaxConnections = 64;
160 };
161 };
162
163 users.users.scanner = {
164 uid = config.ids.uids.scanner;
165 group = "scanner";
166 extraGroups = [ "lp" ] ++ optionals config.services.avahi.enable [ "avahi" ];
167 };
168 })
169 ];
170
171}