lol
1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.scanservjs;
10 settings = {
11 scanimage = lib.getExe' config.hardware.sane.backends-package "scanimage";
12 convert = lib.getExe' pkgs.imagemagick "convert";
13 tesseract = lib.getExe pkgs.tesseract;
14 # it defaults to config/devices.json, but "config" dir doesn't exist by default and scanservjs doesn't create it
15 devicesPath = "devices.json";
16 }
17 // cfg.settings;
18 settingsFormat = pkgs.formats.json { };
19
20 leafs =
21 attrs:
22 builtins.concatLists (
23 lib.mapAttrsToList (k: v: if builtins.isAttrs v then leafs v else [ v ]) attrs
24 );
25
26 package = pkgs.scanservjs;
27
28 configFile = pkgs.writeText "config.local.js" ''
29 /* eslint-disable no-unused-vars */
30 module.exports = {
31 afterConfig(config) {
32 ${builtins.concatStringsSep "" (
33 leafs (
34 lib.mapAttrsRecursive (path: val: ''
35 ${builtins.concatStringsSep "." path} = ${builtins.toJSON val};
36 '') { config = settings; }
37 )
38 )}
39 ${cfg.extraConfig}
40 },
41
42 afterDevices(devices) {
43 ${cfg.extraDevicesConfig}
44 },
45
46 async afterScan(fileInfo) {
47 ${cfg.runAfterScan}
48 },
49
50 actions: [
51 ${builtins.concatStringsSep ",\n" cfg.extraActions}
52 ],
53 };
54 '';
55
56in
57{
58 options.services.scanservjs = {
59 enable = lib.mkEnableOption "scanservjs";
60 stateDir = lib.mkOption {
61 type = lib.types.str;
62 default = "/var/lib/scanservjs";
63 description = ''
64 State directory for scanservjs.
65 '';
66 };
67 settings = lib.mkOption {
68 default = { };
69 description = ''
70 Config to set in config.local.js's `afterConfig`.
71 '';
72 type = lib.types.submodule {
73 freeformType = settingsFormat.type;
74 options.host = lib.mkOption {
75 type = lib.types.str;
76 description = "The IP to listen on.";
77 default = "127.0.0.1";
78 };
79 options.port = lib.mkOption {
80 type = lib.types.port;
81 description = "The port to listen on.";
82 default = 8080;
83 };
84 };
85 };
86 extraConfig = lib.mkOption {
87 default = "";
88 type = lib.types.lines;
89 description = ''
90 Extra code to add to config.local.js's `afterConfig`.
91 '';
92 };
93 extraDevicesConfig = lib.mkOption {
94 default = "";
95 type = lib.types.lines;
96 description = ''
97 Extra code to add to config.local.js's `afterDevices`.
98 '';
99 };
100 runAfterScan = lib.mkOption {
101 default = "";
102 type = lib.types.lines;
103 description = ''
104 Extra code to add to config.local.js's `afterScan`.
105 '';
106 };
107 extraActions = lib.mkOption {
108 default = [ ];
109 type = lib.types.listOf lib.types.lines;
110 description = "Actions to add to config.local.js's `actions`.";
111 };
112 };
113
114 config = lib.mkIf cfg.enable {
115 hardware.sane.enable = true;
116 users.users.scanservjs = {
117 group = "scanservjs";
118 extraGroups = [
119 "scanner"
120 "lp"
121 ];
122 home = cfg.stateDir;
123 isSystemUser = true;
124 createHome = true;
125 };
126 users.groups.scanservjs = { };
127
128 systemd.services.scanservjs = {
129 description = "scanservjs";
130 after = [ "network.target" ];
131 wantedBy = [ "multi-user.target" ];
132 # yes, those paths are configurable, but the config option isn't always used...
133 # a lot of the time scanservjs just takes those from PATH
134 path = with pkgs; [
135 coreutils
136 config.hardware.sane.backends-package
137 imagemagick
138 tesseract
139 ];
140 environment = {
141 NIX_SCANSERVJS_CONFIG_PATH = configFile;
142 SANE_CONFIG_DIR = "/etc/sane-config";
143 LD_LIBRARY_PATH = "/etc/sane-libs";
144 };
145 serviceConfig = {
146 ExecStart = lib.getExe package;
147 Restart = "always";
148 User = "scanservjs";
149 Group = "scanservjs";
150 WorkingDirectory = cfg.stateDir;
151 };
152 };
153 };
154}