summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Fennell <matthew@fennell.dev>2026-09-09 22:22:07 +0100
committerMatthew Fennell <matthew@fennell.dev>2026-09-09 22:22:07 +0100
commit4d958b4784ceda51e0dbb95f8f87d23fdbc4153a (patch)
tree92170f3847ebb0ab355486158367920a8ae0b1cb
parenta9d2acc5987edc8425bfb39156befa7a14adae6b (diff)
Use bootstrap and munin-node roles
I've moved these tasks into their own roles, which can now be used directly.
-rw-r--r--files/50-disable-password-auth.conf5
-rw-r--r--files/50unattended-upgrades168
-rw-r--r--files/hosts.j211
-rw-r--r--files/munin-node.conf.j269
-rw-r--r--playbook.yaml266
5 files changed, 3 insertions, 516 deletions
diff --git a/files/50-disable-password-auth.conf b/files/50-disable-password-auth.conf
deleted file mode 100644
index 483b8f2..0000000
--- a/files/50-disable-password-auth.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-# SPDX-FileCopyrightText: 2024 Matthew Fennell <matthew@fennell.dev>
-#
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-PasswordAuthentication no
diff --git a/files/50unattended-upgrades b/files/50unattended-upgrades
deleted file mode 100644
index ba218a4..0000000
--- a/files/50unattended-upgrades
+++ /dev/null
@@ -1,168 +0,0 @@
-// SPDX-FileCopyrightText: 2024 Matthew Fennell <matthew@fennell.dev>
-//
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-// Unattended-Upgrade::Origins-Pattern controls which packages are
-// upgraded.
-//
-// Lines below have the format "keyword=value,...". A
-// package will be upgraded only if the values in its metadata match
-// all the supplied keywords in a line. (In other words, omitted
-// keywords are wild cards.) The keywords originate from the Release
-// file, but several aliases are accepted. The accepted keywords are:
-// a,archive,suite (eg, "stable")
-// c,component (eg, "main", "contrib", "non-free")
-// l,label (eg, "Debian", "Debian-Security")
-// o,origin (eg, "Debian", "Unofficial Multimedia Packages")
-// n,codename (eg, "jessie", "jessie-updates")
-// site (eg, "http.debian.net")
-// The available values on the system are printed by the command
-// "apt-cache policy", and can be debugged by running
-// "unattended-upgrades -d" and looking at the log file.
-//
-// Within lines unattended-upgrades allows 2 macros whose values are
-// derived from /etc/debian_version:
-// ${distro_id} Installed origin.
-// ${distro_codename} Installed codename (eg, "buster")
-Unattended-Upgrade::Origins-Pattern {
- // Codename based matching:
- // This will follow the migration of a release through different
- // archives (e.g. from testing to stable and later oldstable).
- // Software will be the latest available for the named release,
- // but the Debian release itself will not be automatically upgraded.
-// "origin=Debian,codename=${distro_codename}-updates";
-// "origin=Debian,codename=${distro_codename}-proposed-updates";
- "origin=Debian,codename=${distro_codename},label=Debian";
- "origin=Debian,codename=${distro_codename},label=Debian-Security";
- "origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
-
- // Archive or Suite based matching:
- // Note that this will silently match a different release after
- // migration to the specified archive (e.g. testing becomes the
- // new stable).
-// "o=Debian,a=stable";
-// "o=Debian,a=stable-updates";
-// "o=Debian,a=proposed-updates";
-// "o=Debian Backports,a=${distro_codename}-backports,l=Debian Backports";
-};
-
-// Python regular expressions, matching packages to exclude from upgrading
-Unattended-Upgrade::Package-Blacklist {
- // The following matches all packages starting with linux-
-// "linux-";
-
- // Use $ to explicitely define the end of a package name. Without
- // the $, "libc6" would match all of them.
-// "libc6$";
-// "libc6-dev$";
-// "libc6-i686$";
-
- // Special characters need escaping
-// "libstdc\+\+6$";
-
- // The following matches packages like xen-system-amd64, xen-utils-4.1,
- // xenstore-utils and libxenstore3.0
-// "(lib)?xen(store)?";
-
- // For more information about Python regular expressions, see
- // https://docs.python.org/3/howto/regex.html
-};
-
-// This option allows you to control if on a unclean dpkg exit
-// unattended-upgrades will automatically run
-// dpkg --force-confold --configure -a
-// The default is true, to ensure updates keep getting installed
-//Unattended-Upgrade::AutoFixInterruptedDpkg "true";
-
-// Split the upgrade into the smallest possible chunks so that
-// they can be interrupted with SIGTERM. This makes the upgrade
-// a bit slower but it has the benefit that shutdown while a upgrade
-// is running is possible (with a small delay)
-//Unattended-Upgrade::MinimalSteps "true";
-
-// Install all updates when the machine is shutting down
-// instead of doing it in the background while the machine is running.
-// This will (obviously) make shutdown slower.
-// Unattended-upgrades increases logind's InhibitDelayMaxSec to 30s.
-// This allows more time for unattended-upgrades to shut down gracefully
-// or even install a few packages in InstallOnShutdown mode, but is still a
-// big step back from the 30 minutes allowed for InstallOnShutdown previously.
-// Users enabling InstallOnShutdown mode are advised to increase
-// InhibitDelayMaxSec even further, possibly to 30 minutes.
-//Unattended-Upgrade::InstallOnShutdown "false";
-
-// Send email to this address for problems or packages upgrades
-// If empty or unset then no email is sent, make sure that you
-// have a working mail setup on your system. A package that provides
-// 'mailx' must be installed. E.g. "user@example.com"
-//Unattended-Upgrade::Mail "";
-
-// Set this value to one of:
-// "always", "only-on-error" or "on-change"
-// If this is not set, then any legacy MailOnlyOnError (boolean) value
-// is used to chose between "only-on-error" and "on-change"
-//Unattended-Upgrade::MailReport "on-change";
-
-// Remove unused automatically installed kernel-related packages
-// (kernel images, kernel headers and kernel version locked tools).
-//Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
-
-// Do automatic removal of newly unused dependencies after the upgrade
-//Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
-
-// Do automatic removal of unused packages after the upgrade
-// (equivalent to apt-get autoremove)
-Unattended-Upgrade::Remove-Unused-Dependencies "true";
-
-// Automatically reboot *WITHOUT CONFIRMATION* if
-// the file /var/run/reboot-required is found after the upgrade
-Unattended-Upgrade::Automatic-Reboot "true";
-
-// Automatically reboot even if there are users currently logged in
-// when Unattended-Upgrade::Automatic-Reboot is set to true
-//Unattended-Upgrade::Automatic-Reboot-WithUsers "true";
-
-// If automatic reboot is enabled and needed, reboot at the specific
-// time instead of immediately
-// Default: "now"
-//Unattended-Upgrade::Automatic-Reboot-Time "02:00";
-
-// Use apt bandwidth limit feature, this example limits the download
-// speed to 70kb/sec
-//Acquire::http::Dl-Limit "70";
-
-// Enable logging to syslog. Default is False
-// Unattended-Upgrade::SyslogEnable "false";
-
-// Specify syslog facility. Default is daemon
-// Unattended-Upgrade::SyslogFacility "daemon";
-
-// Download and install upgrades only on AC power
-// (i.e. skip or gracefully stop updates on battery)
-// Unattended-Upgrade::OnlyOnACPower "true";
-
-// Download and install upgrades only on non-metered connection
-// (i.e. skip or gracefully stop updates on a metered connection)
-// Unattended-Upgrade::Skip-Updates-On-Metered-Connections "true";
-
-// Verbose logging
-// Unattended-Upgrade::Verbose "false";
-
-// Print debugging information both in unattended-upgrades and
-// in unattended-upgrade-shutdown
-// Unattended-Upgrade::Debug "false";
-
-// Allow package downgrade if Pin-Priority exceeds 1000
-// Unattended-Upgrade::Allow-downgrade "false";
-
-// When APT fails to mark a package to be upgraded or installed try adjusting
-// candidates of related packages to help APT's resolver in finding a solution
-// where the package can be upgraded or installed.
-// This is a workaround until APT's resolver is fixed to always find a
-// solution if it exists. (See Debian bug #711128.)
-// The fallback is enabled by default, except on Debian's sid release because
-// uninstallable packages are frequent there.
-// Disabling the fallback speeds up unattended-upgrades when there are
-// uninstallable packages at the expense of rarely keeping back packages which
-// could be upgraded or installed.
-// Unattended-Upgrade::Allow-APT-Mark-Fallback "true";
diff --git a/files/hosts.j2 b/files/hosts.j2
deleted file mode 100644
index 9ef5300..0000000
--- a/files/hosts.j2
+++ /dev/null
@@ -1,11 +0,0 @@
-# SPDX-FileCopyrightText: 2025 Matthew Fennell <matthew@fennell.dev>
-#
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-127.0.1.1 {{ hostname }}
-127.0.0.1 localhost
-
-# The following lines are desirable for IPv6 capable hosts
-::1 localhost ip6-localhost ip6-loopback
-ff02::1 ip6-allnodes
-ff02::2 ip6-allrouters
diff --git a/files/munin-node.conf.j2 b/files/munin-node.conf.j2
deleted file mode 100644
index 81400a9..0000000
--- a/files/munin-node.conf.j2
+++ /dev/null
@@ -1,69 +0,0 @@
-# SPDX-FileCopyrightText: 2026 Matthew Fennell <matthew@fennell.dev>
-#
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-#
-# Example config-file for munin-node
-#
-
-log_level 4
-log_file /var/log/munin/munin-node.log
-pid_file /var/run/munin/munin-node.pid
-
-background 1
-setsid 1
-
-user root
-group root
-
-# This is the timeout for the whole transaction.
-# Units are in sec. Default is 15 min
-#
-# global_timeout 900
-
-# This is the timeout for each plugin.
-# Units are in sec. Default is 1 min
-#
-# timeout 60
-
-# Regexps for files to ignore
-ignore_file [\#~]$
-ignore_file DEADJOE$
-ignore_file \.bak$
-ignore_file %$
-ignore_file \.dpkg-(tmp|new|old|dist)$
-ignore_file \.rpm(save|new)$
-ignore_file \.pod$
-
-# Set this if the client doesn't report the correct hostname when
-# telnetting to localhost, port 4949
-#
-#host_name localhost.localdomain
-host_name {{ hostname }}
-
-# A list of addresses that are allowed to connect. This must be a
-# regular expression, since Net::Server does not understand CIDR-style
-# network notation unless the perl module Net::CIDR is installed. You
-# may repeat the allow line as many times as you'd like
-
-allow ^127\.0\.0\.1$
-allow ^::1$
-allow ^{{ munin_host }}$
-
-# If you have installed the Net::CIDR perl module, you can use one or more
-# cidr_allow and cidr_deny address/mask patterns. A connecting client must
-# match any cidr_allow, and not match any cidr_deny. Note that a netmask
-# *must* be provided, even if it's /32
-#
-# Example:
-#
-# cidr_allow 127.0.0.1/32
-# cidr_allow 192.0.2.0/24
-# cidr_deny 192.0.2.42/32
-
-# Which address to bind to;
-host *
-# host 127.0.0.1
-
-# And which port
-port 4949
diff --git a/playbook.yaml b/playbook.yaml
index ebeefd5..7a9dc79 100644
--- a/playbook.yaml
+++ b/playbook.yaml
@@ -7,84 +7,11 @@
- name: Ensure XMPP server is set up
gather_facts: false
hosts: all
+ roles:
+ - bootstrap
+ - munin-node
tasks:
- - name: Ensure all authorized keys are copied to root account
- remote_user: root
- ansible.posix.authorized_key:
- key: |
- {% for key in authorized_key_files %}
- {{ lookup('file', key) }}
- {% endfor %}
- user: root
- exclusive: true
-
- # Now, we create a non-root user with sudo privileges
- - name: Ensure wheel group exists
- remote_user: root
- ansible.builtin.group:
- name: wheel
- state: present
-
- - name: Ensure wheel group allows passwordless sudo
- remote_user: root
- ansible.builtin.lineinfile:
- dest: /etc/sudoers
- state: present
- regexp: "^%wheel"
- line: "%wheel ALL=(ALL) NOPASSWD: ALL"
- validate: visudo -cf %s
-
- - name: Ensure non-root admin account is created in wheel group
- remote_user: root
- ansible.builtin.user:
- name: admin
- groups: wheel
- append: true
-
- - name: Ensure admin ssh directory exists
- remote_user: root
- ansible.builtin.file:
- path: /home/admin/.ssh
- state: directory
- owner: admin
- group: admin
- mode: "0700"
-
- - name: Copy authorised keys to admin account
- remote_user: root
- ansible.builtin.copy:
- src: /root/.ssh/authorized_keys
- dest: /home/admin/.ssh/authorized_keys
- remote_src: true
- owner: admin
- group: admin
- mode: preserve
-
- - name: Ensure cloud-init is disabled
- ansible.builtin.copy:
- content: ""
- dest: /etc/cloud/cloud-init.disabled
- force: false
- owner: root
- group: root
- mode: "0644"
- become: true
-
- - name: Ensure hostname is set
- ansible.builtin.hostname:
- name: "{{ hostname }}"
- become: true
-
- - name: Ensure hostname is configured in /etc/hosts
- ansible.builtin.template:
- src: "{{ playbook_dir }}/files/hosts.j2"
- dest: /etc/hosts
- owner: root
- group: root
- mode: "0644"
- become: true
-
- name: Retrieve DANE hash
ansible.builtin.shell:
cmd: >
@@ -216,39 +143,6 @@
tags:
- dns
- # This is needed by ansible.builtin.deb822_repository to interact with the
- # sources lists. In ansible-core 2.20 and above, there is a builtin
- # install_python_debian parameter that handles that without this extra step,
- # but we are currently running 2.19.
- # TODO(debian-forky) use deb822_repository's install_python_debian parameter
- - name: Ensure python3-debian is installed
- ansible.builtin.apt:
- name:
- - python3-debian
- state: present
- update_cache: true
- become: true
-
- - name: Ensure debian apt repositories are configured
- ansible.builtin.deb822_repository:
- name: debian
- uris: "{{ debian_uri }}"
- suites:
- - "{{ debian_version }}"
- - "{{ debian_version }}-updates"
- components: main
- signed_by: /usr/share/keyrings/debian-archive-keyring.gpg
- become: true
-
- - name: Ensure debian-security apt repositories are configured
- ansible.builtin.deb822_repository:
- name: debian-security
- uris: "{{ debian_security_uri }}"
- suites: "{{ debian_version }}-security"
- components: main
- signed_by: /usr/share/keyrings/debian-archive-keyring.gpg
- become: true
-
# We specifically use apt instead of the more general package module here,
# because we want to ensure the cache is updated before we try and install
# anything. This is needed because, on a freh Debian install on AWS
@@ -258,18 +152,12 @@
- name: Ensure required packages are installed
ansible.builtin.apt:
name:
- - libdbi-perl # Used by munin postgres plugins
- lua-dbi-postgresql # Prosody postgres connection
- lua-unbound # Prosody DNS resolution
- - munin-node # Graphs and monitoring
- postgresql # Database
- prosody # XMPP server
- prosody-modules # Extra addons
- python3-psycopg2 # Used by ansible postgres role
- - rsync # Backups
- - systemd-timesyncd # Used to make sure the date is correct
- - ufw # Firewall
- - unattended-upgrades # Not every hosting provider installs by default
state: present
update_cache: true
become: true
@@ -351,7 +239,6 @@
proto: tcp
state: enabled
loop:
- - 4949 # Munin
- 5000 # XEP-0065
- 5223 # XEP-0368
- 5270 # XEP-0368
@@ -402,113 +289,6 @@
notify: Restart nginx
when: not is_transport_server
- - name: Ensure munin is configured
- ansible.builtin.template:
- src: "{{ playbook_dir }}/files/munin-node.conf.j2"
- dest: /etc/munin/munin-node.conf
- owner: root
- group: root
- mode: "0644"
- become: true
- notify: Restart munin-node
-
- - name: Ensure munin plugins are configured
- ansible.builtin.file:
- src: "/usr/share/munin/plugins/{{ item.src }}"
- dest: "/etc/munin/plugins/{{ item.dest }}"
- owner: root
- group: root
- state: link
- become: true
- with_items:
- - src: apt_all
- dest: apt_all
- - src: cpu
- dest: cpu
- - src: df
- dest: df
- - src: df_abs
- dest: df_abs
- - src: df_inode
- dest: df_inode
- - src: diskstats
- dest: diskstats
- - src: entropy
- dest: entropy
- - src: forks
- dest: forks
- - src: fw_conntrack
- dest: fw_conntrack
- - src: fw_forwarded_local
- dest: fw_forwarded_local
- - src: fw_packets
- dest: fw_packets
- - src: if_
- dest: if_enp1s0
- - src: if_err_
- dest: if_err_enp1s0
- - src: irqstats
- dest: irqstats
- - src: load
- dest: load
- - src: memory
- dest: memory
- - src: netstat
- dest: netstat
- - src: open_files
- dest: open_files
- - src: open_inodes
- dest: open_inodes
- - src: postgres_autovacuum
- dest: postgres_autovacuum
- - src: postgres_bgwriter
- dest: postgres_bgwriter
- - src: postgres_cache_
- dest: postgres_cache_prosody
- - src: postgres_checkpoints
- dest: postgres_checkpoints
- - src: postgres_connections_
- dest: postgres_connections_prosody
- - src: postgres_connections_db
- dest: postgres_connections_db
- - src: postgres_locks_
- dest: postgres_locks_prosody
- - src: postgres_oldest_prepared_xact_
- dest: postgres_oldest_prepared_xact_prosody
- - src: postgres_prepared_xacts_
- dest: postgres_prepared_xacts_prosody
- - src: postgres_querylength_
- dest: postgres_querylength_prosody
- - src: postgres_scans_
- dest: postgres_scans_prosody
- - src: postgres_size_
- dest: postgres_size_prosody
- - src: postgres_streaming_
- dest: postgres_streaming_prosody
- - src: postgres_transactions_
- dest: postgres_transactions_prosody
- - src: postgres_tuples_
- dest: postgres_tuples_prosody
- - src: postgres_users
- dest: postgres_users
- - src: postgres_xlog
- dest: postgres_xlog
- - src: proc_pri
- dest: proc_pri
- - src: processes
- dest: processes
- - src: swap
- dest: swap
- - src: threads
- dest: threads
- - src: uptime
- dest: uptime
- - src: users
- dest: users
- - src: vmstat
- dest: vmstat
- notify: Restart munin-node
-
- name: Ensure turn is configured
ansible.builtin.template:
src: "{{ playbook_dir }}/files/turnserver.conf.j2"
@@ -622,34 +402,6 @@
enabled: true
become: true
- # Vultr adds a custom sshd_config file that enabled password authentication.
- # I don't want this to be enabled, since I'm already copying the public key.
- - name: Ensure password authentication is not explicitly enabled
- ansible.builtin.file:
- path: "/etc/ssh/sshd_config.d/50-cloud-init.conf"
- state: absent
- become: true
- notify: Restart sshd
-
- - name: Ensure password based authentication is disabled
- ansible.builtin.copy:
- src: "{{ playbook_dir }}/files/50-disable-password-auth.conf"
- dest: "/etc/ssh/sshd_config.d/50-disable-password-auth.conf"
- owner: root
- group: root
- mode: "0644"
- become: true
- notify: Restart sshd
-
- - name: Ensure unattended upgrades config is installed
- ansible.builtin.copy:
- src: "{{ playbook_dir }}/files/50unattended-upgrades"
- dest: "/etc/apt/apt.conf.d/50unattended-upgrades"
- owner: root
- group: root
- mode: "0644"
- become: true
-
handlers:
- name: Restart prosody
@@ -664,24 +416,12 @@
state: restarted
become: true
- - name: Restart sshd
- ansible.builtin.service:
- name: sshd
- state: restarted
- become: true
-
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
become: true
- - name: Restart munin-node
- ansible.builtin.service:
- name: munin-node
- state: restarted
- become: true
-
vars:
env_prefix: >-