{ config, lib, pkgs, ... }: with lib; let cfg = config.fudo.webmail; inherit (lib.strings) concatStringsSep; webmail-user = "webmail-php"; webmail-group = "webmail-php"; base-data-path = "/var/rainloop"; fastcgi-conf = builtins.toFile "fastcgi.conf" '' fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param REQUEST_SCHEME $scheme; fastcgi_param HTTPS $https if_not_empty; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; # PHP only, required if PHP was built with --enable-force-cgi-redirect fastcgi_param REDIRECT_STATUS 200; ''; site-packages = mapAttrs (site: site-cfg: pkgs.rainloop-community.overrideAttrs (oldAttrs: { # Not sure how to correctly specify this arg... #dataPath = "${base-data-path}/${site}"; # Overwriting, to correctly create data dir installPhase = '' mkdir $out cp -r rainloop/* $out rm -rf $out/data ln -s ${base-data-path}/${site} $out/data ln -s ${site-cfg.favicon} $out/favicon.ico ''; })) cfg.sites; siteOpts = { site-host, ... }: { options = { title = mkOption { type = types.str; description = "Webmail site title"; example = "My Webmail"; }; debug = mkOption { type = types.bool; description = "Turn debug logs on."; default = false; }; mail-server = mkOption { type = types.str; description = "Mail server from which to send & recieve email."; default = "mail.fudo.org"; }; favicon = mkOption { type = types.str; description = "URL of the site favicon"; example = "https://www.somepage.com/fav.ico"; }; messages-per-page = mkOption { type = types.int; description = "Default number of messages to show per page"; default = 30; }; max-upload-size = mkOption { type = types.int; description = "Size limit in MB for uploaded files"; default = 30; }; theme = mkOption { type = types.str; description = "Default theme to use for this webmail site."; default = "Default"; }; # Ideally, don't even allow admin logins, since they'll just add state that can be clobbered # admin-password = mkOption { # type = types.str; # description = "Password to use for the admin user"; # }; domain = mkOption { type = types.str; description = "Domain for which the server acts as webmail server"; }; edit-mode = mkOption { type = types.enum [ "Plain" "Html" "PlainForced" "HtmlForced" ]; description = "Default text editing mode for email"; default = "Html"; }; layout-mode = mkOption { type = types.enum [ "side" "bottom" ]; description = "Layout mode to use for email preview."; default = "side"; }; enable-threading = mkOption { type = types.bool; description = "Whether to enable threading for email."; default = true; }; enable-mobile = mkOption { type = types.bool; description = "Whether to enable a mobile site view."; default = true; }; database = mkOption { type = with types; nullOr (submodule databaseOpts); description = "Database configuration for storing contact data."; example = { name = "my_db"; host = "db.domain.com"; user = "my_user"; password-file = /path/to/some/file.pw; }; default = null; }; admin-email = mkOption { type = types.str; description = "Email of administrator of this site."; default = "admin@fudo.org"; }; }; }; databaseOpts = { ... }: { options = { type = mkOption { type = types.enum [ "pgsql" "mysql" ]; description = "Driver to use when connecting to the database."; default = "pgsql"; }; hostname = mkOption { type = types.str; description = "Name of host running the database."; example = "my-db.domain.com"; }; port = mkOption { type = types.int; description = "Port on which the database server is listening."; default = 5432; }; name = mkOption { type = types.str; description = "Name of the database containing contact info. must have access."; default = "rainloop_contacts"; }; user = mkOption { type = types.str; description = "User as which to connect to the database."; }; password-file = mkOption { type = types.str; description = "Password to use when connecting to the database."; }; }; }; in { options.fudo.webmail = { enable = mkEnableOption "Enable a RainLoop webmail server."; sites = mkOption { type = with types; (attrsOf (submodule siteOpts)); description = "A map of webmail sites to site configurations."; example = { "webmail.domain.com" = { title = "My Awesome Webmail"; layout-mode = "side"; favicon = "/path/to/favicon.ico"; admin-password = "shh-don't-tell"; }; }; }; }; config = mkIf cfg.enable { users = { users = { ${webmail-user} = { isSystemUser = true; description = "Webmail PHP FPM user"; group = webmail-group; }; }; groups = { ${webmail-group} = { members = [ webmail-user config.services.nginx.user ]; }; }; }; security.acme.certs = mapAttrs' (site: site-cfg: nameValuePair site { email = site-cfg.admin-email; }) cfg.sites; services = { phpfpm = { pools.webmail = { settings = { "pm" = "dynamic"; "pm.max_children" = 50; "pm.start_servers" = 5; "pm.min_spare_servers" = 1; "pm.max_spare_servers" = 8; }; phpOptions = '' memory_limit = 500M ''; # Not working....see chmod below user = webmail-user; group = webmail-group; }; }; nginx = { enable = true; virtualHosts = mapAttrs (site: site-cfg: { enableACME = true; forceSSL = true; root = "${site-packages.${site}}"; locations = { "/" = { index = "index.php"; }; "/data" = { extraConfig = '' deny all; return 403; ''; }; }; extraConfig = '' location ~ \.php$ { expires -1; include ${fastcgi-conf}; fastcgi_index index.php; fastcgi_pass unix:${config.services.phpfpm.pools.webmail.socket}; } ''; }) cfg.sites; }; }; systemd.services = { webmail-init = let link-configs = concatStringsSep "\n" (mapAttrsToList (site: site-cfg: let cfg-file = builtins.toFile "${site}-rainloop.cfg" (import ./include/rainloop.nix lib site site-cfg site-packages.${site}.version); domain-cfg = builtins.toFile "${site}-domain.cfg" '' imap_host = "${site-cfg.mail-server}" imap_port = 143 imap_secure = "TLS" imap_short_login = On sieve_use = Off sieve_allow_raw = Off sieve_host = "" sieve_port = 4190 sieve_secure = "None" smtp_host = "${site-cfg.mail-server}" smtp_port = 587 smtp_secure = "TLS" smtp_short_login = On smtp_auth = On smtp_php_mail = Off white_list = "" ''; in '' ${pkgs.coreutils}/bin/mkdir -p ${base-data-path}/${site}/_data_/_default_/configs ${pkgs.coreutils}/bin/cp ${cfg-file} ${base-data-path}/${site}/_data_/_default_/configs/application.ini ${pkgs.coreutils}/bin/mkdir -p ${base-data-path}/${site}/_data_/_default_/domains/ ${pkgs.coreutils}/bin/cp ${domain-cfg} ${base-data-path}/${site}/_data_/_default_/domains/${site-cfg.domain}.ini '') cfg.sites); scriptPkg = (pkgs.writeScriptBin "webmail-init.sh" '' #!${pkgs.bash}/bin/bash -e ${link-configs} ${pkgs.coreutils}/bin/chown -R ${webmail-user}:${webmail-group} ${base-data-path} ${pkgs.coreutils}/bin/chmod -R ug+w ${base-data-path} ''); in { requiredBy = [ "nginx.service" ]; description = "Initialize webmail service directories prior to starting nginx."; script = "${scriptPkg}/bin/webmail-init.sh"; }; phpfpm-webmail-socket-perm = { wantedBy = [ "multi-user.target" ]; description = "Change ownership of the phpfpm socket for webmail once it's started."; requires = [ "phpfpm-webmail.service" ]; after = [ "phpfpm.target" ]; serviceConfig = { ExecStart = '' ${pkgs.coreutils}/bin/chown ${webmail-user}:${webmail-group} ${config.services.phpfpm.pools.webmail.socket} ''; }; }; nginx = { requires = [ "webmail-init.service" "phpfpm-webmail-socket-perm.service" ]; }; }; }; }