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