[~] BACK



~~ HIY V ~~

** MATRIX SYNAPSE **

[PRO] | [20230816] | [20230817] | [0x17]




When the almighty CORTEX IMPLANT admin Ramses started the Matrix server "cyberwa.re" I was very happy. Unfortunately, CYBERWA.RE disappeared faster than I would have liked. After thinking about setting up a NixOS Matrix server myself, I can understand why :D.

0x01 - MATRIX / SYNAPSE


What is Matrix anyway? Matrix is ​​a decentralized chat service with the option to create rooms, uses E2EE and there are cool terminal clients for it (IAMB for example). There are currently three ways to run a Matrix server. Synapse, Dendrite or Conduit. [EDIT: Conduit can federate]. So that leaves Synapse and Dendrite. Dendrite is written in GO and according to various statements is supposed to perform significantly better than Synapse - however, with my current level of knowledge I am not able to get Dendrite to run under NixOS. That will come at a later date. So in the end, only Synapse remains.


0x02 - INFRASTRUCTUR


Of course, you need hardware behind Matrix. Matrix doesn't run on my main server, where you can find the other stuff like NERDBUDE, CCH! and so on. It runs on a separate VPS. My decision is made quite simply because I like to keep the different types of infrastructure separate. Matrix-Synapse is currently running on the smallest box that Hetzner offers. The Matrix instance is not meant to be public and is purely for me. So the config should be sufficient. Domains are also needed, of course. So all the stuff that is not meant to be on the main server will now run on the KYB3R.SPACE. Of course, Hetzner doesn't have NixOS as a default OS in its range yet, so that has to be ironed out by hand again. I have already described how this works here: HIY II - SPECIFICATION. So if you have everything: NixOS, domain and VPS you can get started.


0x03 - INSTALLATION


Of course, we don't just want to install locally, but use the wonderful potential of NixOS and build a configurations.nix with Matrix-Synapse.
It took me a while to get a functioning Matrix-Synapse instance (and it was nerve-wracking - but that's how learning works). So here's a quick look at the parts we need to set up a Matrix-Synapse server. What we need are the right DNS entries, SSL certificates, a functioning NGINX server, a suitable subdomain that Matrix listens to, the right firewall settings, a proxy for the subdomain and that's it basically. Before we start with the actual configuration, we create the right DNS entries.


0x04 - DNS


In order for the domain to find the IP and forward it accordingly, suitable DNS entries must be created. First come the A entries of the domain with IP connected:

DNS
	* A 0.0.0.0 3600
	www A 0.0.0.0 3600
	A 0.0.0.0 3600
	m A 0.0.0.0 3600
	

This sets the entries for subdomains www.kyb3r.space and for the Synapse sub m.kyb3r.space. But Synapse also needs an SRV entry:

DNS
	_matrix._tcp.kyb3r.space SRV 10 8448 m.kyb3r.space 5 3600
	

This means that we forward kyb3r.space to the subdomain m.kyb3r.space as soon as Matrix traffic comes in. You will of course have to replace the IP addresses (0.0.0.0) with your own. Once the DNS entries have been made, you can move on to the actual configuration.

0x05 - CONFIGURATION.NIX


What I have never really done, but which definitely makes sense, is to modularize the configuration for NixOS instead of maintaining everything in a configuration.nix. So I at least scraped Synapse out of the actual config and put it in /modules/synapse.nix. But let's start at the beginning, with the configuration.nix.
The configuration.nix contains the configurations for users, SSH, firewall, NGINX, certificates and of course the link to synapse.nix.

CONFIGURATION.NIX
{ config, pkgs, ... }: 
let 
   fqdn = "m.kyb3r.space";
   clientConfig."m.homeserver".base_url = "https://${fqdn}";
   serverConfig."m.server" = "${fqdn}:443";

in

{
  imports = [
    ./hardware-configuration.nix
    ./modules/synapse.nix
    
  ];

  boot.tmp.cleanOnBoot = true;
  zramSwap.enable = true;
  networking.hostName = "NB-ES-03";
  networking.domain = "";
  services.openssh.enable = true;
  users.users.root.openssh.authorizedKeys.keys = [
    ''ssh-rsa your-ssh-key'' 
  ];

In the first block, variables are defined for Synapse. The $fqdn is the subdomain that Synapse should use later and clientConfig ... and serverConfig ... are things that Synapse needs later. Then come the imports, here the well-known hardware.nix for the hardware stuff of the box on which the server runs and of course the synapse.nix which then takes care of the actual Synapse server. The last block configures the usual parameters for NixOS such as SSH key, host name, etc.
The next block is a bit more individual. This is about the NGINX that we need for hosting.

CONFIGURATION.NIX
services.nginx = {
enable = true;

    recommendedTlsSettings = true;
    recommendedOptimisation = true;
    recommendedGzipSettings = true;
    recommendedProxySettings = true;

    virtualHosts."kyb3r.space" = 
     let 
      client = 
        { "m.homeserver"      = { "base_url" = "https://m.kyb3r.space"; };
          "m.identity_server" = { "base_url" = "https://matrix.org"; };
        };
      server = { "m.server" = "m.kyb3r.space:443"; };
    in
   {
      enableACME = true;
      forceSSL = true;
      root = "/var/www/kyb3r";
      locations."/.well-known/matrix/server".extraConfig = ''
          add_header Content-Type application/json;
          return 200 '${builtins.toJSON server}';
      '';
      locations."/.well-known/matrix/client".extraConfig = ''
          add_header Content-Type application/json;
          add_header Access-Controll-Allow-Origin *;
          return 200 '${builtins.toJSON client}';
      '';
    };

    virtualHosts."www.kyb3r.space" = {
      enableACME = true;
      forceSSL = true;
      root = "/var/www/kyb3r/";
    };

    virtualHosts."m.kyb3r.space" = 
      let 
        client = 
          { "m.homeserver"      = { "base_url" = "https://m.kyb3r.space"; };
            "m.identity_server" = { "base_url" = "https://matrix.org"; };
          };
        server = { "m.server" = "m.kyb3r.space:443"; };
      in
      {
        enableACME = true;
        forceSSL = true;
        locations."/_matrix".proxyPass = "http://localhost:8448";
        locations."/_synapse".proxyPass = "https://localhost:8448";
      };		
       };

    security.acme.acceptTerms = true;
    security.acme.certs = {
    "kyb3r.space".email = "post@kyb3r.space";
    "www.kyb3r.space".email = "post@kyb3r.space";
    "m.kyb3r.space".email = "post@kyb3r.space";
    };

Here we start NGINX (I have already described exactly how the init works HERE). We let NGINX host three instances:

- kyb3r.space
- www.kyb3r.space
- m.kyb3r.space

The first two are intended as a pure web interface (I'm sure I'll build something there later) and the third is the interface to Synapse. The configuration of the first two is a standard configuration for a website. For the Synapse subdomain, the configuration looks a little different. Here we use locations. to give the subdomain paths that Synapse needs to work.
The last block that is left contains the normal SSL certificate Foo that is necessary to deliver the SSL certificates for the domains. What is still missing in the configuration.nix are the firewall settings:

CONFIGURATION.NIX
networking.firewall = {
enable = true;
allowedTCPPorts = [ 22 80 443 ];
allowedUDPPortRanges = [
  { from = 4000; to = 4007; }
  { from = 8000; to = 8010; }
];
};  

Here we open the necessary TCP and UDP ports. Port 22 for SSH, 80 for the web stuff and 443 for Synapse. The basic framework is now in place - the web server - and we can turn our attention to Synapse.

0x06 - SYNAPSE


Now comes the point that caused me several sleepless nights and brain-melting hours. In the end, however, as is usually the case with NixOS, the error was incredibly easy to identify.
So, on to Synapse. Synapse is included in the NixOS packages with the latest version, so no hacks are needed to get the whole thing running. The database is the first thing in synapse.nix, because without it nothing works. In most tutorials (and by Matrix itself) PostgresQL is recommended because it is much more performant than pure SQL or others. Then let's listen to Matrix and use PostgresQL:

SYNAPSE.NIX
    { pkgs, lib, config, ... }:
    {
      services.postgresql.enable = true;
      services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" ''
        CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'password';
        CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
          TEMPLATE template0
          LC_COLLATE = "C"
          LC_CTYPE = "C";
      '';

With services.postgresql.enable = true; we activate the PostgresQL package and with services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" we give the package the commands we need to create a database and a user. CREATE ROLE creates the user and CREATE DATABASE creates the actual database and assigns the owner with OWNER. So far, it's still quite simple. But just simple is boring, so now comes the SYNAPSE part of the config.

SYNAPSE.NIX
services.matrix-synapse = {
  enable = true;
  settings.server_name = "m.kyb3r.space";
  settings.enable_metrics = true;
  settings.registration_shared_secret_path = "/path/to/secret";
  settings.database.name = "psycopg2";
  settings.database.args = {
      user = "matrix-synapse";
      password = "password";
  };
  settings.listeners = [
    {
      bind_addresses = [ "localhost" ];
port = 8448;
tls = false;
     resources = [
        { compress = true; names = ["client" "federation"]; }
  { compress = false; names = [ "federation" ]; }
      ];
type = "http";
x_forwarded = false;
    }
    {
bind_addresses = [ "127.0.0.1" ];
port = 8008;
resources = [ { compress = true; names = [ "client" "federation" ]; }
];
tls = false;
type = "http";
x_forwarded = true;
    }

  ];
};
}

This bit of configuration cost me time. Most of the tutorials that can be found on the Internet are now out-dated from 2020 or even older. A lot has happened in NixOS since then, and the conventions for providing Synapse configurations have also changed fundamentally. At least with Synapse. The advantage is obvious. Before the change, the registration_shared_secret was defined in plain text (!!!) in the config. Nobody really wants that. For me, I was able to find two variants (yes - there are other ways to deploy keys) for making the key available. Firstly, services.matrix-synapse supports the parameter settings.registration_shared_secret_path = "/path/to/secret";. This allows the key to be written into a separate file and Synapse is pointed to the key file.
The file is quite simple to create:

TERMINAL
  nix-shell -p pwgen
  pwgen -s 64 1 > /path/to/file/matrix-shared-secret
  

This way you don't have pwgen hanging around in the system. Ok - what exactly does the config do now?
We start the service services.matrix-synapse using enable = true; and then define further parameters. Starting with settings.server_name = "m.kyb3r.space"; which defines our server name and settings.enable_metrics = true; records metrics that can be very helpful when debugging. The feature can be deactivated later - we want to be data-efficient ;)
As described above, settings.registration_shared_secret_path points to the path to the shared_secret.

Next, we establish the connection to the database. The parameter settings.database.name = "psycopg2"; tells Synapse that a PostgresQL is running in the background. With settings.database.args = { user = "matrix-synapse"; password = "password"; }; we also enter the login credentials. This should establish the connection to the database.
The rest of the config creates listeners that bind ports 8448 and 8008 to the localhosts and thus take care of the connection to the client and the federation.
That's it. If you configure everything like this, you can get the whole thing running by rebuilding:

TERMINAL
	nixos-rebuild switch
	

If everything worked, you should now have a functional Synapse home server in front of you. If you now grab a Matrix client of your choice and want to create an account on your instance, you will notice that the server we have configured here is not intended for everyone to be able to create an account. There is the option to activate public registration, but I personally don't want that on my server. But you can easily register a user via the terminal.

0x07 - USER REGISTRIEREN


Having a running Synapse server brings a certain level of satisfaction, but of course the whole thing only really makes sense if we have an account there. Since public registration is deactivated, we do the whole thing via terminal. Synapse comes with the tool "register_new_matrix_user" for this purpose. There are two ways to use this tool. No. #1 you look for the right entry under /nix/store/ or - and this is much quicker and less complicated - you operate the whole thing via nix-shell -p matrix-synapse:

NIX-SHELL -P MATRIX-SYNAPSE
	register_new_matrix_user -c /PFAD/ZUR/CONFIG/ http://localhost:8008
	

Das registriert einen neuen User (inkl. Passwort und Adminrechten bei Bedarf) auf eurem Synapse-Server. Das wichtige hier ist das der /PFAD/ZUR/CONFIG/ natürlich nicht der Pfad zu eurer NixOS Configuration, sondern den Pfad zur Synapse-Configfile. An den Pfad zur Config kommt ihr ran, wenn ihr euch matrix-synapse.service anschaut. Darin ist der komplette Pfad definiert.

If the registration runs without errors - we are done and can use our own Matrix Synapse home server

0x08 - FAZIT


All in all, configuring Matrix-Synapse is relatively quick and doesn't require much effort. Unfortunately, the tutorials online are all quite outdated. Only the entry in the NixOS Manual is up-to-date and helpful. With my config, you have everything you need to host your own server and run it privately. If I ever decide to make the registration public, I will of course keep you updated, otherwise you can get the config parameters from the internet.

0x09 - THANK YOU


As I have already mentioned several times, it was a bit of work to get the whole thing up and running. I didn't manage it all by myself (even though I would have liked to). Various nice people from the internet gave me valuable tips that made it possible to build my own Synapse server. So here's a huge thank you to:

- 0x4AxF (Federation fails)
- pinpox (Config Samples and hint to Dendrite)
- fleaz (for the hint that LetsEncrypt has a timeout when issuing certs (5 attempts / hour))
- spohie (for the suggestion to use "workers" in conjunction with Synapse, I will also look into that soon)

Thank you so much :)

EDIT
[20230817] - Conduit can federation [fleaz]

[~] BACK