at 23.05-pre 9.2 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 = with types; nullOr 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.str; 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 121 122 config = 123 let 124 systemd-hardening = { 125 CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; 126 DeviceAllow = ""; 127 LockPersonality = true; 128 NoNewPrivileges = true; 129 PrivateDevices = true; 130 PrivateTmp = true; 131 PrivateUsers = true; 132 ProcSubset = "pid"; 133 ProtectClock = true; 134 ProtectControlGroups = true; 135 ProtectHome = true; 136 ProtectHostname = true; 137 ProtectKernelLogs = true; 138 ProtectKernelModules = true; 139 ProtectKernelTunables = true; 140 ProtectProc = "invisible"; 141 ProtectSystem = "strict"; 142 RemoveIPC = true; 143 RestrictNamespaces = true; 144 RestrictRealtime = true; 145 RestrictSUIDSGID = true; 146 SystemCallArchitectures = "native"; 147 SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ]; 148 UMask = "0007"; 149 }; 150 in 151 mkIf cfg.enable { 152 # Set up a Nginx virtual host. 153 services.nginx = mkIf (cfg.virtualHost != null) { 154 enable = true; 155 virtualHosts.${cfg.virtualHost} = { 156 root = "${cfg.package}/p"; 157 158 # php files handling 159 # this regex is mandatory because of the API 160 locations."~ ^.+?\.php(/.*)?$".extraConfig = '' 161 fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket}; 162 fastcgi_split_path_info ^(.+\.php)(/.*)$; 163 # By default, the variable PATH_INFO is not set under PHP-FPM 164 # But FreshRSS API greader.php need it. If you have a Bad Request error, double check this var! 165 # NOTE: the separate $path_info variable is required. For more details, see: 166 # https://trac.nginx.org/nginx/ticket/321 167 set $path_info $fastcgi_path_info; 168 fastcgi_param PATH_INFO $path_info; 169 include ${pkgs.nginx}/conf/fastcgi_params; 170 include ${pkgs.nginx}/conf/fastcgi.conf; 171 ''; 172 173 locations."/" = { 174 tryFiles = "$uri $uri/ index.php"; 175 index = "index.php index.html index.htm"; 176 }; 177 }; 178 }; 179 180 # Set up phpfpm pool 181 services.phpfpm.pools = mkIf (cfg.pool == poolName) { 182 ${poolName} = { 183 user = "freshrss"; 184 settings = { 185 "listen.owner" = "nginx"; 186 "listen.group" = "nginx"; 187 "listen.mode" = "0600"; 188 "pm" = "dynamic"; 189 "pm.max_children" = 32; 190 "pm.max_requests" = 500; 191 "pm.start_servers" = 2; 192 "pm.min_spare_servers" = 2; 193 "pm.max_spare_servers" = 5; 194 "catch_workers_output" = true; 195 }; 196 phpEnv = { 197 FRESHRSS_DATA_PATH = "${cfg.dataDir}"; 198 }; 199 }; 200 }; 201 202 users.users.freshrss = { 203 description = "FreshRSS service user"; 204 isSystemUser = true; 205 group = "freshrss"; 206 }; 207 users.groups.freshrss = { }; 208 209 systemd.services.freshrss-config = 210 let 211 settingsFlags = concatStringsSep " \\\n " 212 (mapAttrsToList (k: v: "${k} ${toString v}") { 213 "--default_user" = ''"${cfg.defaultUser}"''; 214 "--auth_type" = ''"form"''; 215 "--base_url" = ''"${cfg.baseUrl}"''; 216 "--language" = ''"${cfg.language}"''; 217 "--db-type" = ''"${cfg.database.type}"''; 218 # The following attributes are optional depending on the type of 219 # database. Those that evaluate to null on the left hand side 220 # will be omitted. 221 ${if cfg.database.name != null then "--db-base" else null} = ''"${cfg.database.name}"''; 222 ${if cfg.database.passFile != null then "--db-password" else null} = ''"$(cat ${cfg.database.passFile})"''; 223 ${if cfg.database.user != null then "--db-user" else null} = ''"${cfg.database.user}"''; 224 ${if cfg.database.tableprefix != null then "--db-prefix" else null} = ''"${cfg.database.tableprefix}"''; 225 ${if cfg.database.host != null && cfg.database.port != null then "--db-host" else null} = ''"${cfg.database.host}:${toString cfg.database.port}"''; 226 }); 227 in 228 { 229 description = "Set up the state directory for FreshRSS before use"; 230 wantedBy = [ "multi-user.target" ]; 231 serviceConfig = { 232 Type = "oneshot"; 233 User = "freshrss"; 234 Group = "freshrss"; 235 StateDirectory = "freshrss"; 236 WorkingDirectory = cfg.package; 237 } // systemd-hardening; 238 environment = { 239 FRESHRSS_DATA_PATH = cfg.dataDir; 240 }; 241 242 script = '' 243 # create files with correct permissions 244 mkdir -m 755 -p ${cfg.dataDir} 245 246 # do installation or reconfigure 247 if test -f ${cfg.dataDir}/config.php; then 248 # reconfigure with settings 249 ./cli/reconfigure.php ${settingsFlags} 250 ./cli/update-user.php --user ${cfg.defaultUser} --password "$(cat ${cfg.passwordFile})" 251 else 252 # Copy the user data template directory 253 cp -r ./data ${cfg.dataDir} 254 255 # check correct folders in data folder 256 ./cli/prepare.php 257 # install with settings 258 ./cli/do-install.php ${settingsFlags} 259 ./cli/create-user.php --user ${cfg.defaultUser} --password "$(cat ${cfg.passwordFile})" 260 fi 261 ''; 262 }; 263 264 systemd.services.freshrss-updater = { 265 description = "FreshRSS feed updater"; 266 after = [ "freshrss-config.service" ]; 267 wantedBy = [ "multi-user.target" ]; 268 startAt = "*:0/5"; 269 environment = { 270 FRESHRSS_DATA_PATH = cfg.dataDir; 271 }; 272 serviceConfig = { 273 Type = "oneshot"; 274 User = "freshrss"; 275 Group = "freshrss"; 276 StateDirectory = "freshrss"; 277 WorkingDirectory = cfg.package; 278 ExecStart = "${cfg.package}/app/actualize_script.php"; 279 } // systemd-hardening; 280 }; 281 }; 282}