commit 5f65d4b756443e0930f72c5e471a548c7951198c Author: shmick Date: Sat Nov 18 13:16:57 2023 +0200 Initial Commit - Kumonoboru with working Actions diff --git a/.gitea/workflows/kumonoboru.yaml b/.gitea/workflows/kumonoboru.yaml new file mode 100644 index 0000000..fd01f35 --- /dev/null +++ b/.gitea/workflows/kumonoboru.yaml @@ -0,0 +1,31 @@ +name: Configure Kumonoboru + +on: + push: + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up Python + run: | + apt -y update + apt -y install python3 python3-pip skopeo + + - name: Install Ansible + run: | + python3 -m pip install --upgrade pip + pip install ansible + + - name: Set up SSH + uses: webfactory/ssh-agent@v0.5.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Run Ansible Playbook + run: | + ansible-playbook -i inventory.yaml -e 'ANSIBLE_SUDO_PASS="${{ secrets.SUDO_PASS }}"' kumonoboru.yaml -vv diff --git a/inventory.yaml b/inventory.yaml new file mode 100644 index 0000000..db5f513 --- /dev/null +++ b/inventory.yaml @@ -0,0 +1,5 @@ +all: + hosts: + takahe: + ansible_host: 192.168.0.66 + ansible_user: shmick diff --git a/kumonoboru.service.j2 b/kumonoboru.service.j2 new file mode 100644 index 0000000..3da69fe --- /dev/null +++ b/kumonoboru.service.j2 @@ -0,0 +1,8 @@ +[Unit] +Description=Kumonoboru - cloud backup utility + +[Service] +ExecStart=/bin/bash /usr/local/bin/kumonoboru.sh + +[Install] +WantedBy=multi-user.target diff --git a/kumonoboru.sh b/kumonoboru.sh new file mode 100755 index 0000000..edfacf9 --- /dev/null +++ b/kumonoboru.sh @@ -0,0 +1,185 @@ +#!/bin/bash +##Simple script to run Restic backups + +help() +{ + echo "Kumonoboru - Back up important location to the B2 cloud using Restic." + echo " {-c|--clean} -- Force prune of the remote repositories" + echo " {-r|--repository} repository -- Only backup the specified repository." + echo " {-l|--limit} #[Kbps] -- Limit upload & download speed" + echo " {-v|--verbose} -- Print debug messages" + echo " {-h|--help} -- Print this help message and exit" + echo "Available repositories:" + echo "Gerbil-TK Photos (path: /var/Red-Vol/Media/Pictures)" + echo "Pukeko-XYZ-Containers Containers (path: /var/Red-Vol/Media/Containers)" + echo "Pukeko-XYZ-Cloud Data from all devices (path: /var/Red-Vol/Media/Cloud)" + exit 0 +} +#Pass arguments to the script +flags() +{ + #This is utterly useless + if [[ $# == "0" ]]; then + : + fi + while test $# -gt 0 + do + case "$1" in + (-c|--clean) + export CLEAN="1" + shift;; + (-r|--repository) + shift + export REPOSITORY="$1" + shift;; + (-l|--limit) + shift + export BWLIMIT="$1" + shift;; + (-h|--help) + help;; + (*) help;; + esac + done +} +flags "$@" + +#Defaults +if [[ -z $BWLIMIT ]]; then + export BWLIMIT="0" +else + echo -e "Bandwidth will be limited to" "$BWLIMIT Kbps" +fi +if [[ -n $CLEAN ]]; then + echo -e "Cleaning will take place per request." +fi +if [[ -n $REPOSITORY ]]; then + echo -e "Will only process repository" "$1" +fi + +export B2_ACCOUNT_ID=8582a42a3b99 #Master Key +export B2_ACCOUNT_KEY=00041845e8dd29d7e3d091d77bb8a631ee71332be7 #Master Application ID +RESTIC_PASSWORD='f$774$#je4%U8vp8ov*UsZMHqL$m3Smh#fEbbt7hyULQxfnnWmSiS5MEndzVWT$$n^@s$P*o4vV*^rgv3jvvrv@y35VppU$$y*vnG5V@botU&4$39Y6t9HSb3Z548M!4' + + +#Safety function; accepts repository to check +safety(){ + REPOSITORY="$1" + echo -e "Checking if repository is in use - " "$REPOSITORY" + #Check no other Restic process is using this repository; Free unnecessary locks, if present + if [[ -n $(ps aux | grep restic | grep "$REPOSITORY") ]]; then + echo -e "Repository is in use - ignoring" + return 1 +# ^ If there's a restic process holding the repository, leave it alone. + else + echo -e "Repository is not in use - unlocking" + restic -q -r b2:$REPOSITORY unlock +# ^ If a lock exists but no process, the repository is safe and should be unlocked. + fi +} + +#Backup function; accepts repository and path to backup +backup(){ + REPOSITORY="$1" + REPOSITORY_PATH="$2" + if safety "$REPOSITORY"; then + #Run the backup + echo -e "Backing up repository" "$REPOSITORY" + if restic --cache-dir="$RESTIC_CACHE_DIR" -r b2:"$REPOSITORY" backup "$REPOSITORY_PATH" --limit-upload="$BWLIMIT" --limit-download="$BWLIMIT"; then + echo -e "$REPOSITORY_PATH" "completed upload to $REPOSITORY." + else + echo -e "$REPOSITORY failed to upload path" "$REPOSITORY_PATH" + fi + fi +} + +check(){ + REPOSITORY="$1" + PRUNE="$2" + echo -e "Checking integrity (prune: $PRUNE) of repository" "$REPOSITORY" +## ^ This variable will have value if repo is already clean, indicating +#+ This is a post backup check. + if [[ -n $PRUNE ]]; then + echo -e "This repository has been cleaned already; will not clean again." + fi + if safety "$REPOSITORY"; then + echo -e "Checking repository health - " "$REPOSITORY" + if restic -r b2:"$REPOSITORY" check --limit-upload="$BWLIMIT" --limit-download="$BWLIMIT"; then + echo -e "Repository passed integrity check - " "$REPOSITORY" + echo -e "Current snapshots:" + restic -r b2:"$REPOSITORY" snapshots | tee -a $LOG + else + echo -e "Repository failed integrity check - " "$REPOSITORY" + fi + fi +} + +clean(){ + REPOSITORY="$1" + if safety "$REPOSITORY"; then + echo -e "Cleaning repository" "$REPOSITORY" + if restic -r b2:$REPOSITORY forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune --limit-upload="$BWLIMIT" --limit-download="$BWLIMIT"; then + echo -e "Repository is trim - " "$REPOSITORY" + echo -e "Running post clean check..." + check "$REPOSITORY" "1" +# Marks repository as cleaned already ^ so it won't passed to this function again. + else + echo -e "Failed to prune repository" "$REPOSITORY" + fi + fi +} + +#If cleaning was forced, or if it's the first of this month - clean. +if [[ -n $CLEAN ]] || [[ $(date +%d) == "1" ]]; then + check Gerbil-TK + clean Gerbil-TK + check Pukeko-XYZ-Containers + clean Pukeko-XYZ-Containers + check Pukeko-XYZ-Cloud + clean Pukeko-XYZ-Cloud + +#If a specific repository was requested, back it up; otherwise, back them all up. +elif [[ -n $REPOSITORY ]] && [[ -z $CLEAN ]]; then + case "$REPOSITORY" in + (Gerbil-TK) + backup Gerbil-TK /var/Red-Vol/Media/Pictures/ + ;; + (Pukeko-XYZ-Containers) + backup Pukeko-XYZ-Containers /var/Red-Vol/Media/Containers/ + ;; + (Pukeko-XYZ-Cloud) + backup Pukeko-XYZ-Cloud /var/Red-Vol/Media/Cloud/ + ;; + (*) + help;; + esac + +#If cleaning was not forced, backup the repositories +elif [[ -z $CLEAN ]]; then + backup Gerbil-TK /var/Red-Vol/Media/Pictures/ + backup Pukeko-XYZ-Containers /var/Red-Vol/Media/Containers/ + backup Pukeko-XYZ-Cloud /var/Red-Vol/Media/Cloud/ + +#If a specific repository was requested to be cleaned, clean it +elif [[ -n $REPOSITORY ]] && [[ -n $CLEAN ]]; then + case "$REPOSITORY" in + (Gerbil-TK) + check Gerbil-TK + clean Gerbil-TK + ;; + (Pukeko-XYZ-Containers) + check Pukeko-XYZ-Containers + clean Pukeko-XYZ-Containers + ;; + (Pukeko-XYZ-Cloud) + check Pukeko-XYZ-Cloud + clean Pukeko-XYZ-Cloud + ;; + (*) + help;; + esac +fi + +#Wrap up this run's log and report nicely +echo "All done; have a nice day!" +exit 0 diff --git a/kumonoboru.timer.j2 b/kumonoboru.timer.j2 new file mode 100644 index 0000000..36c5286 --- /dev/null +++ b/kumonoboru.timer.j2 @@ -0,0 +1,9 @@ +[Unit] +Description=Kumonoboru - cloud backup utility + +[Timer] +OnCalendar=daily +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/kumonoboru.yaml b/kumonoboru.yaml new file mode 100644 index 0000000..b5f3143 --- /dev/null +++ b/kumonoboru.yaml @@ -0,0 +1,32 @@ +- hosts: takahe + gather_facts: no + become: yes + vars: + ansible_ssh_common_args: '-o StrictHostKeyChecking=no' + ansible_sudo_pass: "{{ ANSIBLE_SUDO_PASS }}" + + tasks: + - name: Install Restic + ansible.builtin.package: + name: restic + state: latest + + - name: Install kumonoboru + ansible.builtin.copy: + src: kumonoboru.sh + dest: /usr/local/bin/kumonoboru.sh + mode: 'a+x' + + - name: Template service and timer to host + ansible.builtin.template: + src: "{{ item }}.j2" + dest: /etc/systemd/system/{{ item }} + with_items: + - kumonoboru.service + - kumonoboru.timer + + - name: Start kumonoboru timer + ansible.builtin.systemd: + name: kumonoboru.timer + state: started + daemon_reload: true