Skip to content

Instantly share code, notes, and snippets.

@JohnnyJayJay
Created April 27, 2023 10:51
Show Gist options
  • Save JohnnyJayJay/5505ddd9d25ec36c68cc11725dfd132b to your computer and use it in GitHub Desktop.
Save JohnnyJayJay/5505ddd9d25ec36c68cc11725dfd132b to your computer and use it in GitHub Desktop.
Babashka script for daily backups using borg, polkit and zenity
#!/usr/bin/env bb
(ns borg-backup
(:require [babashka.process :as p :refer [shell process sh]]
[clojure.string :as str]
[clojure.java.io :as io]))
(def keep
{:daily 5
:weekly 3
:monthly 2})
(def home (System/getenv "HOME"))
(def borg-rsh (str "ssh -i " home "/.ssh/id_backup"))
(def passphrase-file (str home "/.borg/passphrase.txt.gpg"))
(def repo-base "ssh://[email protected]/home/borg/laptop")
(def repos
{:suffix "/root"
:roots ["/"]
:exclusions
["tmp" "dev" "proc" "sys" "run" "var/tmp" "var/cache"]}
{:suffix "/home"
:roots ["/home"]
:exclusions
["home/*/.config/chromium/Default/Service Worker/CacheStorage"
"home/**/cache/"
"home/*/.m2/repository"
"home/*/.sdkman/tmp"
"home/*/.emacs.d/.local/straight"
"home/*/.config/discord/*Cache"
"home/**/.cache/"
"home/*/.npm"
"home/*/.steam"
"home/*/.local/share/Steam"
"home/**/node_modules"]})
(defn cancel-backup? []
(-> (shell
{:continue true}
"zenity --question --title Backup --text"
"Backup imminent. Perform full system backup now?")
:exit
(= 1)))
(defn create-backup [env exclusions roots]
(apply
process
{:out :inherit}
(concat
["pkexec" "env"]
(map (fn [[k v]] (str k \= v)) env)
["borg" "create" "::{hostname}-{now}"]
roots
["-v" "--list" "--filter" "AME" "-x" "--exclude-caches"]
(mapcat vector (repeat "--exclude") exclusions))))
(defn prune-repo [env]
(apply
process
{:extra-env env
:out :inherit}
"borg prune --list -v"
(mapcat (fn [[k v]] [(str "--keep-" (name k)) (str v)]) keep)))
(defn compact-repo [env]
(process
{:extra-env env
:out :inherit}
"borg compact -v"))
(defn log [progress-in line]
(println line)
(.write progress-in (str \# line))
(.newLine progress-in)
(.flush progress-in))
(defn percentage-update [progress-in percentage]
(.write progress-in (str (int percentage)))
(.newLine progress-in)
(.flush progress-in))
(def progress-step (double (/ 100 (* 3 (count repos)))))
;; Script
(def args *command-line-args*)
(def no-script (= (first args) "--"))
(when (and (not no-script) (cancel-backup?))
(println "Backup cancelled.")
(System/exit 1))
(println "Decrypting passphrase file")
(def borg-passphrase
(-> (shell {:out :string} "gpg --decrypt" passphrase-file) :out str/trim))
(def env
{"BORG_RSH" borg-rsh
"BORG_PASSPHRASE" borg-passphrase})
;; run regular borg command in context
(when no-script
(-> shell
(apply
{:extra-env env :continue true}
(map #(str/replace % "%repo%" repo-base) (rest args)))
:exit
System/exit))
(let [repos (cond->> repos (seq args) (filter (comp (set args) :suffix)))
current-process (volatile! nil)
{:keys [in]}
(process
{:shutdown (fn [{:keys [exit]}]
(when-not (and exit (zero? exit))
(p/destroy @current-process)))}
"zenity --title 'Creating backups' --progress --auto-close")
progress (volatile! 0)
exit-codes (volatile! [])]
(with-open [in (io/writer in)]
(doseq [{:keys [suffix exclusions roots]} repos
:let [env (assoc env "BORG_REPO" (str repo-base suffix))]
[task-name task]
(map vector
["Create Backup" "Prune Repo" "Compact Repo"]
[#(create-backup env exclusions roots) #(prune-repo env) #(compact-repo env)])
:let [{:keys [err] :as proc} (task)]]
(vreset! current-process proc)
(println "Repo" suffix ":" task-name)
(with-open [out (io/reader err)]
(run! (partial log in) (line-seq out))
(vswap! exit-codes conj {:repo suffix :task task-name :code (:exit @proc)})
(vswap! progress + progress-step)
(percentage-update in @progress)))
(percentage-update in 100))
(let [max-code (reduce max (map :code @exit-codes))
status (case max-code
0 :ok
1 :warnings
:errors)
messages (->> @exit-codes
(remove (comp zero? :code))
(map (fn [{:keys [task repo code]}]
(str "Task '" task "' for repo '"
repo "' finished with "
(if (= 1 code) "warnings" "errors") \.)))
(str/join "\n"))]
(shell
{:continue true}
"zenity"
(case status :ok "--info" :warnings "--warning" :errors "--error")
"--text"
(if (= status :ok) "Backup complete." messages))
(System/exit max-code)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment