From 824d7b8a128be466a5bfc87caa121e8e6858130d Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Thu, 24 Apr 2025 18:05:28 +0200 Subject: [PATCH] feat: add initial setup for pearl server --- README.md | 15 +++- Vagrantfile | 87 ++++++++++++++++--- inventory/vagrant.ini | 2 - inventory/vagrant.yml | 6 ++ plays/pearl.yml | 6 +- .../tasks/main.yml | 8 +- .../templates/sources.list.j2 | 10 +++ .../files/sources.list | 10 --- roles/any.tools.caddy/tasks/main.yml | 9 +- roles/any.tools.default/tasks/main.yml | 60 +++++++++++++ roles/any.tools.docker/tasks/main.yml | 44 ++++++++++ .../files/restic_backups_passwd | 7 ++ roles/any.tools.restic/tasks/main.yml | 56 ++++++++++++ .../templates/backup-all.sh.j2 | 43 +++++++++ 14 files changed, 329 insertions(+), 34 deletions(-) delete mode 100644 inventory/vagrant.ini create mode 100644 inventory/vagrant.yml rename roles/{any.common.enable-testing => any.common.debian-repositories}/tasks/main.yml (69%) create mode 100644 roles/any.common.debian-repositories/templates/sources.list.j2 delete mode 100644 roles/any.common.enable-testing/files/sources.list create mode 100644 roles/any.tools.default/tasks/main.yml create mode 100644 roles/any.tools.docker/tasks/main.yml create mode 100644 roles/any.tools.restic/files/restic_backups_passwd create mode 100644 roles/any.tools.restic/tasks/main.yml create mode 100644 roles/any.tools.restic/templates/backup-all.sh.j2 diff --git a/README.md b/README.md index a492204..bd0ac85 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,16 @@ -# Raspberry Pi NAS +# Homelab -Ansible configuration repository for my Raspberry Pi's. +Ansible configuration repository for my homelab. -## Initial setup for new systems +## Servers + +* `pi`: Raspberry Pi's + * `ruby` + * `sapphire` +* `hetzner`: Hetzner VPS servers + * `pearl` + +## Initial setup for new Raspberry Pi 1. Flash [Debian Raspberry Pi](https://raspi.debian.net/) on the SD card. 2. Configure `/boot/firmware/sysconf.txt` @@ -18,3 +26,4 @@ Ansible configuration repository for my Raspberry Pi's. overwrites the one set in the hosts file 9. Run `ansible-playbook -i initial-hosts.ini first_run.yml`. This command will hang at the `restart networking` step; at this point you can Ctrl-C. + diff --git a/Vagrantfile b/Vagrantfile index dc19376..9bd5dbe 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,17 +1,80 @@ # -*- mode: ruby -*- # vi: set ft=ruby : + +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. Vagrant.configure("2") do |config| - config.vm.box = "generic/debian11" - - # Use the standard insecure SSH key - config.ssh.insert_key = false - - # Don't mount the current directory in the VM - config.vm.synced_folder ".", "/vagrant", disabled: true + # The most common configuration options are documented and commented below. + # For a complete reference, please see the online documentation at + # https://docs.vagrantup.com. - config.vm.define "alpha" do |n| - n.vm.hostname = "alpha.test" - n.vm.network :private_network, ip: "192.168.56.5" - end + # Every Vagrant development environment requires a box. You can search for + # boxes at https://vagrantcloud.com/search. + config.vm.box = "bento/debian-12" + + # Disable automatic box update checking. If you disable this, then + # boxes will only be checked for updates when the user runs + # `vagrant box outdated`. This is not recommended. + # config.vm.box_check_update = false + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + # NOTE: This will enable public access to the opened port + # config.vm.network "forwarded_port", guest: 80, host: 8080 + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine and only allow access + # via 127.0.0.1 to disable public access + # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network "public_network" + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Disable the default share of the current code directory. Doing this + # provides improved isolation between the vagrant box and your host + # by making sure your Vagrantfile isn't accessible to the vagrant box. + # If you use this you may want to enable additional shared subfolders as + # shown above. + config.vm.synced_folder ".", "/vagrant", disabled: true + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + config.vm.provider "virtualbox" do |vb| + # Display the VirtualBox GUI when booting the machine + vb.gui = false + + # Customize the amount of memory on the VM: + vb.memory = "4096" + end + + config.vm.define "pearl" do |pearl| + config.vm.network "private_network", ip: "192.168.56.2" + end + # + # View the documentation for the provider you are using for more + # information on available options. + + # Enable provisioning with a shell script. Additional provisioners such as + # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the + # documentation for more information about their specific syntax and use. + # config.vm.provision "shell", inline: <<-SHELL + # apt-get update + # apt-get install -y apache2 + # SHELL end - diff --git a/inventory/vagrant.ini b/inventory/vagrant.ini deleted file mode 100644 index 2255d85..0000000 --- a/inventory/vagrant.ini +++ /dev/null @@ -1,2 +0,0 @@ -[pearl] -192.168.56.2 ansible_ssh_user=vagrant ansible_ssh_private_key_file='.vagrant/machines/pearl/virtualbox/private_key' diff --git a/inventory/vagrant.yml b/inventory/vagrant.yml new file mode 100644 index 0000000..9534c38 --- /dev/null +++ b/inventory/vagrant.yml @@ -0,0 +1,6 @@ +pearl: + hosts: + 192.168.56.2: + ansible_ssh_user: vagrant + ansible_ssh_private_key_file: '.vagrant/machines/pearl/virtualbox/private_key' + debian_version: 'trixie' diff --git a/plays/pearl.yml b/plays/pearl.yml index 5051ec3..675cf46 100644 --- a/plays/pearl.yml +++ b/plays/pearl.yml @@ -9,6 +9,10 @@ - hosts: pearl become: true roles: - # - 'any.common.enable-testing' + - 'any.common.debian-repositories' - 'any.common.debian-user' + - 'any.tools.default' + - 'any.tools.docker' + - 'any.tools.restic' + - 'any.tools.caddy' tags: base diff --git a/roles/any.common.enable-testing/tasks/main.yml b/roles/any.common.debian-repositories/tasks/main.yml similarity index 69% rename from roles/any.common.enable-testing/tasks/main.yml rename to roles/any.common.debian-repositories/tasks/main.yml index 2799dd1..90921c9 100644 --- a/roles/any.common.enable-testing/tasks/main.yml +++ b/roles/any.common.debian-repositories/tasks/main.yml @@ -1,17 +1,21 @@ --- -- ansible.builtin.copy: - src: 'sources.list' +- name: Update sources list + ansible.builtin.template: + src: 'sources.list.j2' dest: '/etc/apt/sources.list' owner: 'root' group: 'root' mode: '0644' + register: res - name: Upgrade all packages to the latest version in testing ansible.builtin.apt: upgrade: dist update_cache: yes cache_valid_time: 3600 + when: 'res.changed' - name: Clean up unused packages ansible.builtin.apt: autoremove: yes + when: 'res.changed' diff --git a/roles/any.common.debian-repositories/templates/sources.list.j2 b/roles/any.common.debian-repositories/templates/sources.list.j2 new file mode 100644 index 0000000..5f5933c --- /dev/null +++ b/roles/any.common.debian-repositories/templates/sources.list.j2 @@ -0,0 +1,10 @@ +deb http://deb.debian.org/debian/ {{ debian_version }} main non-free-firmware +deb-src http://deb.debian.org/debian/ {{ debian_version }} main non-free-firmware + +deb http://security.debian.org/debian-security {{ debian_version }}-security main non-free-firmware +deb-src http://security.debian.org/debian-security {{ debian_version }}-security main non-free-firmware + +# {{ debian_version }}-updates, to get updates before a point release is made; +# see https://www.debian.org/doc/manuals/debian-reference/ch02.en.html#_updates_and_backports +deb http://deb.debian.org/debian/ {{ debian_version }}-updates main non-free-firmware +deb-src http://deb.debian.org/debian/ {{ debian_version }}-updates main non-free-firmware diff --git a/roles/any.common.enable-testing/files/sources.list b/roles/any.common.enable-testing/files/sources.list deleted file mode 100644 index bdb2f58..0000000 --- a/roles/any.common.enable-testing/files/sources.list +++ /dev/null @@ -1,10 +0,0 @@ -deb http://deb.debian.org/debian/ trixie main non-free-firmware -deb-src http://deb.debian.org/debian/ trixie main non-free-firmware - -deb http://security.debian.org/debian-security trixie-security main non-free-firmware -deb-src http://security.debian.org/debian-security trixie-security main non-free-firmware - -# trixie-updates, to get updates before a point release is made; -# see https://www.debian.org/doc/manuals/debian-reference/ch02.en.html#_updates_and_backports -deb http://deb.debian.org/debian/ trixie-updates main non-free-firmware -deb-src http://deb.debian.org/debian/ trixie-updates main non-free-firmware diff --git a/roles/any.tools.caddy/tasks/main.yml b/roles/any.tools.caddy/tasks/main.yml index c40fa3a..f9a30e4 100644 --- a/roles/any.tools.caddy/tasks/main.yml +++ b/roles/any.tools.caddy/tasks/main.yml @@ -1,8 +1,9 @@ ---- - name: Add Caddy GPG key - apt_key: - url: "https://dl.cloudsmith.io/public/caddy/stable/gpg.key" - state: present + ansible.builtin.get_url: + url: 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' + dest: '/etc/apt/trusted.gpg.d/caddy.asc' + mode: '0644' + force: true - name: Add Caddy repositories apt_repository: diff --git a/roles/any.tools.default/tasks/main.yml b/roles/any.tools.default/tasks/main.yml new file mode 100644 index 0000000..1cdbcf5 --- /dev/null +++ b/roles/any.tools.default/tasks/main.yml @@ -0,0 +1,60 @@ +--- +- name: Ensure common packages are installed + apt: + name: + # Needed for handling GPG keys for repositories + - debian-keyring + - debian-archive-keyring + - apt-transport-https + - ca-certificates + - lsb-release + - gnupg + + # Easy to edit files + - vim + - tmux + - htop + + # Spam prevention + - fail2ban + + # Disk monitoring + - smartmontools + + # Periodic tasks + - cron + + # General compression tools + - bzip2 + - zip + + # Working with BTRFS file systems + - btrfs-progs + + - curl + state: present + +- name: Ensure cron service is enabled + service: + name: cron + state: started + enabled: true + +- name: Ensure fail2ban service is enabled + service: + name: fail2ban + state: started + enabled: true + +- name: Ensure Vim config is present + get_url: + url: 'https://r8r.be/vim' + dest: '{{ item.dest }}' + owner: "{{ item.user }}" + group: "{{ item.user }}" + mode: '644' + with_items: + - user: debian + dest: "/home/debian/.vimrc" + - user: root + dest: "/root/.vimrc" diff --git a/roles/any.tools.docker/tasks/main.yml b/roles/any.tools.docker/tasks/main.yml new file mode 100644 index 0000000..720554d --- /dev/null +++ b/roles/any.tools.docker/tasks/main.yml @@ -0,0 +1,44 @@ +--- +- name: Ensure older Docker versions aren't installed. + apt: + name: + - docker + - docker-engine + - docker.io + - containerd + - runc + state: absent + +- name: Add Docker GPG key. + ansible.builtin.get_url: + url: 'https://download.docker.com/linux/ubuntu/gpg' + dest: '/etc/apt/trusted.gpg.d/docker.asc' + mode: '0644' + force: true + +- name: Add Docker PPA. + ansible.builtin.apt_repository: + # https://gist.github.com/rbq/886587980894e98b23d0eee2a1d84933 + repo: "deb https://download.docker.com/{{ ansible_system | lower }}/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable" + state: present + +- name: Install Docker, docker-compose & cron. + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + state: present + +- name: Ensure Docker is running & enabled. + service: + name: docker + state: started + enabled: true + +- name: Add Docker prune cronjob. + cron: + name: Prune the Docker system. + hour: 4 + minute: 0 + job: docker system prune -af diff --git a/roles/any.tools.restic/files/restic_backups_passwd b/roles/any.tools.restic/files/restic_backups_passwd new file mode 100644 index 0000000..005fb46 --- /dev/null +++ b/roles/any.tools.restic/files/restic_backups_passwd @@ -0,0 +1,7 @@ +$ANSIBLE_VAULT;1.1;AES256 +33666438313237356564363136333933633035303531653464643766373434623834663736386463 +3464643731366237633334616536613864396162353264360a316130333032316437393333396466 +34356638393834316235633062646330336438376135346666663064303831666632353834663465 +6636663930356138640a323433613263393939303833616637336436366630386133386338613736 +34353433643539306238663638656539373731616238656635353561356632366332623532396465 +3936373534643966616131616161633234663430633233653435 diff --git a/roles/any.tools.restic/tasks/main.yml b/roles/any.tools.restic/tasks/main.yml new file mode 100644 index 0000000..f2c90d5 --- /dev/null +++ b/roles/any.tools.restic/tasks/main.yml @@ -0,0 +1,56 @@ +--- +- name: Ensure download directory is present + ansible.builtin.file: + path: "/opt/restic/{{ restic_version }}" + state: directory + mode: '0755' + +- name: Ensure compressed binary is downloaded + ansible.builtin.get_url: + url: "https://github.com/restic/restic/releases/download/v{{ restic_version }}/restic_{{ restic_version }}_linux_arm64.bz2" + dest: "/opt/restic/{{ restic_version }}/restic-{{ restic_version }}.bz2" + register: res + +- name: Ensure binary is decompressed + ansible.builtin.shell: + cmd: "bunzip2 -k /opt/restic/{{ restic_version }}/restic-{{ restic_version }}.bz2" + when: 'res.changed' + +- name: Ensure binary is copied to correct location + ansible.builtin.copy: + src: "/opt/restic/{{ restic_version }}/restic-{{ restic_version }}" + remote_src: true + dest: '/usr/local/bin/restic' + owner: 'root' + group: 'root' + mode: '0755' + when: 'res.changed' + +# - name: Ensure backup scripts directory is present +# ansible.builtin.file: +# path: '/etc/backups' +# state: directory +# mode: '0755' + +# - name: Ensure Restic backups password file is present +# ansible.builtin.copy: +# src: 'restic_backups_passwd' +# dest: '/etc/backups/restic_backups_passwd' +# owner: root +# group: root +# mode: '0600' + +# - name: Ensure backup-all script is present +# ansible.builtin.template: +# src: "backup-all.sh.j2" +# dest: '/etc/backups/backup-all.sh' +# owner: root +# group: root +# mode: '0644' + +# - name: Ensure backup cronjob is enabled +# ansible.builtin.cron: +# name: 'Perform nightly backups' +# minute: '0' +# hour: '2' +# job: '/usr/bin/bash /etc/backups/backup-all.sh' diff --git a/roles/any.tools.restic/templates/backup-all.sh.j2 b/roles/any.tools.restic/templates/backup-all.sh.j2 new file mode 100644 index 0000000..6e78783 --- /dev/null +++ b/roles/any.tools.restic/templates/backup-all.sh.j2 @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# This script sequentially executes all shell scripts matching +# /etc/backups/*.backup.sh, with environment variables configured to publish +# backups to the local Restic REST server. + +# Get passed along to subcalls to bash +export RESTIC_REPOSITORY='rest:http://{{ groups['nas'][0] }}:8000/backups' +export RESTIC_PASSWORD_FILE='/etc/backups/restic_backups_passwd' + +log_file='/tmp/backup-all.sh.log' + +rm -f "$log_file" + +for script in $(find /etc/backups -name '*.backup.sh'); do + T="$(date +%s)" + + /usr/bin/bash "$script" + + res="$?" + T="$(($(date +%s)-T))" + + if [[ $res == 0 ]]; then + header='OK' + else + header="FAIL ($res)" + fi + + printf \ + "%s: %s in %02dh%02dm%02ds\n" \ + "$(basename "$script")" "$header" \ + "$((T/3600%24))" "$((T/60%60))" "$((T%60))" \ + >> "$log_file" +done + +# Prune older backups +/usr/local/bin/restic forget --keep-last 7 && \ + /usr/local/bin/restic prune + +# Send status notification +ntfy publish \ + --title "Backups ($(hostname))" \ + homelab "$(cat "$log_file")"