From d7dec88cf6ee78d25639d37fcc7bc5498b9741f5 Mon Sep 17 00:00:00 2001 From: Elia Farin Date: Tue, 16 Dec 2025 16:15:29 -0600 Subject: [PATCH] Created basic playbook skeleton and setup scripts --- .gitignore | 3 +- client.yml | 9 +++ group_vars/client.yml | 20 +++++ group_vars/secret.yml.example | 3 + group_vars/server.yml | 6 ++ install.sh | 126 +++++++++++++++++++++++++++++ inventory | 3 + roles/client/files/backup.sh | 78 ++++++++++++++++++ roles/client/files/backupenv | 2 + roles/client/tasks/main.yml | 51 ++++++++++++ roles/client/templates/borg_env.j2 | 3 + roles/server/files/.gitignore | 1 + roles/server/tasks/sshd.yml | 7 ++ roles/server/tasks/user.yml | 13 +++ server.yml | 7 ++ 15 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 client.yml create mode 100644 group_vars/client.yml create mode 100644 group_vars/secret.yml.example create mode 100644 group_vars/server.yml create mode 100644 install.sh create mode 100644 inventory create mode 100644 roles/client/files/backup.sh create mode 100644 roles/client/files/backupenv create mode 100644 roles/client/tasks/main.yml create mode 100644 roles/client/templates/borg_env.j2 create mode 100644 roles/server/files/.gitignore create mode 100644 roles/server/tasks/sshd.yml create mode 100644 roles/server/tasks/user.yml create mode 100644 server.yml diff --git a/.gitignore b/.gitignore index 5c199eb..4ca1738 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ # ---> Ansible *.retry - +group_vars/secret.yml +**/authorized_keys diff --git a/client.yml b/client.yml new file mode 100644 index 0000000..72b67b8 --- /dev/null +++ b/client.yml @@ -0,0 +1,9 @@ +--- + +- hosts: client + remote_user: root + vars_files: + - group_vars/client.yml + - group_vars/secret.yml + roles: + - client diff --git a/group_vars/client.yml b/group_vars/client.yml new file mode 100644 index 0000000..5218ab4 --- /dev/null +++ b/group_vars/client.yml @@ -0,0 +1,20 @@ +--- +os: + family: "FAMILY" + version: "VERSION" + +backup: + repo: "REPO" + encrypt: "ENCRYPT" + allocate_space: "yes" + additional_space: "50" + compression: "COMPRESSION" + +tailscale: + install: "false" + enable: "true" + +install_dirs: + - { path: "/opt/backup", mode: "0755" } + - { path: "/opt/backup/bin", mode: "0755" } + - { path: "/opt/backup/etc", mode: "0700" } diff --git a/group_vars/secret.yml.example b/group_vars/secret.yml.example new file mode 100644 index 0000000..7daf9c3 --- /dev/null +++ b/group_vars/secret.yml.example @@ -0,0 +1,3 @@ +--- +backup: + passphrase: "PASSPHRASE" diff --git a/group_vars/server.yml b/group_vars/server.yml new file mode 100644 index 0000000..e6e608d --- /dev/null +++ b/group_vars/server.yml @@ -0,0 +1,6 @@ +--- +user: backup +password_locked: true +install_key: true + + diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..0c8be01 --- /dev/null +++ b/install.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +setup_encryption() { + + sed -i 's/ENCRYPT/true/' group_vars/client.yml + pass_reprompt="1" + while [ pass_reprompt == 1 ]; do + printf 'Please enter a passphrase to use for your backup\n Password: ' + read -s password1 + printf '\nVerify Password: ' + read -s password2 + if [ "$password1" -eq "$password2" ]; then + pass_reprompt=0 + else + printf 'Passwords do not match. Please try again\n' + fi + done + + SECRET="---\\nbackup:\\n passphrase: '$password'\\n" + printf '%s' "$SECRET" | tee group_vars/secret.yml + +} + +setup_ssh() { + printf 'What is the IP or hostname of your remote backup?\n' + cont=0 + while [ $cont != 1 ]; do + read -p "IP/hostname: " address + printf '\nIs %s correct? ' "$address" + read -p "[y/N]: " ans + case "$ans" in + [yY].*) cont=1 + ;; + *) cont=0 + ;; + esac + done + + cont=0 + printf 'What is the backup location?\n' + while [ "$cont" != 1 ]; do + read -p "Absolute path: " path + printf '\nIs %s correct? ' "$path" + read -p "[y/N]: " ans + case "$ans" in + [yY].*) cont=1 + ;; + *) cont=0 + esac + done + + printf 's/REPO/ssh://backup@%s:%s/\n' "$address" "$path" > /tmp/backupssh.sed + + sed -i -s /tmp/backupssh.sed group_vars/client.yml + rm /tmp/backupssh.sed + +} + + +setup_remote() { + case "$1" in + "ssh") setup_ssh + ;; + "local") setup_local + ;; + *) + printf 'Error in setup_remote()\n' + printf 'Case %s not understood\n' "$1" + exit 1 + ;; + esac + + +setup_environment() { + printf 'Setting up environment...\n' + printf 'What is your backup location?\n' + printf '0: cancel\n' + printf '1: ssh\n' + printf '2: local\n' + read -p "(0-2)" ans + case "$ans" in + "0") printf 'Cancelling...\n' && exit + ;; + "1") setup_remote "ssh" + ;; + "2") setup_remote "local" + ;; + *) printf 'Answer not understood. Please rerun this script' && exit + ;; + esac + + read -p "Would you like to set up encryption? [y/N]" ans + case "$ans" in + [yY].*) setup_encryption + ;; + *) sed -i 's/ENCRYPT/false/' group_vars/client.yml + ;; + esac + + printf 'Would you like to set up a "reserve" file?\n' + printf 'This file will be an empty file 50GB in size\n' + printf 'that you can delete to free up space should you run out.\n\n' + printf 'This is highly recommended\n' + + read -p "Setup Reserved Space [Y/n]: " reserved + case "$reserved" in + [nN].*) sed -i 's/ALLOCATE/true/' group_vars/client.yml + ;; + *) sed -i 's/ALLOCATE/false/' group_vars/client.yml + ;; + esac + + + +printf 'The setup will now install required packages\n' +printf 'You will be prompted for your sudo password\n' + +sudo dnf install -y ansible ansible-playbook + +read -p "Would you like to set up the environment files?: [Y/n]: " ans + +case $ans in + [yY].*) setup_environment + ;; + *) printf 'No environment set up. You will need to do this manually\n' + ;; +esac diff --git a/inventory b/inventory new file mode 100644 index 0000000..aaf0b56 --- /dev/null +++ b/inventory @@ -0,0 +1,3 @@ +[install] + +localhost diff --git a/roles/client/files/backup.sh b/roles/client/files/backup.sh new file mode 100644 index 0000000..9a623c4 --- /dev/null +++ b/roles/client/files/backup.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +# Setting this, so the repo does not need to be given on the commandline: +# Example: +# ssh://username@example.com:2022/~/backup/main +if [ "$EUID" != "0" ]; then + printf "%s must be run as root\n" "$0" + exit 1 +fi + +source /opt/backup/etc/borg_env +# some helpers and error handling: +info() { printf "\n%s %s\n\n" "$( date )" "$*" >&2; } +trap 'echo $( date ) Backup interrupted >&2; exit 2' INT TERM + +info "Starting backup" + +# Backup the most important directories into an archive named after +# the machine this script is currently running on: + +borg create \ + --verbose \ + --filter AME \ + --list \ + --stats \ + --show-rc \ + --compression $BORG_COMPRESSION \ + --exclude-caches \ + --exclude 'home/*/.cache/*' \ + --exclude 'var/tmp/*' \ + \ + ::'{hostname}-{now}' \ + /etc \ + /home \ + /root \ + /var + +backup_exit=$? + +info "Pruning repository" + +# Use the `prune` subcommand to maintain 7 daily, 4 weekly and 6 monthly +# archives of THIS machine. The '{hostname}-*' matching is very important to +# limit prune's operation to this machine's archives and not apply to +# other machines' archives also: + +borg prune \ + --list \ + --glob-archives '{hostname}-*' \ + --show-rc \ + --keep-daily 7 \ + --keep-weekly 4 \ + --keep-monthly 6 + +prune_exit=$? + +# actually free repo disk space by compacting segments + +info "Compacting repository" + +borg compact + +compact_exit=$? + +# use highest exit code as global exit code +global_exit=$(( backup_exit > prune_exit ? backup_exit : prune_exit )) +global_exit=$(( compact_exit > global_exit ? compact_exit : global_exit )) + +if [ ${global_exit} -eq 0 ]; then + info "Backup, Prune, and Compact finished successfully" +elif [ ${global_exit} -eq 1 ]; then + info "Backup, Prune, and/or Compact finished with warnings" +else + info "Backup, Prune, and/or Compact finished with errors" +fi + +exit ${global_exit} + diff --git a/roles/client/files/backupenv b/roles/client/files/backupenv new file mode 100644 index 0000000..0757f14 --- /dev/null +++ b/roles/client/files/backupenv @@ -0,0 +1,2 @@ +export PATH="$PATH:/opt/backup/bin" + diff --git a/roles/client/tasks/main.yml b/roles/client/tasks/main.yml new file mode 100644 index 0000000..9174555 --- /dev/null +++ b/roles/client/tasks/main.yml @@ -0,0 +1,51 @@ +--- + +- name: Update system + ansible.builtin.dnf5: + name: "*" + state: latest + + +- name: Install borgbackup + ansible.builtin.dnf5: + name: borgbackup + state: present + +- name: Create directory structure + ansible.builtin.file: + path: "{{ item.path }}" + owner: root + group: root + mode: "{{ item.mode }}" + state: directory + loop: "{{ install_dirs }}" + +- name: Install backup script + ansible.builtin.file: + path: /opt/backup/bin/backup.sh + owner: root + group: root + mode: 0755 + +- name: Install environment for path + ansible.builtin.command: + cmd: "echo 'source /etc/backupenv' >> /etc/environment && touch /opt/backup/etc/environment_created" + creates: /opt/backup/etc/environment_created + +- name: Install path environment file + ansible.builtin.file: + path: /etc/backupenv + owner: root + group: root + mode: 0644 + +- name: Install environment file + ansible.builtin.template: + src: borg_env.j2 + dest: /opt/backup/etc/borg_env + backup: yes + owner: root + group: root + mode: 0600 + + diff --git a/roles/client/templates/borg_env.j2 b/roles/client/templates/borg_env.j2 new file mode 100644 index 0000000..b5af529 --- /dev/null +++ b/roles/client/templates/borg_env.j2 @@ -0,0 +1,3 @@ +export BORG_COMPRESSION="{{ backup.compression }}" +export BORG_REPO="{{ backup.repo }}" +export BORG_PASSPHRASE='{{ backup.passphrase }}' diff --git a/roles/server/files/.gitignore b/roles/server/files/.gitignore new file mode 100644 index 0000000..05b023b --- /dev/null +++ b/roles/server/files/.gitignore @@ -0,0 +1 @@ +authorized_keys diff --git a/roles/server/tasks/sshd.yml b/roles/server/tasks/sshd.yml new file mode 100644 index 0000000..71572cc --- /dev/null +++ b/roles/server/tasks/sshd.yml @@ -0,0 +1,7 @@ +--- + +- name: Enable sshd service + ansible.builtin.systemd_service: + name: sshd + enabled: true + state: started diff --git a/roles/server/tasks/user.yml b/roles/server/tasks/user.yml new file mode 100644 index 0000000..d9a279e --- /dev/null +++ b/roles/server/tasks/user.yml @@ -0,0 +1,13 @@ +--- +- name: Create backup user + ansible.builtin.user: + name: "{{ user }}" + password_lock: "{{ password_locked }}" + +- name: Install authorized keys file + ansible.builtin.file: + path: "/home/{{ user }}/.ssh/authorized_keys" + owner: "{{ user }}" + group: "{{ user }}" + mode: "0600" + backup: true diff --git a/server.yml b/server.yml new file mode 100644 index 0000000..921d4ad --- /dev/null +++ b/server.yml @@ -0,0 +1,7 @@ +--- +- hosts: server + remote_user: root + vars_files: + - group_vars/server.yml + roles: + - server