301 lines
8.7 KiB
Nix
301 lines
8.7 KiB
Nix
{
|
||
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 (30000–32767).";
|
||
};
|
||
|
||
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 (30000–32767).";
|
||
};
|
||
|
||
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";
|
||
};
|
||
}
|