at master 6.6 kB view raw
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 boolToInt = b: if b then 1 else 0; 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 = types.submodule { 48 freeformType = 49 with types; 50 attrsOf (oneOf [ 51 str 52 int 53 ]); 54 options = { 55 LISTEN_ADDR = mkOption { 56 type = types.str; 57 default = "localhost:8080"; 58 description = '' 59 Address to listen on. Use absolute path for a Unix socket. 60 Multiple addresses can be specified, separated by commas. 61 ''; 62 example = "127.0.0.1:8080, 127.0.0.1:8081"; 63 }; 64 DATABASE_URL = mkOption { 65 type = types.str; 66 defaultText = "user=miniflux host=/run/postgresql dbname=miniflux"; 67 description = '' 68 Postgresql connection parameters. 69 See [lib/pq](https://pkg.go.dev/github.com/lib/pq#hdr-Connection_String_Parameters) for more details. 70 ''; 71 }; 72 RUN_MIGRATIONS = mkOption { 73 type = with types; coercedTo bool boolToInt int; 74 default = true; 75 description = "Run database migrations."; 76 }; 77 CREATE_ADMIN = mkOption { 78 type = with types; coercedTo bool boolToInt int; 79 default = true; 80 description = "Create an admin user from environment variables."; 81 }; 82 WATCHDOG = mkOption { 83 type = with types; coercedTo bool boolToInt int; 84 default = true; 85 description = "Enable or disable Systemd watchdog."; 86 }; 87 }; 88 }; 89 default = { }; 90 description = '' 91 Configuration for Miniflux, refer to 92 <https://miniflux.app/docs/configuration.html> 93 for documentation on the supported values. 94 ''; 95 }; 96 97 adminCredentialsFile = mkOption { 98 type = types.nullOr types.path; 99 default = null; 100 description = '' 101 File containing the ADMIN_USERNAME and 102 ADMIN_PASSWORD (length >= 6) in the format of 103 an EnvironmentFile=, as described by {manpage}`systemd.exec(5)`. 104 ''; 105 example = "/etc/nixos/miniflux-admin-credentials"; 106 }; 107 }; 108 }; 109 110 config = mkIf cfg.enable { 111 assertions = [ 112 { 113 assertion = cfg.config.CREATE_ADMIN == 0 || cfg.adminCredentialsFile != null; 114 message = "services.miniflux.adminCredentialsFile must be set if services.miniflux.config.CREATE_ADMIN is 1"; 115 } 116 ]; 117 services.miniflux.config = { 118 DATABASE_URL = lib.mkIf cfg.createDatabaseLocally "user=miniflux host=/run/postgresql dbname=miniflux"; 119 }; 120 121 services.postgresql = lib.mkIf cfg.createDatabaseLocally { 122 enable = true; 123 ensureUsers = [ 124 { 125 name = "miniflux"; 126 ensureDBOwnership = true; 127 } 128 ]; 129 ensureDatabases = [ "miniflux" ]; 130 }; 131 132 systemd.services.miniflux-dbsetup = lib.mkIf cfg.createDatabaseLocally { 133 description = "Miniflux database setup"; 134 requires = [ "postgresql.target" ]; 135 after = [ 136 "network.target" 137 "postgresql.target" 138 ]; 139 serviceConfig = { 140 Type = "oneshot"; 141 User = config.services.postgresql.superUser; 142 ExecStart = preStart; 143 }; 144 }; 145 146 systemd.services.miniflux = { 147 description = "Miniflux service"; 148 wantedBy = [ "multi-user.target" ]; 149 requires = lib.optional cfg.createDatabaseLocally "miniflux-dbsetup.service"; 150 after = [ 151 "network.target" 152 ] 153 ++ lib.optionals cfg.createDatabaseLocally [ 154 "postgresql.target" 155 "miniflux-dbsetup.service" 156 ]; 157 158 serviceConfig = { 159 Type = "notify"; 160 ExecStart = lib.getExe cfg.package; 161 User = "miniflux"; 162 DynamicUser = true; 163 RuntimeDirectory = "miniflux"; 164 RuntimeDirectoryMode = "0750"; 165 EnvironmentFile = lib.mkIf (cfg.adminCredentialsFile != null) cfg.adminCredentialsFile; 166 WatchdogSec = 60; 167 WatchdogSignal = "SIGKILL"; 168 Restart = "always"; 169 RestartSec = 5; 170 171 # Hardening 172 CapabilityBoundingSet = [ "" ]; 173 DeviceAllow = [ "" ]; 174 LockPersonality = true; 175 MemoryDenyWriteExecute = true; 176 PrivateDevices = true; 177 PrivateUsers = true; 178 ProcSubset = "pid"; 179 ProtectClock = true; 180 ProtectControlGroups = true; 181 ProtectHome = true; 182 ProtectHostname = true; 183 ProtectKernelLogs = true; 184 ProtectKernelModules = true; 185 ProtectKernelTunables = true; 186 ProtectProc = "invisible"; 187 RestrictAddressFamilies = [ 188 "AF_INET" 189 "AF_INET6" 190 "AF_UNIX" 191 ]; 192 RestrictNamespaces = true; 193 RestrictRealtime = true; 194 RestrictSUIDSGID = true; 195 SystemCallArchitectures = "native"; 196 SystemCallFilter = [ 197 "@system-service" 198 "~@privileged" 199 ]; 200 UMask = "0077"; 201 }; 202 203 environment = lib.mapAttrs (_: toString) cfg.config; 204 }; 205 environment.systemPackages = [ cfg.package ]; 206 207 security.apparmor.policies."bin.miniflux".profile = '' 208 include <tunables/global> 209 ${cfg.package}/bin/miniflux { 210 include <abstractions/base> 211 include <abstractions/nameservice> 212 include <abstractions/ssl_certs> 213 include "${pkgs.apparmorRulesFromClosure { name = "miniflux"; } cfg.package}" 214 r ${cfg.package}/bin/miniflux, 215 r @{sys}/kernel/mm/transparent_hugepage/hpage_pmd_size, 216 rw /run/miniflux/**, 217 } 218 ''; 219 }; 220}