nixos-config/config/service/selby-forum.nix

278 lines
8.4 KiB
Nix

{ 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
'';
};
};
};
};
};
};
});
}