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
66 config =
67 let
68 configuration = settingsFormat.generate "server.yml" cfg.settings;
69 in
70 lib.mkIf cfg.enable {
71 # to configure access control via the cli
72 environment = {
73 etc."ntfy/server.yml".source = configuration;
74 systemPackages = [ cfg.package ];
75 };
76
77 services.ntfy-sh.settings = {
78 auth-file = lib.mkDefault "/var/lib/ntfy-sh/user.db";
79 listen-http = lib.mkDefault "127.0.0.1:2586";
80 attachment-cache-dir = lib.mkDefault "/var/lib/ntfy-sh/attachments";
81 cache-file = lib.mkDefault "/var/lib/ntfy-sh/cache-file.db";
82 };
83
84 systemd.services.ntfy-sh = {
85 description = "Push notifications server";
86
87 wantedBy = [ "multi-user.target" ];
88 after = [ "network.target" ];
89
90 serviceConfig = {
91 ExecStart = "${cfg.package}/bin/ntfy serve -c ${configuration}";
92 User = cfg.user;
93 StateDirectory = "ntfy-sh";
94
95 DynamicUser = true;
96 AmbientCapabilities = "CAP_NET_BIND_SERVICE";
97 PrivateTmp = true;
98 NoNewPrivileges = true;
99 CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
100 ProtectSystem = "full";
101 ProtectKernelTunables = true;
102 ProtectKernelModules = true;
103 ProtectKernelLogs = true;
104 ProtectControlGroups = true;
105 PrivateDevices = true;
106 RestrictSUIDSGID = true;
107 RestrictNamespaces = true;
108 RestrictRealtime = true;
109 MemoryDenyWriteExecute = true;
110 # Upstream Recommandation
111 LimitNOFILE = 20500;
112 };
113 };
114
115 users.groups = lib.optionalAttrs (cfg.group == "ntfy-sh") {
116 ntfy-sh = { };
117 };
118
119 users.users = lib.optionalAttrs (cfg.user == "ntfy-sh") {
120 ntfy-sh = {
121 isSystemUser = true;
122 group = cfg.group;
123 };
124 };
125 };
126}