1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 mkEnableOption
11 mkPackageOption
12 mkOption
13 types
14 literalExpression
15 mkIf
16 mkDefault
17 ;
18 cfg = config.services.miniflux;
19
20 defaultAddress = "localhost:8080";
21
22 pgbin = "${config.services.postgresql.package}/bin";
23 preStart = pkgs.writeScript "miniflux-pre-start" ''
24 #!${pkgs.runtimeShell}
25 ${pgbin}/psql "miniflux" -c "CREATE EXTENSION IF NOT EXISTS hstore"
26 '';
27in
28
29{
30 options = {
31 services.miniflux = {
32 enable = mkEnableOption "miniflux";
33
34 package = mkPackageOption pkgs "miniflux" { };
35
36 createDatabaseLocally = mkOption {
37 type = types.bool;
38 default = true;
39 description = ''
40 Whether a PostgreSQL database should be automatically created and
41 configured on the local host. If set to `false`, you need provision a
42 database yourself and make sure to create the hstore extension in it.
43 '';
44 };
45
46 config = mkOption {
47 type =
48 with types;
49 attrsOf (oneOf [
50 str
51 int
52 ]);
53 example = literalExpression ''
54 {
55 CLEANUP_FREQUENCY = 48;
56 LISTEN_ADDR = "localhost:8080";
57 }
58 '';
59 description = ''
60 Configuration for Miniflux, refer to
61 <https://miniflux.app/docs/configuration.html>
62 for documentation on the supported values.
63
64 Correct configuration for the database is already provided.
65 By default, listens on ${defaultAddress}.
66 '';
67 };
68
69 adminCredentialsFile = mkOption {
70 type = types.nullOr types.path;
71 default = null;
72 description = ''
73 File containing the ADMIN_USERNAME and
74 ADMIN_PASSWORD (length >= 6) in the format of
75 an EnvironmentFile=, as described by {manpage}`systemd.exec(5)`.
76 '';
77 example = "/etc/nixos/miniflux-admin-credentials";
78 };
79 };
80 };
81
82 config = mkIf cfg.enable {
83 assertions = [
84 {
85 assertion = cfg.config.CREATE_ADMIN == 0 || cfg.adminCredentialsFile != null;
86 message = "services.miniflux.adminCredentialsFile must be set if services.miniflux.config.CREATE_ADMIN is 1";
87 }
88 ];
89 services.miniflux.config = {
90 LISTEN_ADDR = mkDefault defaultAddress;
91 DATABASE_URL = lib.mkIf cfg.createDatabaseLocally "user=miniflux host=/run/postgresql dbname=miniflux";
92 RUN_MIGRATIONS = 1;
93 CREATE_ADMIN = lib.mkDefault 1;
94 WATCHDOG = 1;
95 };
96
97 services.postgresql = lib.mkIf cfg.createDatabaseLocally {
98 enable = true;
99 ensureUsers = [
100 {
101 name = "miniflux";
102 ensureDBOwnership = true;
103 }
104 ];
105 ensureDatabases = [ "miniflux" ];
106 };
107
108 systemd.services.miniflux-dbsetup = lib.mkIf cfg.createDatabaseLocally {
109 description = "Miniflux database setup";
110 requires = [ "postgresql.service" ];
111 after = [
112 "network.target"
113 "postgresql.service"
114 ];
115 serviceConfig = {
116 Type = "oneshot";
117 User = config.services.postgresql.superUser;
118 ExecStart = preStart;
119 };
120 };
121
122 systemd.services.miniflux = {
123 description = "Miniflux service";
124 wantedBy = [ "multi-user.target" ];
125 requires = lib.optional cfg.createDatabaseLocally "miniflux-dbsetup.service";
126 after =
127 [ "network.target" ]
128 ++ lib.optionals cfg.createDatabaseLocally [
129 "postgresql.service"
130 "miniflux-dbsetup.service"
131 ];
132
133 serviceConfig = {
134 Type = "notify";
135 ExecStart = lib.getExe cfg.package;
136 User = "miniflux";
137 DynamicUser = true;
138 RuntimeDirectory = "miniflux";
139 RuntimeDirectoryMode = "0750";
140 EnvironmentFile = lib.mkIf (cfg.adminCredentialsFile != null) cfg.adminCredentialsFile;
141 WatchdogSec = 60;
142 WatchdogSignal = "SIGKILL";
143 Restart = "always";
144 RestartSec = 5;
145
146 # Hardening
147 CapabilityBoundingSet = [ "" ];
148 DeviceAllow = [ "" ];
149 LockPersonality = true;
150 MemoryDenyWriteExecute = true;
151 PrivateDevices = true;
152 PrivateUsers = true;
153 ProcSubset = "pid";
154 ProtectClock = true;
155 ProtectControlGroups = true;
156 ProtectHome = true;
157 ProtectHostname = true;
158 ProtectKernelLogs = true;
159 ProtectKernelModules = true;
160 ProtectKernelTunables = true;
161 ProtectProc = "invisible";
162 RestrictAddressFamilies = [
163 "AF_INET"
164 "AF_INET6"
165 "AF_UNIX"
166 ];
167 RestrictNamespaces = true;
168 RestrictRealtime = true;
169 RestrictSUIDSGID = true;
170 SystemCallArchitectures = "native";
171 SystemCallFilter = [
172 "@system-service"
173 "~@privileged"
174 ];
175 UMask = "0077";
176 };
177
178 environment = lib.mapAttrs (_: toString) cfg.config;
179 };
180 environment.systemPackages = [ cfg.package ];
181
182 security.apparmor.policies."bin.miniflux".profile = ''
183 include <tunables/global>
184 ${cfg.package}/bin/miniflux {
185 include <abstractions/base>
186 include <abstractions/nameservice>
187 include <abstractions/ssl_certs>
188 include "${pkgs.apparmorRulesFromClosure { name = "miniflux"; } cfg.package}"
189 r ${cfg.package}/bin/miniflux,
190 r @{sys}/kernel/mm/transparent_hugepage/hpage_pmd_size,
191 rw /run/miniflux/**,
192 }
193 '';
194 };
195}