#!/usr/bin/env bb ;; Usage: bb backup.clj ;; ;; 备份脚本功能: ;; 1. 备份所有 Docker volumes 到 /home/eltrac/Backup ;; 2. 备份 Akkoma 数据库到 akkoma_full_backup.sql ;; 3. 使用 restic 备份 /home 和 /etc 到 contabo-backup 仓库 ;; ;; 说明: ;; 1. forgejo 等应用使用挂载到 Home 目录子目录的 SQLite 数据库,所以直接备份整个 /home 目录就好 ;; 2. 由于 docker volumes 已经单独备份,所以不必备份 /var ;; 3. 目前暂时不能自动化,因为 restic 备份需要输入 repository 密码 (require '[babashka.tasks :refer [shell]]) (require '[babashka.fs :as fs]) (require '[clojure.string :as str]) (def backup-path "/home/eltrac/Backup") (def timestamp (java.time.LocalDateTime/now)) (def log-file (str backup-path "/backup-" (.format java.time.format.DateTimeFormatter/ISO_LOCAL_DATE timestamp) ".log")) (defn log-msg [msg] (let [timestamp (.format (java.time.LocalDateTime/now) java.time.format.DateTimeFormatter/ISO_LOCAL_DATE_TIME)] (println (str "[" timestamp "] " msg)))) (defn ensure-backup-dir [] (log-msg (str "确保备份目录存在: " backup-path)) (try (fs/create-dirs backup-path) (log-msg "✓ 备份目录已创建/已存在") (catch Exception e (log-msg (str "⚠ 无法创建备份目录: " (.getMessage e))) (log-msg "⚠ 请手动创建目录或检查权限")))) (defn backup-docker-volumes [] (log-msg "开始备份 Docker volumes...") (let [output (:out (shell {:out :string} "docker" "volume" "ls" "--format" "{{.Name}}")) ;; filter out anonymous volumes based on length volumes (filter #(< (count %) 60) (str/split-lines output))] (log-msg (str "找到 " (count volumes) " 个 volumes")) (doseq [volume volumes] (log-msg (str "备份 volume: " volume)) (let [backup-file (str backup-path "/docker-volumes/" volume "-backup.tar.gz")] (try (shell "docker" "run" "--rm" "-v" (str volume ":/data") "-v" (str backup-path ":/backup") "alpine" "tar" "czf" (str "/backup/" volume "-backup.tar.gz") "-C" "/data" ".") (log-msg (str "✓ 成功备份 volume: " volume " 到 " backup-file)) (catch Exception e (log-msg (str "✗ 备份 volume " volume " 失败: " (.getMessage e))))))))) (defn backup-akkoma-database [] (log-msg "开始备份 Akkoma 数据库...") (let [backup-file (str backup-path "/akkoma_full_backup.sql")] (try (shell {:out backup-file} "docker" "exec" "akkoma-db-1" "pg_dumpall" "-U" "akkoma") (log-msg (str "✓ 成功备份 Akkoma 数据库到 " backup-file)) (catch Exception e (log-msg (str "✗ 备份 Akkoma 数据库失败: " (.getMessage e))))))) (defn backup-with-restic [] (log-msg "开始使用 restic 备份 /home 和 /etc 目录...") (try ;; 备份 /home 目录 (log-msg "备份 /home 目录...") (shell "restic" "--repo" "rclone:contabo-backup:" "backup" "/home" "--password-file" (str backup-path "/password")) (log-msg "✓ 成功备份 /home 目录") ;; 备份 /etc 目录 (log-msg "备份 /etc 目录...") (shell "restic" "--repo" "rclone:contabo-backup:" "backup" "/etc" "--password-file" (str backup-path "/password")) (log-msg "✓ 成功备份 /etc 目录") (catch Exception e (log-msg (str "✗ restic 备份失败: " (.getMessage e)))))) (defn -main [] (log-msg "=== 开始备份流程 ===") (ensure-backup-dir) (log-msg "1. 备份 Docker volumes") (backup-docker-volumes) (log-msg "2. 备份 Akkoma 数据库") (backup-akkoma-database) (log-msg "3. 使用 restic 备份系统目录") (backup-with-restic) (log-msg "=== 备份完成 ===")) ;; 当脚本直接运行时执行主函数 (when (= *file* (System/getProperty "babashka.file")) (-main))