1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 mkIf
11 mkOption
12 mkEnableOption
13 mkPackageOption
14 mkDefault
15 types
16 concatMapStringsSep
17 generators
18 ;
19 cfg = config.services.open-web-calendar;
20
21 nixosSpec = calendarSettingsFormat.generate "nixos_specification.json" cfg.calendarSettings;
22 finalPackage = cfg.package.override {
23 # The calendarSettings need to be merged with the default_specification.yml
24 # in the source. This way we use upstreams default values but keep everything overridable.
25 defaultSpecificationFile = pkgs.runCommand "custom-default_specification.yml" { } ''
26 ${pkgs.yq}/bin/yq -s '.[0] * .[1]' ${cfg.package}/${cfg.package.defaultSpecificationPath} ${nixosSpec} > $out
27 '';
28 };
29
30 inherit (finalPackage) python;
31 pythonEnv = python.buildEnv.override {
32 extraLibs = [
33 (python.pkgs.toPythonModule finalPackage)
34 # Allows Gunicorn to set a meaningful process name
35 python.pkgs.gunicorn.optional-dependencies.setproctitle
36 ];
37 };
38
39 settingsFormat = pkgs.formats.keyValue { };
40 calendarSettingsFormat = pkgs.formats.json { };
41in
42{
43 options.services.open-web-calendar = {
44
45 enable = mkEnableOption "OpenWebCalendar service";
46
47 package = mkPackageOption pkgs "open-web-calendar" { };
48
49 domain = mkOption {
50 type = types.str;
51 description = "The domain under which open-web-calendar is made available";
52 example = "open-web-calendar.example.org";
53 };
54
55 settings = mkOption {
56 type = types.submodule {
57 freeformType = settingsFormat.type;
58 options = {
59 ALLOWED_HOSTS = mkOption {
60 type = types.str;
61 readOnly = true;
62 description = ''
63 The hosts that the Open Web Calendar permits. This is required to
64 mitigate the Host Header Injection vulnerability.
65
66 We always set this to the empty list, as Nginx already checks the Host header.
67 '';
68 default = "";
69 };
70 };
71 };
72 default = { };
73 description = ''
74 Configuration for the server. These are set as environment variables to the gunicorn/flask service.
75
76 See the documentation options in <https://open-web-calendar.quelltext.eu/host/configure/#configuring-the-server>.
77 '';
78 };
79
80 calendarSettings = mkOption {
81 type = types.submodule {
82 freeformType = calendarSettingsFormat.type;
83 options = { };
84 };
85 default = { };
86 description = ''
87 Configure the default calendar.
88
89 See the documentation options in <https://open-web-calendar.quelltext.eu/host/configure/#configuring-the-default-calendar> and <https://github.com/niccokunzmann/open-web-calendar/blob/master/open_web_calendar/default_specification.yml>.
90
91 Individual calendar instances can be further configured outside this module, by specifying the `specification_url` parameter.
92 '';
93 };
94
95 };
96
97 config = mkIf cfg.enable {
98
99 assertions = [
100 {
101 assertion = !cfg.settings ? "PORT";
102 message = ''
103 services.open-web-calendar.settings.PORT can't be set, as the service uses a unix socket.
104 '';
105 }
106 ];
107
108 systemd.sockets.open-web-calendar = {
109 before = [ "nginx.service" ];
110 wantedBy = [ "sockets.target" ];
111 socketConfig = {
112 ListenStream = "/run/open-web-calendar/socket";
113 SocketUser = "open-web-calendar";
114 SocketGroup = "open-web-calendar";
115 SocketMode = "770";
116 };
117 };
118
119 systemd.services.open-web-calendar = {
120 description = "Open Web Calendar";
121 after = [ "network.target" ];
122 environment.PYTHONPATH = "${pythonEnv}/${python.sitePackages}/";
123 serviceConfig = {
124 Type = "notify";
125 NotifyAccess = "all";
126 ExecStart = ''
127 ${pythonEnv.pkgs.gunicorn}/bin/gunicorn \
128 --name=open-web-calendar \
129 --bind='unix:///run/open-web-calendar/socket' \
130 open_web_calendar.app:app
131 '';
132 EnvironmentFile = settingsFormat.generate "open-web-calendar.env" cfg.settings;
133 ExecReload = "kill -s HUP $MAINPID";
134 KillMode = "mixed";
135 PrivateTmp = true;
136 RuntimeDirectory = "open-web-calendar";
137 User = "open-web-calendar";
138 Group = "open-web-calendar";
139 };
140 };
141
142 users.users.open-web-calendar = {
143 isSystemUser = true;
144 group = "open-web-calendar";
145 };
146
147 services.nginx = {
148 enable = true;
149 virtualHosts."${cfg.domain}" = {
150 forceSSL = mkDefault true;
151 enableACME = mkDefault true;
152 locations."/".proxyPass = "http://unix:///run/open-web-calendar/socket";
153 };
154 };
155
156 users.groups.open-web-calendar.members = [ config.services.nginx.user ];
157
158 };
159
160 meta.maintainers = with lib.maintainers; [ erictapen ];
161
162}