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