vuls: init at 0.27.0 (#348530)
[NixPkgs.git] / nixos / modules / services / web-apps / changedetection-io.nix
blobf0d72b1e4d699d492ed9502ef3ac467ea9adc3d8
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.changedetection-io;
7 in
9   options.services.changedetection-io = {
10     enable = mkEnableOption "changedetection-io";
12     user = mkOption {
13       default = "changedetection-io";
14       type = types.str;
15       description = ''
16         User account under which changedetection-io runs.
17       '';
18     };
20     group = mkOption {
21       default = "changedetection-io";
22       type = types.str;
23       description = ''
24         Group account under which changedetection-io runs.
25       '';
26     };
28     listenAddress = mkOption {
29       type = types.str;
30       default = "localhost";
31       description = "Address the server will listen on.";
32     };
34     port = mkOption {
35       type = types.port;
36       default = 5000;
37       description = "Port the server will listen on.";
38     };
40     datastorePath = mkOption {
41       type = types.str;
42       default = "/var/lib/changedetection-io";
43       description = ''
44         The directory used to store all data for changedetection-io.
45       '';
46     };
48     baseURL = mkOption {
49       type = types.nullOr types.str;
50       default = null;
51       example = "https://changedetection-io.example";
52       description = ''
53         The base url used in notifications and `{base_url}` token.
54       '';
55     };
57     behindProxy = mkOption {
58       type = types.bool;
59       default = false;
60       description = ''
61         Enable this option when changedetection-io runs behind a reverse proxy, so that it trusts X-* headers.
62         It is recommend to run changedetection-io behind a TLS reverse proxy.
63       '';
64     };
66     environmentFile = mkOption {
67       type = types.nullOr types.path;
68       default = null;
69       example = "/run/secrets/changedetection-io.env";
70       description = ''
71         Securely pass environment variabels to changedetection-io.
73         This can be used to set for example a frontend password reproducible via `SALTED_PASS`
74         which convinetly also deactivates nags about the hosted version.
75         `SALTED_PASS` should be 64 characters long while the first 32 are the salt and the second the frontend password.
76         It can easily be retrieved from the settings file when first set via the frontend with the following command:
77         ``jq -r .settings.application.password /var/lib/changedetection-io/url-watches.json``
78       '';
79     };
81     webDriverSupport = mkOption {
82       type = types.bool;
83       default = false;
84       description = ''
85         Enable support for fetching web pages using WebDriver and Chromium.
86         This starts a headless chromium controlled by puppeteer in an oci container.
88         ::: {.note}
89         Playwright can currently leak memory.
90         See https://github.com/dgtlmoon/changedetection.io/wiki/Playwright-content-fetcher#playwright-memory-leak
91         :::
92       '';
93     };
95     playwrightSupport = mkOption {
96       type = types.bool;
97       default = false;
98       description = ''
99         Enable support for fetching web pages using playwright and Chromium.
100         This starts a headless Chromium controlled by puppeteer in an oci container.
102         ::: {.note}
103         Playwright can currently leak memory.
104         See https://github.com/dgtlmoon/changedetection.io/wiki/Playwright-content-fetcher#playwright-memory-leak
105         :::
106       '';
107     };
109     chromePort = mkOption {
110       type = types.port;
111       default = 4444;
112       description = ''
113         A free port on which webDriverSupport or playwrightSupport listen on localhost.
114       '';
115     };
116   };
118   config = mkIf cfg.enable {
119     assertions = [
120       {
121         assertion = !((cfg.webDriverSupport == true) && (cfg.playwrightSupport == true));
122         message = "'services.changedetection-io.webDriverSupport' and 'services.changedetection-io.playwrightSupport' cannot be used together.";
123       }
124     ];
126     systemd = let
127       defaultStateDir = cfg.datastorePath == "/var/lib/changedetection-io";
128     in {
129       services.changedetection-io = {
130         wantedBy = [ "multi-user.target" ];
131         after = [ "network.target" ];
132         preStart = ''
133           mkdir -p ${cfg.datastorePath}
134         '';
135         serviceConfig = {
136           User = cfg.user;
137           Group = cfg.group;
138           StateDirectory = mkIf defaultStateDir "changedetection-io";
139           StateDirectoryMode = mkIf defaultStateDir "0750";
140           WorkingDirectory = cfg.datastorePath;
141           Environment = [ "HIDE_REFERER=true" ]
142             ++ lib.optional (cfg.baseURL != null) "BASE_URL=${cfg.baseURL}"
143             ++ lib.optional cfg.behindProxy "USE_X_SETTINGS=1"
144             ++ lib.optional cfg.webDriverSupport "WEBDRIVER_URL=http://127.0.0.1:${toString cfg.chromePort}/wd/hub"
145             ++ lib.optional cfg.playwrightSupport "PLAYWRIGHT_DRIVER_URL=ws://127.0.0.1:${toString cfg.chromePort}/?stealth=1&--disable-web-security=true";
146           EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
147           ExecStart = ''
148             ${pkgs.changedetection-io}/bin/changedetection.py \
149               -h ${cfg.listenAddress} -p ${toString cfg.port} -d ${cfg.datastorePath}
150           '';
151           ProtectHome = true;
152           ProtectSystem = true;
153           Restart = "on-failure";
154         };
155       };
156       tmpfiles.rules = mkIf defaultStateDir [
157         "d ${cfg.datastorePath} 0750 ${cfg.user} ${cfg.group} - -"
158       ];
159     };
161     users = {
162       users = optionalAttrs (cfg.user == "changedetection-io") {
163         "changedetection-io" = {
164           isSystemUser = true;
165           group = "changedetection-io";
166         };
167       };
169       groups = optionalAttrs (cfg.group == "changedetection-io") {
170         "changedetection-io" = { };
171       };
172     };
174     virtualisation = {
175       oci-containers.containers = lib.mkMerge [
176         (mkIf cfg.webDriverSupport {
177           changedetection-io-webdriver = {
178             image = "selenium/standalone-chrome";
179             environment = {
180               VNC_NO_PASSWORD = "1";
181               SCREEN_WIDTH = "1920";
182               SCREEN_HEIGHT = "1080";
183               SCREEN_DEPTH = "24";
184             };
185             ports = [
186               "127.0.0.1:${toString cfg.chromePort}:4444"
187             ];
188             volumes = [
189               "/dev/shm:/dev/shm"
190             ];
191             extraOptions = [ "--network=bridge" ];
192           };
193         })
195         (mkIf cfg.playwrightSupport {
196           changedetection-io-playwright = {
197             image = "browserless/chrome";
198             environment = {
199               SCREEN_WIDTH = "1920";
200               SCREEN_HEIGHT = "1024";
201               SCREEN_DEPTH = "16";
202               ENABLE_DEBUGGER = "false";
203               PREBOOT_CHROME = "true";
204               CONNECTION_TIMEOUT = "300000";
205               MAX_CONCURRENT_SESSIONS = "10";
206               CHROME_REFRESH_TIME = "600000";
207               DEFAULT_BLOCK_ADS = "true";
208               DEFAULT_STEALTH = "true";
209             };
210             ports = [
211               "127.0.0.1:${toString cfg.chromePort}:3000"
212             ];
213             extraOptions = [ "--network=bridge" ];
214           };
215         })
216       ];
217       podman.defaultNetwork.settings.dns_enabled = true;
218     };
219   };