1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.ntfy-sh;
7
8 settingsFormat = pkgs.formats.yaml { };
9in
10
11{
12 options.services.ntfy-sh = {
13 enable = mkEnableOption (mdDoc "[ntfy-sh](https://ntfy.sh), a push notification service");
14
15 package = mkOption {
16 type = types.package;
17 default = pkgs.ntfy-sh;
18 defaultText = literalExpression "pkgs.ntfy-sh";
19 description = mdDoc "The ntfy.sh package to use.";
20 };
21
22 user = mkOption {
23 default = "ntfy-sh";
24 type = types.str;
25 description = lib.mdDoc "User the ntfy-sh server runs under.";
26 };
27
28 group = mkOption {
29 default = "ntfy-sh";
30 type = types.str;
31 description = lib.mdDoc "Primary group of ntfy-sh user.";
32 };
33
34 settings = mkOption {
35 type = types.submodule {
36 freeformType = settingsFormat.type;
37 options = {
38 base-url = mkOption {
39 type = types.str;
40 example = "https://ntfy.example";
41 description = lib.mdDoc ''
42 Public facing base URL of the service
43
44 This setting is required for any of the following features:
45 - attachments (to return a download URL)
46 - e-mail sending (for the topic URL in the email footer)
47 - iOS push notifications for self-hosted servers
48 (to calculate the Firebase poll_request topic)
49 - Matrix Push Gateway (to validate that the pushkey is correct)
50 '';
51 };
52 };
53 };
54
55 default = { };
56
57 example = literalExpression ''
58 {
59 listen-http = ":8080";
60 }
61 '';
62
63 description = mdDoc ''
64 Configuration for ntfy.sh, supported values are [here](https://ntfy.sh/docs/config/#config-options).
65 '';
66 };
67 };
68
69 config =
70 let
71 configuration = settingsFormat.generate "server.yml" cfg.settings;
72 in
73 mkIf cfg.enable {
74 # to configure access control via the cli
75 environment = {
76 etc."ntfy/server.yml".source = configuration;
77 systemPackages = [ cfg.package ];
78 };
79
80 services.ntfy-sh.settings = {
81 auth-file = mkDefault "/var/lib/ntfy-sh/user.db";
82 listen-http = mkDefault "127.0.0.1:2586";
83 attachment-cache-dir = mkDefault "/var/lib/ntfy-sh/attachments";
84 cache-file = mkDefault "/var/lib/ntfy-sh/cache-file.db";
85 };
86
87 systemd.tmpfiles.rules = [
88 "f ${cfg.settings.auth-file} 0600 ${cfg.user} ${cfg.group} - -"
89 "d ${cfg.settings.attachment-cache-dir} 0700 ${cfg.user} ${cfg.group} - -"
90 "f ${cfg.settings.cache-file} 0600 ${cfg.user} ${cfg.group} - -"
91 ];
92
93 systemd.services.ntfy-sh = {
94 description = "Push notifications server";
95
96 wantedBy = [ "multi-user.target" ];
97 after = [ "network.target" ];
98
99 serviceConfig = {
100 ExecStart = "${cfg.package}/bin/ntfy serve -c ${configuration}";
101 User = cfg.user;
102 StateDirectory = "ntfy-sh";
103
104 DynamicUser = true;
105 AmbientCapabilities = "CAP_NET_BIND_SERVICE";
106 PrivateTmp = true;
107 NoNewPrivileges = true;
108 CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
109 ProtectSystem = "full";
110 ProtectKernelTunables = true;
111 ProtectKernelModules = true;
112 ProtectKernelLogs = true;
113 ProtectControlGroups = true;
114 PrivateDevices = true;
115 RestrictSUIDSGID = true;
116 RestrictNamespaces = true;
117 RestrictRealtime = true;
118 MemoryDenyWriteExecute = true;
119 # Upstream Recommandation
120 LimitNOFILE = 20500;
121 };
122 };
123
124 users.groups = optionalAttrs (cfg.group == "ntfy-sh") {
125 ntfy-sh = { };
126 };
127
128 users.users = optionalAttrs (cfg.user == "ntfy-sh") {
129 ntfy-sh = {
130 isSystemUser = true;
131 group = cfg.group;
132 };
133 };
134 };
135}