Element Server Suite on NixOS
  • Nix 97.8%
  • Shell 2.2%
Find a file
2026-07-28 10:31:35 +02:00
hosts/ess-helm rm user sid. add root ssh auth 2026-07-28 10:31:35 +02:00
modules/nixos add baibot 2026-07-28 10:31:13 +02:00
overlays initial ESS Community NixOS module 2026-05-10 22:54:27 +02:00
pkgs initial commit 2026-04-16 20:10:03 +02:00
.envrc add more pre-commit-hooks 2026-05-10 18:10:45 +02:00
.gitignore flake update. export FLAKE_PATH via direnv 2026-05-10 16:47:19 +02:00
.sops.yaml add baibot 2026-07-28 10:31:13 +02:00
flake.lock add dedicated postres service 2026-07-27 11:43:18 +02:00
flake.nix add dedicated postres service 2026-07-27 11:43:18 +02:00
README.md add baibot 2026-07-28 10:31:13 +02:00

ess-helm-nixos

NixOS module for Element Server Suite Community (ess-helm) on a single host: K3s + ingress-nginx inside, NixOS nginx as TLS-terminating reverse proxy in front.

Usage

{
  inputs.ess-helm.url = "github:sid/ess-helm";
  # ...
  modules = [ ess-helm.nixosModules.ess-helm ];
}
{
  services.ess-helm = {
    enable = true;
    serverName = "example.com";
    openFirewall = true;
  };

  security.acme.defaults.email = "admin@example.com";
}

This deploys Synapse at example.com, MAS at auth., Element Web at chat., Element Admin at admin., Matrix RTC at mrtc. - all fronted by nginx with Let's Encrypt certificates. Override a subdomain via e.g. services.ess-helm.elementWeb.subdomain = "element";.

Notable options (see modules/nixos/ess-helm/ for all):

services.ess-helm.turn.tls.enable = true;   # TURNS relay for restrictive firewalls
services.ess-helm.database.type = "nixos";  # dedicated host PostgreSQL instead of in-chart
services.ess-helm.ess.version = "26.X.Y";   # chart pin (+ hash) upgrades
services.ess-helm.bridges.whatsapp = {      # mautrix bridges
  enable = true;
  admin = "@admin:example.com";
};

With database.type = "nixos", set sops secrets ess-postgres.synapse and ess-postgres.mas. Bundled Postgres data is not migrated automatically.

Bots

Forgejo notifications

Forgejo can post repository/organisation events (push, PR, issues, ...) directly into a Matrix room via its built-in Matrix webhook - no bot deployment required, just a bot user + access token, provisioned automatically:

services.ess-helm.bots.forgejo.enable = true;

After deployment, retrieve the credentials:

kubectl -n ess-bots get secret forgejo-matrix-credentials \
  -o jsonpath='{.data.access-token}' | base64 -d

Then in Forgejo (repo or org Settings -> Webhooks -> Add webhook -> Matrix):

  • Homeserver URL: https://<serverName> (also stored as homeserver-url in the Secret)
  • Room: the room ID (e.g. !abc123:<serverName>)
  • Access token: the value retrieved above

Use "Test delivery" to verify. To re-provision the token: kubectl -n ess-bots delete secret forgejo-matrix-credentials && systemctl restart ess-bot-forgejo-provision.

baibot (AI bot)

baibot is a standalone AI (LLM) Matrix bot. The module provisions the bot user + access token via MAS, deploys baibot into the baibot namespace and keeps its SQLite/session data on a PVC.

Non-secret config

services.ess-helm.bots.baibot = {
  enable = true;
  admin = "@admin:example.com";
  config = {
    agents.static_definitions = [
      {
        id = "ollama";
        provider = "ollama";
        config = {
          base_url = "http://ollama.example.com:11434/v1";
          api_key = null;
          text_generation.model_id = "gemma2:2b";
        };
      }
    ];
    initial_global_config.handler.text_generation = "ollama";
  };
};

Secret config

To provide API keys and other optional secrets, provide a complete config.yml via sops instead:

Add a key ess-helm/baibot-config your secrets.yaml containing a full baibot config.yml. See upstream config.yml.dist at the pinned image tag.

services.ess-helm.bots.baibot = {
  enable = true;
  admin = "@admin:example.com";
  secretsFile = config.sops.secrets."ess-helm/baibot-config".path;
};

Usage

Invite @baibot:<serverName> to a room or open a DM with it. !bai help lists the available commands. After the first start, the bot's runtime configuration is managed via chat (!bai config ...) as a bot admin.

Troubleshooting

  • systemctl status ess-bot-baibot-provision: Matrix user + token provisioning. Re-provision with kubectl -n baibot delete secret baibot-matrix-credentials && systemctl restart ess-bot-baibot-provision.
  • kubectl -n baibot logs deploy/baibot: bot logs. The pod stays in CreateContainerConfigError until the credentials Secret exists; it starts automatically afterwards.

DNS

One A record per hostname pointing at the host: example.com, auth., chat., admin., mrtc. (and turn. when TURNS is enabled).

Firewall

Port Protocol Purpose
80/443 TCP HTTP (ACME) / HTTPS
30001 TCP Matrix RTC WebRTC
30002 UDP Matrix RTC WebRTC (muxed)
30003 TCP TURNS (when turn.tls.enable)
30004 UDP plain TURN (otherwise)