summaryrefslogtreecommitdiff
path: root/vps-backup
diff options
context:
space:
mode:
authoreltrac <hi@eltr.ac>2026-04-13 16:30:09 +0800
committereltrac <hi@eltr.ac>2026-04-13 16:30:09 +0800
commit325c5c8676491f2a21eb05d7602e5876e209e27e (patch)
tree962766a582b3e7fb620a3facc78eb02d6a72f58f /vps-backup
parentfacd5a84f298292c4eb5676c5bea6b4677782427 (diff)
vps backup script
Diffstat (limited to 'vps-backup')
-rwxr-xr-xvps-backup/backup.clj101
1 files changed, 101 insertions, 0 deletions
diff --git a/vps-backup/backup.clj b/vps-backup/backup.clj
new file mode 100755
index 0000000..b8ac549
--- /dev/null
+++ b/vps-backup/backup.clj
@@ -0,0 +1,101 @@
+#!/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))