{ config, lib, pkgs, ... }: with lib; let relative-hostname = "forum.test"; strip-hash = filename: head (builtins.match "^[a-zA-Z0-9]+-(.+)$" filename); clean-utf8-file = filename: pkgs.stdenv.mkDerivation { name = "${strip-hash (baseNameOf filename)}.utf8"; phases = [ "installPhase" ]; installPhase = "iconv -c -f utf-8 -t utf-8 -o $out ${filename}"; }; in { options.fudo.services.selby-forum = with types; { enable = mkEnableOption "Enable Selby Forum on this host."; state-directory = mkOption { type = str; description = "Directory at which to store Selby Forum state."; }; legacy-forum-data = mkOption { type = path; description = "Path to legacy Vanilla forum data."; }; external-interface = mkOption { type = str; description = "Public-facing network interface on this host."; }; mail = { host = mkOption { type = str; description = "Mail server hostname."; }; }; }; config = (let hostname = config.instance.hostname; cfg = config.fudo.services.selby-forum; host-fqdn = let host-domain = config.fudo.hosts.${hostname}.domain; in "${hostname}.${host-domain}"; host-secrets = config.fudo.secrets.host-secrets.${hostname}; parent-ip = "192.168.92.1"; child-ip = "192.168.92.2"; mkPasswd = name: pkgs.lib.passwd.stablerandom-passwd-file "selby-forum-${name}" "selby-forum-${name}-${config.instance.build-seed}"; mail-user = "selby-forum"; mail-password = mkPasswd "mail-password"; database-name = "forum_selby_ca"; database-user = "forum_selby_ca"; runtime-path = "/run/selby/forum"; domain-name = "selby.ca"; zone-name = config.fudo.domains.${domain-name}.zone; forum-hostname = "${relative-hostname}.${domain-name}"; in { networking = mkIf cfg.enable { nat = { enable = true; internalInterfaces = [ "ve-selby-forum" ]; externalInterface = cfg.external-interface; }; }; services.nginx = mkIf cfg.enable { enable = true; recommendedOptimisation = true; recommendedProxySettings = true; virtualHosts.${forum-hostname} = { enableACME = true; forceSSL = true; locations."/".proxyPass = "http://${child-ip}:80"; }; }; fudo = { # Email user system-users.${mail-user} = { description = "Selby Forum"; ldap-hashed-password = pkgs.lib.passwd.hash-ldap-passwd "selby-forum-mail-passwd" mail-password; }; zones.${zone-name}.aliases = { ${relative-hostname} = "${host-fqdn}."; }; secrets.host-secrets.${hostname} = mkIf cfg.enable (let db-passwd = mkPasswd "database-password"; in { selby-forum-database-password = { source-file = db-passwd; target-file = "${runtime-path}/db.passwd"; }; postgres-selby-forum-password = { source-file = db-passwd; target-file = "/run/postgres-users/selby-forum.passwd"; user = config.services.postgresql.superUser; }; selby-forum-admin-passwd = { source-file = mkPasswd "admin-password"; target-file = "${runtime-path}/admin.passwd"; }; selby-forum-mail-passwd = { source-file = mail-password; target-file = "${runtime-path}/mail.passwd"; }; legacy-selby-forum-data = { source-file = "${clean-utf8-file cfg.legacy-forum-data}"; target-file = "${runtime-path}/selby-forum-data.sql"; }; }); postgresql = mkIf cfg.enable { local-networks = [ "192.168.92.0/30" ]; databases.${database-name}.users = config.instance.local-admins; users.${database-user} = { password-file = host-secrets.postgres-selby-forum-password.target-file; databases.${database-name} = { access = "CONNECT,CREATE"; entity-access = { "ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE"; "ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE"; }; }; }; }; }; systemd = mkIf cfg.enable { tmpfiles.rules = [ "d ${cfg.state-directory} 750 - - - -" ]; services.discourse-prepare-selby-forum = { description = "Perform superuser tasks for Discourse, which doesn't have SU perms."; wantedBy = [ "container@selby-forum.service" ]; before = [ "container@selby-forum.service" ]; requires = [ config.fudo.postgresql.systemd-target ]; after = [ config.fudo.postgresql.systemd-target ]; path = with pkgs; [ postgresql ]; serviceConfig = { User = config.services.postgresql.superUser; ExecStart = pkgs.writeShellScript "discourse-prepare-selby-forum.sh" '' psql -d ${database-name} -c "CREATE EXTENSION IF NOT EXISTS hstore;" psql -d ${database-name} -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" ''; }; }; }; containers.selby-forum = mkIf cfg.enable { ephemeral = true; privateNetwork = true; hostAddress = parent-ip; localAddress = child-ip; autoStart = true; bindMounts = { ${runtime-path} = { hostPath = runtime-path; isReadOnly = true; }; "/var/lib/discourse" = { hostPath = cfg.state-directory; isReadOnly = false; }; }; config = { config, lib, ... }: let discourse-user = config.systemd.services.discourse.serviceConfig.User; in { networking = { defaultGateway = parent-ip; firewall.enable = false; }; services.discourse = { enable = true; hostname = forum-hostname; enableACME = false; admin = { username = "admin"; fullName = "Admin"; email = "admin@selby.ca"; passwordFile = "/etc/selby-forum/admin.passwd"; }; database = { name = database-name; host = parent-ip; username = database-user; passwordFile = "/etc/selby-forum/db.passwd"; }; mail.outgoing = { username = mail-user; passwordFile = "/etc/selby-forum/mail.passwd"; domain = domain-name; forceTLS = true; serverAddress = cfg.mail.host; }; }; environment.etc = { "selby-forum/admin.passwd" = { source = "/run/selby/forum/admin.passwd"; user = discourse-user; mode = "0400"; }; "selby-forum/db.passwd" = { source = "/run/selby/forum/db.passwd"; user = discourse-user; mode = "0400"; }; "selby-forum/selby-forum-data.sql" = { source = "/run/selby/forum/selby-forum-data.sql"; user = discourse-user; mode = "0400"; }; "selby-forum/mail.passwd" = { source = "/run/selby/forum/mail.passwd"; user = discourse-user; mode = "0400"; }; }; systemd = { tmpfiles.rules = [ "d /var/lib/discourse 700 ${discourse-user} - - -" ]; services = { discourse = { after = [ "multi-user.target" ]; }; discourse-import-selby-forum = let env-without-path = filterAttrs (attr: _: attr != "PATH") config.systemd.services.discourse.environment; in { description = "One-off job to import Vanilla Selby Forum data."; path = config.systemd.services.discourse.path; environment = env-without-path; serviceConfig = { User = discourse-user; type = "oneshot"; WorkingDirectory = config.systemd.services.discourse.serviceConfig.WorkingDirectory; ExecStart = pkgs.writeShellScript "import-vanilla-selby-forum-data.sh" '' ruby script/import_scripts/vanilla.rb /etc/selby-forum/selby-forum-data.sql ''; }; }; }; }; }; }; }); }