summaryrefslogtreecommitdiff
path: root/vps-backup/backup.clj
blob: b8ac549962ef108d28d9c500b4d384692ea2fd99 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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))