at 25.11-pre 9.3 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.services.gancio; 9 settingsFormat = pkgs.formats.json { }; 10 inherit (lib) 11 mkEnableOption 12 mkPackageOption 13 mkOption 14 types 15 literalExpression 16 mkIf 17 optional 18 mapAttrsToList 19 concatStringsSep 20 concatMapStringsSep 21 getExe 22 mkMerge 23 mkDefault 24 ; 25in 26{ 27 options.services.gancio = { 28 enable = mkEnableOption "Gancio, a shared agenda for local communities"; 29 30 package = mkPackageOption pkgs "gancio" { }; 31 32 plugins = mkOption { 33 type = with types; listOf package; 34 default = [ ]; 35 example = literalExpression "[ pkgs.gancioPlugins.telegram-bridge ]"; 36 description = '' 37 Paths of gancio plugins to activate (linked under $WorkingDirectory/plugins/). 38 ''; 39 }; 40 41 user = mkOption { 42 type = types.str; 43 description = "The user (and PostgreSQL database name) used to run the gancio server"; 44 default = "gancio"; 45 }; 46 47 settings = mkOption rec { 48 type = types.submodule { 49 freeformType = settingsFormat.type; 50 options = { 51 hostname = mkOption { 52 type = types.str; 53 description = "The domain name under which the server is reachable."; 54 }; 55 baseurl = mkOption { 56 type = types.str; 57 default = "http${ 58 lib.optionalString config.services.nginx.virtualHosts."${cfg.settings.hostname}".enableACME "s" 59 }://${cfg.settings.hostname}"; 60 defaultText = lib.literalExpression ''"https://''${config.services.gancio.settings.hostname}"''; 61 example = "https://demo.gancio.org/gancio"; 62 description = "The full URL under which the server is reachable."; 63 }; 64 server = { 65 socket = mkOption { 66 type = types.path; 67 readOnly = true; 68 default = "/run/gancio/socket"; 69 description = '' 70 The unix socket for the gancio server to listen on. 71 ''; 72 }; 73 }; 74 db = { 75 dialect = mkOption { 76 type = types.enum [ 77 "sqlite" 78 "postgres" 79 ]; 80 default = "sqlite"; 81 description = '' 82 The database dialect to use 83 ''; 84 }; 85 storage = mkOption { 86 description = '' 87 Location for the SQLite database. 88 ''; 89 readOnly = true; 90 type = types.nullOr types.str; 91 default = if cfg.settings.db.dialect == "sqlite" then "/var/lib/gancio/db.sqlite" else null; 92 defaultText = ''if config.services.gancio.settings.db.dialect == "sqlite" then "/var/lib/gancio/db.sqlite" else null''; 93 }; 94 host = mkOption { 95 description = '' 96 Connection string for the PostgreSQL database 97 ''; 98 readOnly = true; 99 type = types.nullOr types.str; 100 default = if cfg.settings.db.dialect == "postgres" then "/run/postgresql" else null; 101 defaultText = ''if config.services.gancio.settings.db.dialect == "postgres" then "/run/postgresql" else null''; 102 }; 103 database = mkOption { 104 description = '' 105 Name of the PostgreSQL database 106 ''; 107 readOnly = true; 108 type = types.nullOr types.str; 109 default = if cfg.settings.db.dialect == "postgres" then cfg.user else null; 110 defaultText = ''if config.services.gancio.settings.db.dialect == "postgres" then cfg.user else null''; 111 }; 112 }; 113 log_level = mkOption { 114 description = "Gancio log level."; 115 type = types.enum [ 116 "debug" 117 "info" 118 "warning" 119 "error" 120 ]; 121 default = "info"; 122 }; 123 # FIXME upstream proper journald logging 124 log_path = mkOption { 125 description = "Directory Gancio logs into"; 126 readOnly = true; 127 type = types.str; 128 default = "/var/log/gancio"; 129 }; 130 }; 131 }; 132 description = '' 133 Configuration for Gancio, see <https://gancio.org/install/config> for supported values. 134 ''; 135 }; 136 137 userLocale = mkOption { 138 type = with types; attrsOf (attrsOf (attrsOf str)); 139 default = { }; 140 example = { 141 en.register.description = "My new registration page description"; 142 }; 143 description = '' 144 Override default locales within gancio. 145 See [default languages and locales](https://framagit.org/les/gancio/tree/master/locales). 146 ''; 147 }; 148 149 nginx = mkOption { 150 type = types.submodule ( 151 lib.recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) { 152 # enable encryption by default, 153 # as sensitive login credentials should not be transmitted in clear text. 154 options.forceSSL.default = true; 155 options.enableACME.default = true; 156 } 157 ); 158 default = { }; 159 example = { 160 enableACME = false; 161 forceSSL = false; 162 }; 163 description = "Extra configuration for the nginx virtual host of gancio."; 164 }; 165 }; 166 167 config = mkIf cfg.enable { 168 environment.systemPackages = [ 169 (pkgs.runCommand "gancio" { } '' 170 mkdir -p $out/bin 171 echo '#!${pkgs.runtimeShell} 172 cd /var/lib/gancio/ 173 sudo=exec 174 if [[ "$USER" != ${cfg.user} ]]; then 175 sudo="exec /run/wrappers/bin/sudo -u ${cfg.user}" 176 fi 177 $sudo ${lib.getExe cfg.package} "''${@:--help}" 178 ' > $out/bin/gancio 179 chmod +x $out/bin/gancio 180 '') 181 ]; 182 183 users.users.gancio = lib.mkIf (cfg.user == "gancio") { 184 isSystemUser = true; 185 group = cfg.user; 186 home = "/var/lib/gancio"; 187 }; 188 users.groups.gancio = lib.mkIf (cfg.user == "gancio") { }; 189 190 systemd.tmpfiles.settings."10-gancio" = 191 let 192 rules = { 193 mode = "0755"; 194 user = cfg.user; 195 group = config.users.users.${cfg.user}.group; 196 }; 197 in 198 { 199 "/var/lib/gancio/user_locale".d = rules; 200 "/var/lib/gancio/plugins".d = rules; 201 }; 202 203 systemd.services.gancio = 204 let 205 configFile = settingsFormat.generate "gancio-config.json" cfg.settings; 206 in 207 { 208 description = "Gancio server"; 209 documentation = [ "https://gancio.org/" ]; 210 211 wantedBy = [ "multi-user.target" ]; 212 after = [ 213 "network.target" 214 ] ++ optional (cfg.settings.db.dialect == "postgres") "postgresql.service"; 215 216 environment = { 217 NODE_ENV = "production"; 218 }; 219 220 path = [ 221 # required for sendmail 222 "/run/wrappers" 223 ]; 224 225 preStart = '' 226 # We need this so the gancio executable run by the user finds the right settings. 227 ln -sf ${configFile} config.json 228 229 rm -f user_locale/* 230 ${concatStringsSep "\n" ( 231 mapAttrsToList ( 232 l: c: "ln -sf ${settingsFormat.generate "gancio-${l}-locale.json" c} user_locale/${l}.json" 233 ) cfg.userLocale 234 )} 235 236 rm -f plugins/* 237 ${concatMapStringsSep "\n" (p: "ln -sf ${p} plugins/") cfg.plugins} 238 ''; 239 240 serviceConfig = { 241 ExecStart = "${getExe cfg.package} start ${configFile}"; 242 # set umask so that nginx can write to the server socket 243 # FIXME: upstream socket permission configuration in Nuxt 244 UMask = "0002"; 245 RuntimeDirectory = "gancio"; 246 StateDirectory = "gancio"; 247 WorkingDirectory = "/var/lib/gancio"; 248 LogsDirectory = "gancio"; 249 User = cfg.user; 250 # hardening 251 RestrictRealtime = true; 252 RestrictNamespaces = true; 253 LockPersonality = true; 254 ProtectKernelModules = true; 255 ProtectKernelTunables = true; 256 ProtectKernelLogs = true; 257 ProtectControlGroups = true; 258 ProtectClock = true; 259 RestrictSUIDSGID = true; 260 SystemCallArchitectures = "native"; 261 CapabilityBoundingSet = ""; 262 ProtectProc = "invisible"; 263 }; 264 }; 265 266 services.postgresql = mkIf (cfg.settings.db.dialect == "postgres") { 267 enable = true; 268 ensureDatabases = [ cfg.user ]; 269 ensureUsers = [ 270 { 271 name = cfg.user; 272 ensureDBOwnership = true; 273 } 274 ]; 275 }; 276 277 services.nginx = { 278 enable = true; 279 virtualHosts."${cfg.settings.hostname}" = mkMerge [ 280 cfg.nginx 281 { 282 locations = { 283 "/" = { 284 index = "index.html"; 285 tryFiles = "$uri $uri @proxy"; 286 }; 287 "@proxy" = { 288 proxyWebsockets = true; 289 proxyPass = "http://unix:${cfg.settings.server.socket}"; 290 recommendedProxySettings = true; 291 }; 292 }; 293 } 294 ]; 295 }; 296 # for nginx to access gancio socket 297 users.users."${config.services.nginx.user}" = lib.mkIf (config.services.nginx.enable) { 298 extraGroups = [ config.users.users.${cfg.user}.group ]; 299 }; 300 }; 301}