#!/usr/bin/env bb ;;; TODO: option to either move OR copy (require '[clojure.core.match :refer [match]] '[babashka.cli :as cli] '[babashka.process :refer [$ check] :as p]) (defn get-archive-path [] (fs/path "/persist/previous")) (defn get-files [] (let [diff (:out (check ($ {:out :string} "zfs" "diff" "-HF" "rpool/local/home@blank" "rpool/local/home")))] ;; See zfs-diff(8) to understand what we're parsing here. (->> diff str/split-lines (map #(str/split % #"\s")) (filter #(and ;; We only care to preserve /new/ content. (contains? #{"+" "M"} (first %)) ;; We only bother with plain old files. No directories, ;; symlinks, etc. (= (second %) "F"))) (map #(nth % 2))))) (defn move-out-of-my-way [file] ;; No TCO. }:< (loop [n 0] (let [file' (format "%s-%d" file n)] (if (fs/exists? file') (recur (inc n)) (do (fs/move file file') file'))))) (defn archive-files [archive-path files] (let [new-archive (fs/path archive-path "home/new-archive")] (when (fs/exists? new-archive) (println "Warning: `new-archive' already exists... we'll rename it for you?") (move-out-of-my-way new-archive)) (doseq [file files] (let [destination (fs/path new-archive (fs/relativize "/home" (fs/parent file)))] (fs/create-dirs destination) (fs/move file destination))))) (defn cycle-archives [archive-path n] "Delete the oldest archive path, and increment each previous path by one. More precisely, - Delete the archive path labeled `n` (the oldest allowed). - For each remaining path labeled 'i', relabel to 'i + 1'. - Lastly, we delete the path labeled `new-archive`, if it exists." (let [gp #(fs/path archive-path "home" (str %))] (fs/delete-if-exists (gp n)) (doseq [i (range (dec n) 1 -1)] (when (fs/exists? (gp (dec i))) (fs/move (gp (dec i)) (gp i)))) (when (fs/exists? (gp "new-archive")) (fs/move (gp "new-archive") (gp 1))))) (defn do-rollback [] (let [proc (deref ($ "zfs" "rollback" "-r" "rpool/local/home@blank"))] (if (= (:exit proc) 0) (println (str "Successfully rolled back /home. " "Enjoy the fresh filesystem smell! }:D")) (println "Something went wrong rolling back /home... D:{")))) (defn -main [] (let [n (if (< 0 (count *command-line-args*)) (parse-long (first *command-line-args*)) 3)] (binding [p/*defaults* {:pre-start-fn #(println (str "+ " (str/join (:cmd %))))}] (let [archive-path (get-archive-path) files (get-files)] (archive-files archive-path files) (cycle-archives archive-path n) (do-rollback))))) (-main)