ess-helm-nixos/modules/nixos/ess-helm/default.nix

301 lines
8.7 KiB
Nix
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
config,
lib,
...
}:
let
cfg = config.services.ess-helm;
inherit (lib)
literalExpression
mkDefault
mkEnableOption
mkIf
mkOption
recursiveUpdate
types
;
chartModule =
{
defaultRepo,
defaultName,
defaultVersion,
defaultHash,
...
}:
types.submodule {
options = {
repo = mkOption {
type = types.str;
default = defaultRepo;
description = "Helm chart repository URL. For OCI registries this must include the chart name (e.g. `oci://ghcr.io/org/repo/chart`).";
};
name = mkOption {
type = types.str;
default = defaultName;
description = "Chart name. For HTTP repositories appended to the pull command. For OCI repositories only used to name the store derivation.";
};
version = mkOption {
type = types.str;
default = defaultVersion;
description = "Chart version to deploy.";
example = "26.5.0";
};
hash = mkOption {
type = types.str;
default = defaultHash;
description = "SRI hash of the chart `.tgz` archive.";
example = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
};
extraValues = mkOption {
type = types.attrs;
default = { };
description = "Additional Helm values merged on top of the module-generated ones";
example = literalExpression ''
{
synapse.additional."user-config.yaml".config = "max_upload_size: 100M";
}
'';
};
};
};
serviceModule =
{ defaultSubdomain }:
types.submodule (
{ config, ... }:
{
options = {
subdomain = mkOption {
type = types.str;
default = defaultSubdomain;
description = "Subdomain for this service. Full hostname is `<subdomain>.<serverName>`, or just `<serverName>` when empty (Synapse).";
};
forceSSL = mkOption {
type = types.bool;
default = true;
description = "Redirect plain-HTTP requests to HTTPS.";
};
enableACME = mkOption {
type = types.bool;
description = "Obtain a Let's Encrypt certificate for this vhost. Defaults to the value of `forceSSL`.";
};
};
config.enableACME = mkDefault config.forceSSL;
}
);
hostFor = svc: if svc.subdomain == "" then cfg.serverName else "${svc.subdomain}.${cfg.serverName}";
vhostFor = svc: {
inherit (svc) forceSSL enableACME;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString cfg.ingressNginxPort}";
proxyWebsockets = true;
extraConfig = ''
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
client_max_body_size 50M;
'';
};
};
in
{
options.services.ess-helm = {
enable = mkEnableOption "Element Server Suite Community";
serverName = mkOption {
type = types.str;
description = "Matrix server name the domain part of every Matrix ID (`@user:<serverName>`). Must point at this host and **cannot be changed** after the first deployment without wiping the database.";
example = "example.com";
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = "Open the Matrix RTC WebRTC ports in the firewall (TCP 30001, UDP 30002).";
};
configureNginx = mkOption {
type = types.bool;
default = true;
description = "Configure nginx as a TLS-terminating reverse proxy for all ESS services, proxying to ingress-nginx on `ingressNginxPort`.";
};
ingressNginxPort = mkOption {
type = types.port;
default = 30080;
description = "NodePort for ingress-nginx plain HTTP. Must be in the Kubernetes NodePort range (3000032767).";
};
ingressNginxTlsPort = mkOption {
type = types.port;
default = 30443;
description = "NodePort reserved for ingress-nginx HTTPS (unused at runtime but required by the chart schema). Must be in the Kubernetes NodePort range (3000032767).";
};
ipFamily = mkOption {
type = types.enum [
"ipv4"
"ipv6"
"dual"
];
default = "ipv4";
description = "IP family for the ESS cluster networking.";
};
synapse = mkOption {
type = serviceModule { defaultSubdomain = ""; };
default = { };
description = "Synapse homeserver (served at the root serverName domain).";
};
matrixAuthenticationService = mkOption {
type = serviceModule { defaultSubdomain = "auth"; };
default = { };
description = "Matrix Authentication Service (MAS).";
};
elementWeb = mkOption {
type = serviceModule { defaultSubdomain = "chat"; };
default = { };
description = "Element Web client.";
};
elementAdmin = mkOption {
type = serviceModule { defaultSubdomain = "admin"; };
default = { };
description = "Element Admin console.";
};
matrixRTC = mkOption {
type = serviceModule { defaultSubdomain = "mrtc"; };
default = { };
description = "Matrix RTC SFU signalling endpoint.";
};
ess = mkOption {
type = chartModule {
name = "ess";
defaultRepo = "oci://ghcr.io/element-hq/ess-helm/matrix-stack";
defaultName = "matrix-stack";
defaultVersion = "26.5.0";
defaultHash = "sha256-2YfDk29c8MoEa7rJXnHiKgKVqL7I0mmIcbtqgh2tunU=";
};
default = { };
description = "ESS Community Helm chart pin and extra values.";
};
ingressNginx = mkOption {
type = chartModule {
name = "ingressNginx";
defaultRepo = "https://kubernetes.github.io/ingress-nginx";
defaultName = "ingress-nginx";
defaultVersion = "4.15.1";
defaultHash = "sha256-Pv8L0YFR1uaxxEFGNBBXFEPdoax4KSyxiTRmKN54Tww=";
};
default = { };
description = "ingress-nginx Helm chart pin and extra values.";
};
};
config = mkIf cfg.enable {
services.k3s = {
enable = true;
role = "server";
disable = [ "traefik" ]; # use ingress-nginx
autoDeployCharts = {
ingress-nginx = recursiveUpdate {
inherit (cfg.ingressNginx)
repo
name
version
hash
;
targetNamespace = "ingress-nginx";
createNamespace = true;
values = {
controller = {
ingressClassResource.default = true;
service = {
type = "NodePort";
nodePorts = {
http = cfg.ingressNginxPort;
https = cfg.ingressNginxTlsPort;
};
};
config = {
use-forwarded-headers = "true";
compute-full-forwarded-for = "true";
proxy-read-timeout = "86400";
proxy-send-timeout = "86400";
proxy-body-size = "50m";
};
};
};
} { values = cfg.ingressNginx.extraValues; };
ess = recursiveUpdate {
inherit (cfg.ess)
repo
name
version
hash
;
targetNamespace = "ess";
createNamespace = true;
values = {
inherit (cfg) serverName;
ingress = {
className = "nginx";
tlsEnabled = false;
};
synapse.ingress.host = hostFor cfg.synapse;
matrixAuthenticationService.ingress.host = hostFor cfg.matrixAuthenticationService;
elementWeb.ingress.host = hostFor cfg.elementWeb;
elementAdmin.ingress.host = hostFor cfg.elementAdmin;
matrixRTC.ingress.host = hostFor cfg.matrixRTC;
networking.ipFamily = cfg.ipFamily;
};
} { values = cfg.ess.extraValues; };
};
};
services.nginx = mkIf cfg.configureNginx {
enable = true;
virtualHosts = {
"${hostFor cfg.synapse}" = vhostFor cfg.synapse;
"${hostFor cfg.matrixAuthenticationService}" = vhostFor cfg.matrixAuthenticationService;
"${hostFor cfg.elementWeb}" = vhostFor cfg.elementWeb;
"${hostFor cfg.elementAdmin}" = vhostFor cfg.elementAdmin;
"${hostFor cfg.matrixRTC}" = vhostFor cfg.matrixRTC;
};
};
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ 30001 ];
allowedUDPPorts = [ 30002 ];
};
environment.variables.KUBECONFIG = "/etc/rancher/k3s/k3s.yaml";
};
}