at 23.11-pre 9.4 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4let 5 cfg = config.services.freshrss; 6 7 poolName = "freshrss"; 8in 9{ 10 meta.maintainers = with maintainers; [ etu stunkymonkey ]; 11 12 options.services.freshrss = { 13 enable = mkEnableOption (mdDoc "FreshRSS feed reader"); 14 15 package = mkOption { 16 type = types.package; 17 default = pkgs.freshrss; 18 defaultText = lib.literalExpression "pkgs.freshrss"; 19 description = mdDoc "Which FreshRSS package to use."; 20 }; 21 22 defaultUser = mkOption { 23 type = types.str; 24 default = "admin"; 25 description = mdDoc "Default username for FreshRSS."; 26 example = "eva"; 27 }; 28 29 passwordFile = mkOption { 30 type = types.path; 31 description = mdDoc "Password for the defaultUser for FreshRSS."; 32 example = "/run/secrets/freshrss"; 33 }; 34 35 baseUrl = mkOption { 36 type = types.str; 37 description = mdDoc "Default URL for FreshRSS."; 38 example = "https://freshrss.example.com"; 39 }; 40 41 language = mkOption { 42 type = types.str; 43 default = "en"; 44 description = mdDoc "Default language for FreshRSS."; 45 example = "de"; 46 }; 47 48 database = { 49 type = mkOption { 50 type = types.enum [ "sqlite" "pgsql" "mysql" ]; 51 default = "sqlite"; 52 description = mdDoc "Database type."; 53 example = "pgsql"; 54 }; 55 56 host = mkOption { 57 type = types.nullOr types.str; 58 default = "localhost"; 59 description = mdDoc "Database host for FreshRSS."; 60 }; 61 62 port = mkOption { 63 type = types.nullOr types.port; 64 default = null; 65 description = mdDoc "Database port for FreshRSS."; 66 example = 3306; 67 }; 68 69 user = mkOption { 70 type = types.nullOr types.str; 71 default = "freshrss"; 72 description = mdDoc "Database user for FreshRSS."; 73 }; 74 75 passFile = mkOption { 76 type = types.nullOr types.path; 77 default = null; 78 description = mdDoc "Database password file for FreshRSS."; 79 example = "/run/secrets/freshrss"; 80 }; 81 82 name = mkOption { 83 type = types.nullOr types.str; 84 default = "freshrss"; 85 description = mdDoc "Database name for FreshRSS."; 86 }; 87 88 tableprefix = mkOption { 89 type = types.nullOr types.str; 90 default = null; 91 description = mdDoc "Database table prefix for FreshRSS."; 92 example = "freshrss"; 93 }; 94 }; 95 96 dataDir = mkOption { 97 type = types.str; 98 default = "/var/lib/freshrss"; 99 description = mdDoc "Default data folder for FreshRSS."; 100 example = "/mnt/freshrss"; 101 }; 102 103 virtualHost = mkOption { 104 type = types.nullOr types.str; 105 default = "freshrss"; 106 description = mdDoc '' 107 Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost. 108 ''; 109 }; 110 111 pool = mkOption { 112 type = types.str; 113 default = poolName; 114 description = mdDoc '' 115 Name of the phpfpm pool to use and setup. If not specified, a pool will be created 116 with default values. 117 ''; 118 }; 119 120 user = mkOption { 121 type = types.str; 122 default = "freshrss"; 123 description = lib.mdDoc "User under which Freshrss runs."; 124 }; 125 }; 126 127 config = 128 let 129 defaultServiceConfig = { 130 ReadWritePaths = "${cfg.dataDir}"; 131 CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; 132 DeviceAllow = ""; 133 LockPersonality = true; 134 NoNewPrivileges = true; 135 PrivateDevices = true; 136 PrivateTmp = true; 137 PrivateUsers = true; 138 ProcSubset = "pid"; 139 ProtectClock = true; 140 ProtectControlGroups = true; 141 ProtectHome = true; 142 ProtectHostname = true; 143 ProtectKernelLogs = true; 144 ProtectKernelModules = true; 145 ProtectKernelTunables = true; 146 ProtectProc = "invisible"; 147 ProtectSystem = "strict"; 148 RemoveIPC = true; 149 RestrictNamespaces = true; 150 RestrictRealtime = true; 151 RestrictSUIDSGID = true; 152 SystemCallArchitectures = "native"; 153 SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ]; 154 UMask = "0007"; 155 Type = "oneshot"; 156 User = cfg.user; 157 Group = config.users.users.${cfg.user}.group; 158 StateDirectory = "freshrss"; 159 WorkingDirectory = cfg.package; 160 }; 161 in 162 mkIf cfg.enable { 163 # Set up a Nginx virtual host. 164 services.nginx = mkIf (cfg.virtualHost != null) { 165 enable = true; 166 virtualHosts.${cfg.virtualHost} = { 167 root = "${cfg.package}/p"; 168 169 # php files handling 170 # this regex is mandatory because of the API 171 locations."~ ^.+?\.php(/.*)?$".extraConfig = '' 172 fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket}; 173 fastcgi_split_path_info ^(.+\.php)(/.*)$; 174 # By default, the variable PATH_INFO is not set under PHP-FPM 175 # But FreshRSS API greader.php need it. If you have a Bad Request error, double check this var! 176 # NOTE: the separate $path_info variable is required. For more details, see: 177 # https://trac.nginx.org/nginx/ticket/321 178 set $path_info $fastcgi_path_info; 179 fastcgi_param PATH_INFO $path_info; 180 include ${pkgs.nginx}/conf/fastcgi_params; 181 include ${pkgs.nginx}/conf/fastcgi.conf; 182 ''; 183 184 locations."/" = { 185 tryFiles = "$uri $uri/ index.php"; 186 index = "index.php index.html index.htm"; 187 }; 188 }; 189 }; 190 191 # Set up phpfpm pool 192 services.phpfpm.pools = mkIf (cfg.pool == poolName) { 193 ${poolName} = { 194 user = "freshrss"; 195 settings = { 196 "listen.owner" = "nginx"; 197 "listen.group" = "nginx"; 198 "listen.mode" = "0600"; 199 "pm" = "dynamic"; 200 "pm.max_children" = 32; 201 "pm.max_requests" = 500; 202 "pm.start_servers" = 2; 203 "pm.min_spare_servers" = 2; 204 "pm.max_spare_servers" = 5; 205 "catch_workers_output" = true; 206 }; 207 phpEnv = { 208 FRESHRSS_DATA_PATH = "${cfg.dataDir}"; 209 }; 210 }; 211 }; 212 213 users.users."${cfg.user}" = { 214 description = "FreshRSS service user"; 215 isSystemUser = true; 216 group = "${cfg.user}"; 217 home = cfg.dataDir; 218 }; 219 users.groups."${cfg.user}" = { }; 220 221 systemd.tmpfiles.rules = [ 222 "d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -" 223 ]; 224 225 systemd.services.freshrss-config = 226 let 227 settingsFlags = concatStringsSep " \\\n " 228 (mapAttrsToList (k: v: "${k} ${toString v}") { 229 "--default_user" = ''"${cfg.defaultUser}"''; 230 "--auth_type" = ''"form"''; 231 "--base_url" = ''"${cfg.baseUrl}"''; 232 "--language" = ''"${cfg.language}"''; 233 "--db-type" = ''"${cfg.database.type}"''; 234 # The following attributes are optional depending on the type of 235 # database. Those that evaluate to null on the left hand side 236 # will be omitted. 237 ${if cfg.database.name != null then "--db-base" else null} = ''"${cfg.database.name}"''; 238 ${if cfg.database.passFile != null then "--db-password" else null} = ''"$(cat ${cfg.database.passFile})"''; 239 ${if cfg.database.user != null then "--db-user" else null} = ''"${cfg.database.user}"''; 240 ${if cfg.database.tableprefix != null then "--db-prefix" else null} = ''"${cfg.database.tableprefix}"''; 241 ${if cfg.database.host != null && cfg.database.port != null then "--db-host" else null} = ''"${cfg.database.host}:${toString cfg.database.port}"''; 242 }); 243 in 244 { 245 description = "Set up the state directory for FreshRSS before use"; 246 wantedBy = [ "multi-user.target" ]; 247 serviceConfig = defaultServiceConfig //{ 248 Type = "oneshot"; 249 User = "freshrss"; 250 Group = "freshrss"; 251 StateDirectory = "freshrss"; 252 WorkingDirectory = cfg.package; 253 }; 254 environment = { 255 FRESHRSS_DATA_PATH = cfg.dataDir; 256 }; 257 258 script = '' 259 # do installation or reconfigure 260 if test -f ${cfg.dataDir}/config.php; then 261 # reconfigure with settings 262 ./cli/reconfigure.php ${settingsFlags} 263 ./cli/update-user.php --user ${cfg.defaultUser} --password "$(cat ${cfg.passwordFile})" 264 else 265 # check correct folders in data folder 266 ./cli/prepare.php 267 # install with settings 268 ./cli/do-install.php ${settingsFlags} 269 ./cli/create-user.php --user ${cfg.defaultUser} --password "$(cat ${cfg.passwordFile})" 270 fi 271 ''; 272 }; 273 274 systemd.services.freshrss-updater = { 275 description = "FreshRSS feed updater"; 276 after = [ "freshrss-config.service" ]; 277 wantedBy = [ "multi-user.target" ]; 278 startAt = "*:0/5"; 279 environment = { 280 FRESHRSS_DATA_PATH = cfg.dataDir; 281 }; 282 serviceConfig = defaultServiceConfig //{ 283 ExecStart = "${cfg.package}/app/actualize_script.php"; 284 }; 285 }; 286 }; 287}