About me: My name is Solène Rapenne, pronouns she/her. I like learning and sharing knowledge. Hobbies: '(BSD OpenBSD Lisp cmdline gaming internet-stuff). I love percent and lambda characters. OpenBSD developer solene@.

Contact me: solene on libera.chat, solene+www at dataswamp dot org or @solene@bsd.network (mastodon). If for some reason you want to support my work, this is my paypal address: donate@perso.pw.

(R)?ex automation for deploying Matrix synapse on OpenBSD

Written by Solène, on 31 May 2021.
Tags: #rex #matrix #openbsd

Comments on Mastodon

Introduction §

Today I will introduce you to Rex, an automation tool written in Perl and using SSH, it's an alternative to Salt, Ansible or drist.

(R)?ex project website

Setup §

You need to install Rex on the management system, this can be done using cpan or your package manager, on OpenBSD you can use "pkg_add p5-Rex" to install it. You will get an executable script named "rex".

To make things easier, we will use ssh from the management machine (your own computer) and a remote server, using your ssh key to access the root account (escalation with sudo is possible but will complicate things).

Get Rex

Simple steps §

Create a text file named "Rexfile" in a directory, this will contain all the instructions and tasks available.

We will write in it that we want the features up to the syntax version 1.4 (latest at this time, doesn't change often), the default user to connect to remote host will be root and our servers group has only one address.

use Rex -feature => ['1.4'];

user "root";
group servers => "myremoteserver.com";

We can go further now.

Rex commands cheat sheet §

Here are some commands, you don't need much to use Rex.

- rex -T : display the list of tasks defined in Rexfile

- rex -h : display help

- rex -d : when you need some debug

- rex -g : run a task on group

Installing Munin-master §

An example I like is deploying Munin on a computer, it requires a cron and a package.

The following task will install a package and add a crontab entry for root.

desc "Munin-cron installation";
task "install_munin_cron", sub {
	pkg "munin-server", ensure => "present";
	
	cron add => "root", {
		ensure => "present",
		command = > "su -s /bin/sh _munin /usr/local/bin/munin-cron",
		on_change => sub {
			say "Munin cron modified";
		}
	};
};

Now, let's say we want to configure this munin cron by providing it a /etc/munin/munin.conf file that we have locally. This can be done by adding the following code:

	file "/etc/munin/munin.conf",
	source => "local_munin.conf",
	owner => "root",
	group => "wheel",
	mode => 644,
	on_change => sub {
		say "munin.conf has been modified";
	};

This will install the local file "local_munin.conf" into "/etc/munin/munin.conf" on the remote host, owned by root:wheel with a chmod 644.

Now you can try "rex -g servers install_munin_cron" to deploy.

Real world tasks §

Configuring PF §

This task deploys a local pf.conf file into /etc/pf.conf and reload the configuration on changes.

desc "Configuration PF";
task "prepare_pf", sub {

    file "/etc/pf.conf",
    source => "pf.conf",
    owner => "root",
    group => "wheel",
    mode => 400,
    on_change => sub {
        say "pf.conf modified";
        run "Restart pf", command => "pfctl -f /etc/pf.conf";
    };
};

Deploying Matrix Synapse §

A task can call multiples tasks for bigger deployments. In this one, we have a "synapse_deploy" task that will run synapse_install() and then synapse_configure() and synapse_service() and finally prepare_pf() to ensure the rules are correct.

As synapse will generate a working config file, there are no reason to push one from the local system.

desc "Deploy synapse";
task "synapse_deploy", sub {
    synapse_install();
    synapse_configure();
    synapse_service();
    prepare_pf();
};

desc "Install synapse";
task "synapse_install", sub {
    pkg "synapse", ensure => "present";
    
    run "Init synapse",
    	command => 'su -s /bin/sh _synapse -c "/usr/local/bin/python3 -m synapse.app.homeserver -c /var/synapse/
    	cwd => "/tmp/",
    	only_if => is_file("/var/synapse/homeserver.yaml");
};

desc "Configure synapse";
task "synapse_configure", sub {
    file "/etc/nginx/sites-enabled/synapse.conf",
    	source => "nginx_synapse.conf",
    	owner => "root",
    	group => "wheel",
    	mode => "444",
    	on_change => sub {
    		service nginx => "reload";
    	};
};

desc "Service for synapse";
task "synapse_service", sub {
    service synapse => "ensure", "started";
};

Going further §

Rex offers many feature because the configuration is real Perl code, you can make loops, conditions and extend Rex by writing local modules.

Instead of pushing configuration file from an hard coded local one, I could write a template of the configuration file and then use Rex to generate the configuration file on the fly by giving it the needed variables.

Rex has many functions to directly alter text files like "append-if_no_such_line" to add a line if it doesn't exist or replace/add/update a line matching a regex (can be handy to uncomment some lines).

Full list of Rex commands

Rex guides

Rex FAQ

Conclusion §

Rex is a fantastic tool if you want to programmaticaly configure a system, it can even be used for your local machine to allow reproducible configuration or for keeping track of all the changes in one place.

I really like it because it's simple to work with, it's Perl code doing real things, it's easy to hack on it (I contributed to some changes and the process was easy) and it only requires a working ssh toward a server (and Perl on the remote host). While Salt stack also works "agent less", it's painfully slow compared to Rex.