Declarative, Decentralised, and Secure communication via Matrix, Jitsi, & NixOS

It has been one of my strong pursuit to figure out a properly encrypted and decentralised communication setup. Matrix’s claim to provide that is pretty strong against other alternatives. I’ve been using Riot, the most feature rich Matrix client, but mainly as an IRC bouncer.

Just the other day I read this awesome guide on Running your own secure communication service with Matrix and Jitsi and realised it’s not that difficult nowadays to setup all the pieces of the puzzle.

And so if I want to follow those steps shown in the above article but rather do it in NixOS - another project I am really excited about - how easy/difficult it would be ?

Well apart from minor tweaks, it was pretty straightforward and I would argue more simpler than how its done for a Debian based system. Following is the rundown of how I made the relevant steps work for my setup. I am not going to repeat all the nice technical explanations of above article. So anyone following should first read the above article.

Before we begin, I would also like to address one difference from the basic premise of the above article - Unlike in the above article the main domain (dangerousdemos.net) is already pointing to an existing blog hosted from a Github repository. I think this might be a case for many like me who already have a domain name serving a static blog (like this one) but want to setup Matrix via the subdomains.

So lets begin,

NixOS

With NixOS every step is declarative and hence we don’t have to worry about the mechanical rigor as is the case with most of the steps in the original article. What it entails for us broadly are these 3 steps (apart from some additional stuff)

  • Update /etc/nixos/configuration.nix
  • Run sudo nixos-rebuild switch
  • Done!

NixOS made following the original article really easy as I could make mistakes and not worry because I could rollback and start again. Let that sink in as I think that in itself is mind-blowing.

DNS

Let’s use the same domain name - dangerousdemos.net - as used in the original article for our explanations. As mentioned above, we have one change here that the root domain already is redirecting to the static blog hosted in Github Pages. So we need to setup A records for the following subdomains:

  • matrix.dangerousdemos.net (for Synapse)
  • riot.dangerousdemos.net (for Riot/Web)
  • jitsi.dangerousdemos.net (for Jitsi)
  • turn.dangerousdemos.net (for coturn based TURN server that in original article is taken care by jitsi-meet installer)

Its important for all these to setup in the DNS before we run our configurations so that LetsEncrypt challenge flow works fine or else we might face errors.

Firewall

We need to ensure that following ports are open in the NixOS firewall for all the configurations to work fine

 # /etc/nixos/configuration.nix

  networking.firewall = {
    allowedUDPPorts = [ 5349 5350];
    allowedTCPPorts = [ 80 443 3478 3479];
  }

Nginx with LetsEncrypt


# /etc/nixos/configuration.nix


 services.nginx = {
   enable = true;
   virtualHosts = {

     ## virtual host for Syapse
     "matrix.dangerousdemos.net" = {
       ## for force redirecting HTTP to HTTPS
       forceSSL = true;
       ## this setting takes care of all LetsEncrypt business
       enableACME = true;
       locations."/" = {
         proxyPass = "http://localhost:8008";
       };
     };

     ## virtual host for Riot/Web
     "riot.dangerousdemos.net" = {
       ## for force redirecting HTTP to HTTPS
       forceSSL = true;
       ## this setting takes care of all LetsEncrypt business
       enableACME = true;
       ## root points to the riot-web package content, also configured via Nix
       locations."/" = {
           root = pkgs.riot-web;
       };
     };

     ## virtual host for Jitsi, reusing the same hostname as used
     ## while configuring jitsi-meet
     ${config.services.jitsi-meet.hostName} = {
       enableACME = true;
       forceSSL = true;
     };
   };

   ## other nginx specific best practices
   recommendedGzipSettings = true;
   recommendedOptimisation = true;
   recommendedTlsSettings = true;
 };

The above setups Nginx along with LetsEncrypt. If it’s required (like in the original article) to setup main domain as a virtual host then make an additional entry inside virtualHosts


"dangerousdemos.net" = {
  forceSSL = true;
  enableACME = true;
  locations."/" = {
    root = "/var/www/dangerousdemos.net"
  }
}

Synapse

 # /etc/nixos/configuration.nix

  services.matrix-synapse = {
    enable = true;

    ## domain for the Matrix IDs
    server_name = "dangerousdemos.net";

    ## enable metrics collection
    enable_metrics = true;

    ## enable user registration
    enable_registration = true;

    ## Synapse guys recommend to use PostgreSQL over SQLite
    database_type = "psycopg2";

    ## database setup clarified later
    database_args = {
      password = "synapse";
    };

    ## default http listener which nginx will passthrough to
    listeners = [
      {
        port = 8008;
        tls = false;
        resources = [
          {
            compress = true;
            names = ["client" "webclient" "federation"];
          }
        ];
      }
    ];

    ## coturn based TURN server integration (TURN server setup mentioned later),
    ## shared secret generated while configuring coturn
    ## and reused here (power of Nix being a real programming language)
    turn_uris = [
      "turn:turn.dangerousdemos.net:3478?transport=udp"
      "turn:turn.dangerousdemos.net:3478?transport=tcp"
    ];
    turn_shared_secret = config.services.coturn.static-auth-secret;
  };

Unlike in the original article we will do what’s recommended by Synapse team and configure PostgreSQL. In the current NixOS configuration for services.matrix-synapse it might be configuring PostgreSQL automatically based on the database_type setting but that will change in the new versions of NixOS where PostgreSQL setups need to happen separately. Here’s how we will do it for Synapse

 # /etc/nixos/configuration.nix

  services.postgresql = {
    enable = true;

    ## postgresql user and db name remains in the
    ## service.matrix-synapse.database_args setting which
    ## by default is matrix-synapse
    initialScript = pkgs.writeText "synapse-init.sql" ''
        CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
        CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
            TEMPLATE template0
            LC_COLLATE = "C"
            LC_CTYPE = "C";
        '';
  };

Federation setting

Original article’s description for how to configure so that Matrix could find one’s server is nice. I am just leveraging that with one additional setting required when there is already a static blog running via Github Pages pointing to base domain:

  • create a file having this path .well-known/matrix/server in the top level of the Github repository serving the static site having the following content

    { "m.server": "matrix.dangerousdemos.net:443" }
  • Github Pages via Jekyll exclude dotfiles/dotdirs from serving, so we need to explicitly include them by creating _config.yml at the top level of the Github repository having the following content

    include: [".well-known"]

Riot/Web

riot-web is a package already available within Nix packages which will take care of getting the right release package along with signature verification. So we can safely add the same to systemPackages like this

 # /etc/nixos/configuration.nix

  environment = {
    systemPackages = with pkgs; [
        riot-web
    ];
  };

However, we need to let know Nix of the additional configuration required once riot-web is setup. That we could do via overlays. This is another advantage of declarative package management where we state in clear terms the state of our system which also help us revert in case unexpected happens.

 # /etc/nixos/configuration.nix

  nixpkgs.overlays = [
    (self: super: {
        riot-web = super.riot-web.override {
            conf = {
                 default_server_config = {
                    "m.homeserver" = {
                        "base_url" = "https://matrix.dangerousdemos.net";
                        "server_name" = "dangerousdemos.net";
                    };
                    "m.identity_server" = {
                        "base_url" = "https://vector.im";
                    };
                 };

                 ## jitsi will be setup later,
                 ## but we need to add to Riot configuration
                 jitsi.preferredDomain = "jitsi.dangerousdemos.net";
            };
        };
    })
  ];

Jitsi

Finally we head towards our last leg of configurations - setting up jitsi-meet. Unfortunately, current master and stable channels of Nix packages lack jitsi-meet and other supporting packages required to setup Jitsi. However, it will change pretty soon thanks to the awesome work from user mmilata in this pull request. It’s feature complete and might come as part of the next Nix release (20.03). However, we can already use the same via NUR.

 # /etc/nixos/configuration.nix

  imports =
    let
      nur-no-pkgs =
        import (
          builtins.fetchTarball
          "https://github.com/nix-community/NUR/archive/master.tar.gz"
        ) {};
    in
      [
        nur-no-pkgs.repos.mmilata.modules.jitsi-meet
      ];

The above import add only the new service to setup jitsi-meet without any other OS specific stuff. And the configuration for the new service would auto-magically setup the related software (like videobridge & jicofo) to have a functioning Jitsi. We can then add a new virtual host to the Nginx configuration with the Jitsi hostName mentioned below (shown above in Nginx Section)

 # /etc/nixos/configuration.nix

  services.jitsi-meet = {
    enable = true;
    hostName = "jitsi.dangerousdemos.net";

    ## this setting is going to add relevant ports to
    ## networking.firewall.allowedTCPPorts &
    ## networking.firewall.allowedUDPPorts
    videobridge.openFirewall = true;
  };

TURN server

jitsi-meet Debian package takes care of setting up TURN server but in our case we need to take care of it ourselves. I followed the instructions mentioned in this documentation. Nix already has a service configuration to setup coturn. Once set up, we will integrate the relevant parts to Synapse (which we have already done above).

 # /etc/nixos/configuration.nix

  services.coturn = {
    enable = true;
    use-auth-secret = true;
    static-auth-secret = "XJmzTf6VixzX5pDZKHOxtiUenkKzr10tlhBWYoti5DvCxR4TM9XlRHxII3Ml6yV2";
    realm = "turn.dangerousdemos.net";
    no-tcp-relay = true;
    no-tls = true;
    no-dtls = true;
    extraConfig = ''
        user-quota=12
        total-quota=1200
        denied-peer-ip=10.0.0.0-10.255.255.255
        denied-peer-ip=192.168.0.0-192.168.255.255
        denied-peer-ip=172.16.0.0-172.31.255.255

        allowed-peer-ip=192.168.191.127
    '';
  };

static-auth-secret generated above is via the tool pwgen which in Nix we could do like this

$ nix-shell -p pwgen --command "pwgen -s 64 1"
XJmzTf6VixzX5pDZKHOxtiUenkKzr10tlhBWYoti5DvCxR4TM9XlRHxII3Ml6yV2

Conclusion

Once we have all the above configuration in place, now is the time to finally run them all. This will take time to get all the necessary stuff downloaded and setup.

$ sudo nixos-rebuild switch

However, once its done you can launch Riot/Web, register a new user, create a room with someone else from the same MatrixVerse, have end-to-end encrypted conversation, and also start having a video call via Jitsi. All that infrastructure now being 100% owned by you with a protocol enforcing privacy and decentralisation. And finally every piece here is fully open-source.

In these times of covert and overt surveillance its imperative to take matters at your hand. And when right tools make it simple to set them up and easy to reason their behavior, it’s a nice feeling.