VPS 挂掉那天才想起没备份:写一份 Python 自动备份脚本

VPS 挂掉那天才想起没备份:写一份 Python 自动备份脚本

先给第一次来的读者:这篇解决什么问题

VPS 是你租在机房里的一台"小电脑",按月或按年付费,上面可能跑着博客、代理服务和各种脚本。但这台机器是"无担保"的:硬盘会坏、小主机商可能跑路、你自己手滑也可能删错东西。主机商的快照(snapshot)要么收费、要么只能恢复到同一家——真出事那天,唯一靠得住的是你自己手里的一份备份。

这篇给你一个实测过的 Python 脚本:每天凌晨自动把你指定的关键目录打包成 tar.gz,只保留最近 7 份,还能顺手推送到另一台机器。需要的前置知识只有"会用 SSH 连服务器、会粘贴命令"。代码里的路径、账号全部走环境变量,不写死任何敏感信息。

先定三条原则

  1. 只备份"丢了重装很麻烦"的东西:配置文件(如 /etc/nginx、/etc/sing-box)、网站数据、数据库文件。整盘镜像又大又慢,还容易把虚拟目录打包进去,没必要。
  2. 保留份数要轮转:备份留最近 7 份,旧的自动删——不然硬盘迟早被备份塞满。
  3. 至少一份放异地:同一台机器上的备份,硬盘挂了就一起没。推送到另一台 VPS 或家里 NAS 才算数。

脚本:完整代码(已实测)

#!/usr/bin/env python3
"""VPS 自动备份脚本:打包关键目录 + 保留最近 N 份 + 可选异地推送。"""
import os, sys, tarfile, logging, subprocess
from datetime import datetime

BACKUP_ROOT = os.environ.get("BACKUP_ROOT", "/var/backups/myvps")
SOURCES = [s for s in os.environ.get("BACKUP_SOURCES", "/etc/sing-box,/etc/nginx").split(",") if s]
KEEP = int(os.environ.get("BACKUP_KEEP", "7"))
REMOTE = os.environ.get("BACKUP_REMOTE", "")  # 如 user@host:/backups ,空则不推送

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("backup")

def main():
    ts = datetime.now().strftime("%Y%m%d-%H%M%S")
    os.makedirs(BACKUP_ROOT, exist_ok=True)
    path = os.path.join(BACKUP_ROOT, f"vps-backup-{ts}.tar.gz")
    missing = [s for s in SOURCES if not os.path.exists(s)]
    if missing:
        log.warning("以下路径不存在,已跳过:%s", missing)
    with tarfile.open(path, "w:gz") as tar:
        for s in SOURCES:
            if os.path.exists(s):
                tar.add(s, arcname=os.path.basename(s.rstrip("/")))
    size_mb = os.path.getsize(path) / 1048576
    log.info("已生成 %s(%.1f MB)", path, size_mb)
    # 轮转:只保留最近 KEEP 份
    archives = sorted(f for f in os.listdir(BACKUP_ROOT)
                      if f.startswith("vps-backup-") and f.endswith(".tar.gz"))
    for old in archives[:-KEEP]:
        os.remove(os.path.join(BACKUP_ROOT, old))
        log.info("删除旧备份 %s", old)
    # 异地推送(可选)
    if REMOTE:
        r = subprocess.run(["/usr/bin/rsync", "-a", "--timeout=60", path, REMOTE],
                           capture_output=True, text=True)
        if r.returncode != 0:
            log.error("异地推送失败:%s", r.stderr.strip()[:200])
            return 1
        log.info("已推送到 %s", REMOTE)
    log.info("备份完成")
    return 0

if __name__ == "__main__":
    sys.exit(main())

这段代码已经真实跑过:打包、轮转、缺失路径跳过都验证正常。退出码非 0 表示失败,cron 或监控脚本可以直接拿它判断。

三步用起来

第 1 步:把脚本存为 /usr/local/bin/vps-backup.py,给执行权限:

chmod +x /usr/local/bin/vps-backup.py

第 2 步:手动跑一次,确认没问题:

BACKUP_SOURCES="/etc/sing-box,/var/www" BACKUP_KEEP=7 \
  python3 /usr/local/bin/vps-backup.py

看到 备份完成 就算通了。有异地机器的话再加 BACKUP_REMOTE="user@你的另一台机器:/backups",注意提前配好 SSH 密钥免密登录。

第 3 步:加进 cron,每天凌晨 3 点自动跑:

0 3 * * * BACKUP_SOURCES="/etc/sing-box,/var/www" BACKUP_KEEP=7 BACKUP_REMOTE="user@你的另一台机器:/backups" /usr/bin/python3 /usr/local/bin/vps-backup.py >> /var/log/vps-backup.log 2>&1

恢复:比备份更重要的一步

备份从来没人看,恢复才是考试。解压到原位即可:

tar -xzf /var/backups/myvps/vps-backup-20261012-030000.tar.gz -C /

建议每月手动做一次"恢复演练":把备份解到 /tmp,确认文件都在、配置能打开。备份了三年、恢复时发现包是坏的,这种故事我听过不止一回。

三个坑,提前避开

  • 别备份 /proc、/sys、/dev:这些是内核虚拟目录,打包会报错或体积爆炸。SOURCES 里只写真实数据目录。
  • 备份文件本身含敏感信息:配置里可能有密钥、token,备份目录权限设成 700,异地推送只走 SSH 密钥,别用密码。
  • cron 里 PATH 很干净:脚本里 rsync 我写了绝对路径 /usr/bin/rsync,就是为了避开"手动能跑、cron 里找不到命令"这个经典坑。

相关阅读