{ config, lib, pkgs, ... }: with lib; let site = "forum.test.selby.ca"; hostname = config.instance.hostname; host-secrets = config.fudo.secrets.host-secrets.${hostname}; discourse-user = config.systemd.services.discourse.serviceConfig.User; database-name = "forum_selby_ca"; database-user = "forum_selby_ca"; state-directory = "/state/selby/forum"; password-injector-sql = csv-file: pkgs.stdenv.mkDerivation { name = "${site}-password-injector-sql"; phases = [ "installPhase" ]; buildInputs = [ pkgs.ruby ]; installPhase = '' ${password-convert-script csv-file} ''; }; password-convert-script = csv-file: pkgs.writeScript "vanilla-forum-password-convert.rb" '' #!${pkgs.ruby}/bin/ruby require 'csv' data = CSV::readlines("${csv-file}") File::open(ENV["out"], "w") { |sql| data.each { |row| sql.puts("UPDATE users SET import_pass='#{row[2]}' FROM user_emails WHERE users.id = user_emails.user_id AND user_emails.email = '#{row[1]}';") } } ''; in { config = { services.discourse = { enable = true; hostname = site; enableACME = true; plugins = with config.services.discourse.package.plugins; [ discourse-migratepassword ]; admin = { username = "admin"; fullName = "Admin"; email = "admin@selby.ca"; passwordFile = host-secrets.selby-discourse-admin.target-file; }; database = { name = database-name; host = "localhost"; username = database-user; passwordFile = host-secrets.selby-discourse-database-passwd.target-file; }; }; fudo = { secrets.host-secrets.${hostname} = let selby-discourse-db-password = pkgs.lib.passwd.stablerandom-passwd-file "selby-discourse-database-password" "selby-discourse-database-password-${config.instance.build-seed}"; files = config.fudo.secrets.files; in { selby-discourse-database-passwd = { source-file = selby-discourse-db-password; target-file = "/run/selby/forum/database.passwd"; user = discourse-user; }; postgresql-selby-discourse-password = { source-file = selby-discourse-db-password; target-file = "/run/postgres/selby-discourse.passwd"; user = config.services.postgresql.superUser; }; selby-discourse-admin = { source-file = pkgs.lib.passwd.stablerandom-passwd-file "selby-discourse-admin" "selby-discourse-admin-${config.instance.build-seed}"; target-file = "/run/selby/forum/admin.passwd"; user = discourse-user; }; selby-forum-data = { source-file = files.blobs."selby-forum-2021-12-14.clean"; target-file = "/run/selby/forum/forum-data.txt"; user = discourse-user; }; selby-forum-passwords-sql = { source-file = "${password-injector-sql files.blobs."forum_selby_ca-passwd.csv"}"; target-file = "/run/postgres/selby/forum-passwords.sql"; user = config.services.postgresql.superUser; }; }; postgresql = { databases.${database-name}.users = [ "niten" ]; users.${database-user} = { password-file = host-secrets.postgresql-selby-discourse-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"; }; }; }; }; }; security.acme.certs.${site}.email = "admin@selby.ca"; systemd = { tmpfiles.rules = [ "d ${state-directory} 750 ${discourse-user} - - -" "L /var/lib/discourse - - - - ${state-directory}" ]; services = { discourse = { bindsTo = [ "postgresql.service" ]; after = [ config.fudo.postgresql.systemd-target "postgresql.service" ]; }; discourse-prepare = { description = "Do discourse's superuser-requiring database work for it."; wantedBy = [ "discourse.service" ]; before = [ "discourse.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.sh" '' psql -d ${database-name} -c "CREATE EXTENSION IF NOT EXISTS hstore;" psql -d ${database-name} -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" ''; }; }; discourse-import-vanilla = let env-without-path = filterAttrs (attr: _: attr != "PATH") config.systemd.services.discourse.environment; selby-forum-data = host-secrets.selby-forum-data.target-file; in { description = "One-off job to import Vanilla forum."; path = config.systemd.services.discourse.path; environment = env-without-path; serviceConfig = { User = config.systemd.services.discourse.serviceConfig.User; Group = config.systemd.services.discourse.serviceConfig.Group; Type = "oneshot"; WorkingDirectory = config.systemd.services.discourse.serviceConfig.WorkingDirectory; ExecStart = pkgs.writeShellScript "import-vanilla-forum.sh" '' ruby script/import_scripts/vanilla.rb ${selby-forum-data} ''; }; }; discourse-add-password-hash = let alter-user-script = pkgs.writeText "create-password-column.sql" '' ALTER TABLE users ADD COLUMN IF NOT EXISTS import_pass VARCHAR (64); ''; in { description = "One-off job to add user password hashes from Vanilla forum."; path = with pkgs; [ postgresql ]; wantedBy = [ "discourse.service" ]; serviceConfig = { User = config.services.postgresql.superUser; Type = "oneshot"; ExecStart = pkgs.writeShellScript "import-vanilla-passwords.sh" '' psql -d ${database-name} -f ${alter-user-script} psql -d ${database-name} -f ${host-secrets.selby-forum-passwords-sql.target-file} ''; }; }; }; }; }; }