# How to use: # # rex commons nsd_master nsd_slaves # # Why use Rex to automate my servers? Because Rex is KISS, Puppet, SALT and Chef # are not. So, why not use Ansible then? To use Ansible correctly you should also # install Python on the target machines (not mandatory, though. But better). # Rex is programmed in Perl and there is already Perl in the base system of OpenBSD. # Also, I find Perl > Python (my personal opinion). use Rex -feature => ['1.14']; use Rex::Logger; use File::Slurp; # REX CONFIG SECTION group frontends => 'blowfish.buetow.org:2', 'fishfinger.buetow.org:2'; group dnsmaster => 'blowfish.buetow.org:2'; group dnsslaves => 'fishfinger.buetow.org:2'; our $ircbouncer_server = 'fishfinger.buetow.org:2'; group ircbouncer => $ircbouncer_server; user 'rex'; sudo TRUE; parallelism 5; # CUSTOM (PERL-ish) CONFIG SECTION (what Rex can't do by itself) # Note we using anonymous subs here. This is so we can pass the subs as # Rex template variables too. # Gather IPv6 addresses based on hostname. our $ipv6address = sub { my $hostname = shift; return '2a01:4f8:c17:20f1::42' if $hostname eq 'blowfish'; return '2a03:6000:6f67:624::99' if $hostname eq 'fishfinger'; Rex::Logger::info("Unable to determine IPv6 address for $hostname", 'error'); return '::1'; }; # Bootstrapping the FQDN based on the server IP as the hostname and domain # facts aren't set yet due to the myname file in the first place. our $fqdns = sub { my $ipv4 = shift; return 'blowfish.buetow.org' if $ipv4 eq '23.88.35.144'; return 'fishfinger.buetow.org' if $ipv4 eq '46.23.94.99'; Rex::Logger::info("Unable to determine hostname for $ipv4", 'error'); return 'HOSTNAME-UNKNOWN.buetow.org'; }; # To determine whether te server is te primary or the secondary. our $is_primary = sub { my $ipv4 = shift; $fqdns->($ipv4) eq 'blowfish.buetow.org'; }; our $filewalk; our $filewalk = sub { my $dir = shift; my @files; opendir my $dh, $dir or die $!; while (my $entry = readdir $dh) { next if $entry eq '.' or $entry eq '..'; if (-d "$dir/$entry") { push @files, $_ for $filewalk->("$dir/$entry"); } elsif (-f "$dir/$entry") { push @files, "$dir/$entry"; } else { Rex::Logger::info("Unsupported file type for $dir/$entry", 'error'); } } closedir $dh; return @files; }; # The secret store. Note to myself: "geheim cat rexfilesecrets.txt" our $secrets = sub { read_file './secrets/' . shift }; our @dns_zones = qw/buetow.org dtail.dev foo.zone irregular.ninja snonux.land paul.cyou/; our @dns_zones_remove = qw//; our @acme_hosts = qw/buetow.org paul.buetow.org tmp.buetow.org dory.buetow.org footos.buetow.org znc.buetow.org dtail.dev foo.zone irregular.ninja snonux.land paul.cyou/; # UTILITY TASKS task 'id', group => 'frontends', sub { say run 'id' }; task 'dump_info', group => 'frontends', sub { dump_system_information }; # OPENBSD TASKS SECTION desc 'Install base stuff'; task 'base', group => 'frontends', sub { pkg 'tig', ensure => present; pkg 'vger', ensure => present; pkg 'zsh', ensure => present; my @pkg_scripts = qw/uptimed httpd dserver icinga2/; push @pkg_scripts, 'znc' if connection->server eq $ircbouncer_server; my $pkg_scripts = join ' ', @pkg_scripts; append_if_no_such_line '/etc/rc.conf.local', "pkg_scripts=\"$pkg_scripts\""; file '/etc/myname', content => template('./etc/myname.tpl', fqdns => $fqdns), owner => 'root', group => 'wheel', mode => '644'; }; desc 'Setup uptimed'; task 'uptimed', group => 'frontends', sub { pkg 'uptimed', ensure => present; service 'uptimed', ensure => 'started'; }; desc 'Setup rsync'; task 'rsync', group => 'frontends', sub { pkg 'rsync', ensure => present; file '/etc/rsyncd.conf', content => template('./etc/rsyncd.conf.tpl'), owner => 'root', group => 'wheel', mode => '644'; file '/usr/local/bin/rsync.sh', content => template('./scripts/rsync.sh.tpl', is_primary => $is_primary), owner => 'root', group => 'wheel', mode => '755'; append_if_no_such_line '/etc/daily.local', '/usr/local/bin/rsync.sh'; }; desc 'Configure the gemtexter sites'; task 'gemtexter', group => 'frontends', sub { file '/usr/local/bin/gemtexter.sh', content => template('./scripts/gemtexter.sh.tpl', is_primary => $is_primary), owner => 'root', group => 'wheel', mode => '744'; file '/etc/daily.local', ensure => 'present', owner => 'root', group => 'wheel', mode => '644'; append_if_no_such_line '/etc/daily.local', '/usr/local/bin/gemtexter.sh'; }; desc 'Configure ACME client'; task 'acme', group => 'frontends', sub { file '/etc/acme-client.conf', content => template('./etc/acme-client.conf.tpl', acme_hosts => \@acme_hosts, is_primary => $is_primary), owner => 'root', group => 'wheel', mode => '644'; file '/usr/local/bin/acme.sh', content => template('./scripts/acme.sh.tpl', acme_hosts => \@acme_hosts, is_primary => $is_primary), owner => 'root', group => 'wheel', mode => '744'; file '/etc/daily.local', ensure => 'present', owner => 'root', group => 'wheel', mode => '644'; append_if_no_such_line '/etc/daily.local', '/usr/local/bin/acme.sh'; }; desc 'Invoke ACME client'; task 'acme_invoke', group => 'frontends', sub { say run '/usr/local/bin/acme.sh'; }; desc 'Setup httpd'; task 'httpd', group => 'frontends', sub { append_if_no_such_line '/etc/rc.conf.local', 'httpd_flags='; #delete_lines_according_to qr{httpd_flags}, '/etc/rc.conf.local'; file '/etc/httpd.conf', content => template('./etc/httpd.conf.tpl', acme_hosts => \@acme_hosts, is_primary => $is_primary), owner => 'root', group => 'wheel', mode => '644', on_change => sub { service 'httpd' => 'restart' }; service 'httpd', ensure => 'started'; }; desc 'Setup inetd'; task 'inetd', group => 'frontends', sub { append_if_no_such_line '/etc/rc.conf.local', 'inetd_flags='; file '/etc/inetd.conf', source => './etc/inetd.conf', owner => 'root', group => 'wheel', mode => '644', on_change => sub { service 'inetd' => 'restart' }; service 'inetd', ensure => 'started'; }; desc 'Setup relayd'; task 'relayd', group => 'frontends', sub { append_if_no_such_line '/etc/rc.conf.local', 'relayd_flags='; file '/etc/relayd.conf', content => template('./etc/relayd.conf.tpl', ipv6address => $ipv6address, is_primary => $is_primary), owner => 'root', group => 'wheel', mode => '600', on_change => sub { service 'relayd' => 'restart' }; service 'relayd', ensure => 'started'; }; desc 'Setup OpenSMTPD'; task 'smtpd', group => 'frontends', sub { Rex::Logger::info('Dealing with mail aliases'); file '/etc/mail/aliases', source => './etc/mail/aliases', owner => 'root', group => 'wheel', mode => '644', on_change => sub { say run 'newaliases' }; Rex::Logger::info('Dealing with mail virtual domains'); file '/etc/mail/virtualdomains', source => './etc/mail/virtualdomains', owner => 'root', group => 'wheel', mode => '644', on_change => sub { service 'smtpd' => 'restart' }; Rex::Logger::info('Dealing with mail virtual users'); file '/etc/mail/virtualusers', source => './etc/mail/virtualusers', owner => 'root', group => 'wheel', mode => '644', on_change => sub { service 'smtpd' => 'restart' }; Rex::Logger::info('Dealing with smtpd.conf'); file '/etc/mail/smtpd.conf', content => template('./etc/mail/smtpd.conf.tpl'), owner => 'root', group => 'wheel', mode => '644', on_change => sub { service 'smtpd' => 'restart' }; service 'smtpd', ensure => 'started'; }; desc 'Setup DNS server'; task 'nsd_master', group => 'dnsmaster', sub { my $restart = FALSE; append_if_no_such_line '/etc/rc.conf.local', 'nsd_flags='; Rex::Logger::info('Dealing with master DNS key'); file '/var/nsd/etc/key.conf', content => template('./var/nsd/etc/key.conf.tpl', nsd_key => $secrets->('/var/nsd/etc/nsd_key.txt')), owner => 'root', group => '_nsd', mode => '640', on_change => sub { $restart = TRUE }; Rex::Logger::info('Dealing with master DNS config'); file '/var/nsd/etc/nsd.conf', content => template('./var/nsd/etc/nsd.conf.master.tpl', dns_zones => \@dns_zones), owner => 'root', group => '_nsd', mode => '640', on_change => sub { $restart = TRUE }; for my $zone (@dns_zones) { Rex::Logger::info("Dealing with DNS zone $zone"); file "/var/nsd/zones/master/$zone.zone", content => template("./var/nsd/zones/master/$zone.zone.tpl"), owner => 'root', group => 'wheel', mode => '644', on_change => sub { $restart = TRUE }; } for my $zone (@dns_zones_remove) { Rex::Logger::info("Dealing with DNS zone removal $zone"); file "/var/nsd/zones/master/$zone.zone", ensure => 'absent'; } service 'nsd' => 'restart' if $restart; service 'nsd', ensure => 'started'; }; desc 'Setup DNS slaves'; task 'nsd_slaves', group => 'dnsslaves', sub { my $restart = FALSE; Rex::Logger::info('Dealing with slave DNS key'); file '/var/nsd/etc/key.conf', content => template('./var/nsd/etc/key.conf.tpl', nsd_key => $secrets->('/var/nsd/etc/nsd_key.txt')), owner => 'root', group => '_nsd', mode => '640', on_change => sub { $restart = TRUE }; Rex::Logger::info('Dealing with slave DNS config'); file '/var/nsd/etc/nsd.conf', content => template('./var/nsd/etc/nsd.conf.slave.tpl', dns_zones => \@dns_zones), owner => 'root', group => '_nsd', mode => '640', on_change => sub { $restart = TRUE }; service 'nsd' => 'restart' if $restart; service 'nsd', ensure => 'started'; }; desc 'Setup DTail'; task 'dtail', group => 'frontends', sub { my $restart = FALSE; file '/etc/rc.d/dserver', content => template('./etc/rc.d/dserver.tpl'), owner => 'root', group => 'wheel', mode => '755', on_change => sub { $restart = TRUE }; file '/etc/dserver', ensure => 'directory', owner => 'root', group => 'wheel', mode => '755'; file '/etc/dserver/dtail.json', content => template('./etc/dserver/dtail.json.tpl'), owner => 'root', group => 'wheel', mode => '755', on_change => sub { $restart = TRUE }; file '/usr/local/bin/dserver-update-key-cache.sh', content => template('./scripts/dserver-update-key-cache.sh.tpl'), owner => 'root', group => 'wheel', mode => '500'; append_if_no_such_line '/etc/daily.local', '/usr/local/bin/dserver-update-key-cache.sh'; run 'adduser -class nologin -group _dserver -batch _dserver', unless => 'id _dserver'; run 'usermod -d /var/run/dserver _dserver'; service 'dserver' => 'restart' if $restart; service 'dserver', ensure => 'started'; }; desc 'Installing Gogios binary'; task 'gogios_install', group => 'frontends', sub { file '/usr/local/bin/gogios', source => 'usr/local/bin/gogios', mode => '0755'; owner => 'root', group => 'root'; }; desc 'Setup Gogios monitoring system'; task 'gogios', group => 'frontends', sub { pkg 'monitoring-plugins', ensure => present; pkg 'nrpe', ensure => present; my $gogios_path = '/usr/local/bin/gogios'; file '/etc/gogios.json', content => template('./etc/gogios.json.tpl', acme_hosts => \@acme_hosts), owner => 'root', group => 'wheel', mode => '744'; file '/var/run/gogios', ensure => 'directory', owner => '_gogios', group => '_gogios', mode => '755'; unless (is_file($gogios_path)) { Rex::Logger::info("Gogios not installed to $gogios_path! Run task 'gogios_install'", 'error'); } run 'adduser -group _gogios -batch _gogios', unless => 'id _gogios'; run 'usermod -d /var/run/gogios _gogios'; file '/tmp/gogios.cron', ensure => 'file', content => template('./etc/gogios.cron.tpl', gogios_path => $gogios_path), mode => '600'; run 'cat /tmp/gogios.cron | crontab -u _gogios -'; run 'rm /tmp/gogios.cron'; }; desc 'Setup IRC bouncer'; task 'ircbouncer', group => 'ircbouncer', sub { pkg 'znc', ensure => present; # Requires runtime config in /var/znc before it can start. # => geheim search znc.conf service 'znc', ensure => 'started'; }; # COMBINED TASKS SECTION desc 'Common configs of all hosts'; task 'commons', group => 'frontends', sub { base(); uptimed(); httpd(); gemtexter(); acme(); acme_invoke(); inetd(); relayd(); smtpd(); rsync(); gogios(); # Requires manually installing the binary! # dtail(); }; 1; # vim: syntax=perl