前提
Python3环境
安装百度云第三方库bypy
推荐Linux,Windows下应该也可以(博主没测试过)
脚本
大体分两个文件,一个是包装了bypy的自用类,一个是实际供命令行调用的主程序文件。
直接上代码,很多逻辑是博主自己用的,请自行根据需要删减或修改。
特别是发送短信提醒的功能,这个博主是使用了腾讯云的短信服务。读者请无视。(自行删除)
主程序
文件名:backupBaota.py (可以随意修改)
文件位置:/home/XXX/mytools/ (用户home目录下的mytools子目录,理论上任意文件夹都可以,不过博主没测试过)
#!/usr/bin/python
#-*- coding:utf-8 -*-
import os,sys
import logging
import requests,json
import time
import socket, getpass
from myLib import Backup2BDY
from myLib import MySmsLib
# 本程序用于备份宝塔面板的定时备份,包括网站和数据库(宝塔面板设置每日一次备份,本程序在cron里也设置每日一次即可)
# 本程序需要Python3,并安装百度云命令行工具bypy
# crontab例子(注意cron命令中的logs文件夹需要提前准备好)
# 45 15 * * * cd ~/mytools/ ; python3 ~/mytools/backupBaota.py >>~/mytools/logs/backupBaota.std.log 2>&1
currentPath = sys.path[0] + "/"
# 日志输出配置
LOG_FILE = currentPath + "logs/backupBaota.log"
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
DATE_FORMAT = "%Y/%m/%d %H:%M:%S"
logging.basicConfig(filename=LOG_FILE, level=logging.INFO, format=LOG_FORMAT, datefmt=DATE_FORMAT)
#logging.basicConfig(level=logging.INFO, format=LOG_FORMAT, datefmt=DATE_FORMAT)
# 保留多少份最新备份
KEEP_DAYS = 30
# 用来取得文件名中的日期部分的正则表达式(用于保留指定份数的最新文件)
# Db_mlc_20210502_150501.sql.gz
# Web_mango.natappvip.cc_20210502_150002.tar.gz
DATE_REGX = r'.*(\d{8})_\d{6}.*'
# 百度云远程目录
BDY_ROOT = '/raspiBackup/wwwBackup/'
BDY_PATH_SITE = BDY_ROOT + 'site/'
BDY_PATH_DATABASE = BDY_ROOT + 'database/'
# 其他备份文件,一般是程序文件本身和日志文件,本地文件和远程路径成对设置即可
other_backup_list = []
other_backup_list.append([currentPath + "backupBaota.py", BDY_ROOT + 'tool/backupBaota.py'])
other_backup_list.append([currentPath + 'myLib/Backup2BDY.py', BDY_ROOT + 'tool/myLib/Backup2BDY.py'])
other_backup_list.append([currentPath + 'myLib/MySmsLib.py', BDY_ROOT + 'tool/myLib/MySmsLib.py'])
other_backup_list.append([LOG_FILE, BDY_ROOT + 'backupBaota.log'])
# 配置完成,以下基本不用修改
hostName = socket.gethostname()
execUser = getpass.getuser()
try:
timeStart = time.time()
logging.info("备份开始-----------------------------------------------------")
logging.info("运行环境: Hostname [%s] User [%s]", hostName, execUser)
# 发送提醒短信用工具类(不需要可注释掉)
sms = MySmsLib.MySms()
bdy = Backup2BDY.Backup2BDY()
logging.info("开始备份database...")
# 宝塔备份文件夹使用的是默认设置如果有变化需要修改
bdy.backupFolder(localPath='/www/backup/database/', remotePath=BDY_PATH_DATABASE, \
keepDays=KEEP_DAYS, dateRegx=DATE_REGX)
logging.info("开始备份site...")
bdy.backupFolder(localPath='/www/backup/site/', remotePath=BDY_PATH_SITE, \
keepDays=KEEP_DAYS, dateRegx=DATE_REGX)
logging.info("最后备份其他文件(覆盖)...")
for row in other_backup_list:
bdy.backupFile(row[0], row[1])
# logging.info("备份成功,发送提醒短信...")
# timeEnd = time.time()
# ret = sms.send947195(hostName, "[备份宝塔站点]", "成功", "%d秒"%(timeEnd-timeStart), "无")
# logging.info("提醒短信发送完毕。")
logging.info("备份完成,程序终止。")
except Exception as err:
logging.error("出现未知错误\n{0}".format(err))
logging.info("发送提醒短信...")
timeEnd = time.time()
ret = sms.send947195(hostName, "[备份宝塔站点]", "失败。", "%d秒"%(timeEnd-timeStart), "无")
logging.info("提醒短信发送完毕,程序终止。")
包装类
文件名:Backup2BDY.py
文件位置:/home/XXX/mytools/myLib/ (主程序所在文件夹下的 myLib 子文件夹)
import os
import logging
import re
import shutil
from bypy import ByPy
class Backup2BDY:
# 日志输出配置
# LOG_FILE = "./Up2BDY.log"
# LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
# DATE_FORMAT = "%Y/%m/%d %H:%M:%S"
# logging.basicConfig(filename=LOG_FILE, level=logging.INFO, format=LOG_FORMAT, datefmt=DATE_FORMAT)
# 获取远程目录下的文件名列表
def __getRemoteFileList(self, remotePath):
# 通过os.popen来执行bypy以获取标准输出内容(os.popen返回的其实是一个文件对象)
fileNameList = []
isIntroLine = True
retFile = os.popen("python3 -m bypy list " + remotePath + " '$f'")
for row in retFile:
if (not isIntroLine):
# 已跳过介绍部分文本,开始接收文件名
fileNameList.append(row.replace('\n', ''))
# 跳过输出结果开头的部分(MD5码不对之类的消息)
# 直到输出/app/bypy/...才是真正需要的数据
if (row.startswith("/app")):
isIntroLine = False
retFile.close()
return fileNameList
# 取得文件列表中的所有日期一览,倒序排列
def __getDateListByFileNameList(self, fileNameList, dateRegx ):
# 获取所有文件名中的日期,宝塔的站点和数据库备份文件名中会包含yyyymmdd_hhmmss
dateList = []
for fileName in fileNameList:
matchObj = re.match( dateRegx, fileName)
date = matchObj.group(1)
if (date not in dateList):
dateList.append(date)
# 倒序排列
dateList.sort(reverse=True)
return dateList
# 备份文件夹
def backupFolder(self, localPath, remotePath, keepDays, dateRegx=r'.*(\d{8})_\d{6}.*'):
# logging.info("test:localPath:%s, remotePath:%s, keepDays:%s, dateRegx:%s", localPath, remotePath, keepDays, dateRegx)
# return
# 定义百度云模块
bp=ByPy()
logging.info("本地文件夹:%s", localPath)
logging.info("检查云盘中有无过期备份,只保留最新%d天(份)备份。", keepDays)
logging.info("取得云盘文件夹「%s」中的文件列表...", remotePath)
fileNameList = self.__getRemoteFileList(remotePath)
# 取得文件列表中的所有日期一览,倒序排列
dateList = self.__getDateListByFileNameList(fileNameList, dateRegx)
logging.info("当前云盘上存在的备份日期列表")
for date in dateList:
logging.info("%s", date)
# 根据定义的保留备份天数计算过期日期
# 比如存在如下日期的文件
# 20210415
# 20210412
# 20210407
# 20210329
# 20210203
# 当备份天数=2,过期日期=20210407
# 当备份天数=3,过期日期=20210329
# 当备份天数>=5,没有过期文件
if (keepDays < len(dateList)):
outdate = dateList[keepDays]
logging.info("存在过期备份,早于或等于日期「%s」的备份将被删除。", outdate)
# 再次循环一次所有文件,并进行删除
for fileName in fileNameList:
matchObj = re.match( dateRegx, fileName)
date = matchObj.group(1)
# 删除过期备份
if (date <= outdate):
# 删除文件不需要获取标准输出的内容,所以这里直接调用模块而不是os.popen
retCode = bp.delete(remotePath + fileName)
if (retCode == 0):
logging.info("过期备份「%s」删除成功。", remotePath + fileName)
else:
logging.warn("过期备份「%s」删除失败。", remotePath + fileName)
else:
logging.info("目前不存在过期备份。")
# 上传本地文件夹,已存在的话覆盖
logging.info("上传本地文件夹...")
bp.upload(localPath, remotePath)
# 上传完成以后再次取得远程文件文件列表便于检查
showLastFileCnt = 10
logging.info("上传完成。当前云盘上的最新%d个备份文件:", showLastFileCnt)
fileNameList = self.__getRemoteFileList(remotePath)
dateList = self.__getDateListByFileNameList(fileNameList, dateRegx)
cnt = 1
for date in dateList:
if (cnt <= showLastFileCnt) :
for fileName in fileNameList :
if (cnt <= showLastFileCnt) :
if (fileName.find(date) != -1) :
logging.info("[%02d] %s", cnt, fileName)
cnt = cnt + 1
# 备份文件
def backupFile(self, localFile, remoteFile):
bp=ByPy()
logging.info("本地文件: %s", localFile)
logging.info("远程文件: %s", remoteFile)
logging.info("上传中...")
bp.upload(localFile, remoteFile)
logging.info("完成")