harvies 最近的时间轴更新
harvies

harvies

V2EX 第 203237 号会员,加入于 2016-11-28 00:50:51 +08:00
harvies 最近回复了
SmartisanOS
89 天前
回复了 GhanaMalta 创建的主题 杭州 临安房子网络布置(有偿
使用有线回程,弱电箱只放光猫(路由器在客厅),光猫设置桥接,光猫 wan 口连接路由器 wan 口,光猫 lan 口连接路由器 lan 口(重点),房间的网线连接到光猫的 lan 口,然后在房间放路由器
配置下静态路由,本地环境就可以访问 service 的 cluster ip 了
238 天前
回复了 QingquanBaby 创建的主题 问与答 适合办公用的 Linux 系统
ArchLinux+sway
255 天前
回复了 MRG0 创建的主题 程序员 哪一个桌面 Linux 比较好用
sway
274 天前
回复了 robking 创建的主题 问与答 服务器 备份
@harvies 增量备份 打错了,是每次全量备份,可设置保留历史份数
274 天前
回复了 robking 创建的主题 问与答 服务器 备份
@harvies 加密压缩备份通过 rclone 同步到 oss ,增量备份,针对数据量小可以用这个脚本。数据量大,如几百 g 、上 T 建议直接同步到 nas
274 天前
回复了 robking 创建的主题 问与答 服务器 备份
```python
#!/usr/bin/env python3

import os
import shutil
import subprocess
import json
from datetime import date,datetime

# 默认配置文件路径
CONFIG_FILE = "backup.json"

# 用法函数
def print_usage():
print("Usage: {} [-c <config_file>]".format(os.path.basename(__file__)))
print(" -c, --config-file specify the path of the configuration file (default: backup.json)")

# 解析参数
import argparse
parser = argparse.ArgumentParser(description="Simple backup script")
parser.add_argument("-c", "--config-file", metavar="CONFIG_FILE", type=str, default=CONFIG_FILE, help="specify the path of the configuration file (default: {})".format(CONFIG_FILE))
args = parser.parse_args()

# 加载配置文件
if not os.path.isfile(args.config_file):
print("Cannot find config file {}.".format(args.config_file))
exit(1)

with open(args.config_file) as f:
config = json.load(f)

compress_password = config.get("compress_password")
rclone_transfers = config.get("rclone_transfers", 4)
rclone_config_file = config.get("rclone_config_file")
backup_dirs = config.get("backup_dirs")

# 同步单个文件夹到备份路径下
def backup_folder(src_path, backup_path, prefix, enabled, ignore_files, compress_rate, volume_size,temp_dir):
# 如果备份文件夹未启用备份,直接返回备份成功
if not enabled:
print("Skipping backup for {} as it is not enabled.".format(src_path))
return True

# 压缩文件夹,生成 .7z 文件
compressed_path = os.path.join(temp_dir,prefix, os.path.basename(src_path) + ".7z")
print("Compressing {} to {} ...".format(src_path, compressed_path))
compress_command = ["7z","-bb3", "a", "-p{}".format(compress_password), "-y", "-mhe=on", "-mx{}".format(compress_rate), "-v{}".format(volume_size)]
for ignore_item in ignore_files:
compress_command.append("-xr!" + ignore_item)
compress_command.append(compressed_path)
compress_command.append(src_path)
print("Compressing with command: {}".format(" ".join(compress_command)))
if subprocess.call(compress_command) != 0:
print("Failed to compress {}.".format(src_path))
return False

# 分卷压缩后的文件名列表
compressed_path_parts = compressed_path.split(".")
compressed_path_parts[-1] = "7z"
compressed_path_prefix = ".".join(compressed_path_parts)

# 同步压缩后的文件夹到备份路径下
dest_path = os.path.join(backup_path, prefix)
print("Backing up {} to {} ...".format(os.path.dirname(compressed_path), dest_path))
if subprocess.call(["rclone", "--config", rclone_config_file, "copy", "--transfers", str(rclone_transfers), "--progress", os.path.dirname(compressed_path), dest_path]) != 0:
print("Backup failed for {}!".format(src_path))
return False
else:
file_count_output = subprocess.check_output(["rclone", "--config", rclone_config_file, "ls", dest_path], universal_newlines=True)
file_count = len(file_count_output.splitlines())
print("Backup succeed. {} files has been backed up to {}".format(file_count, dest_path))

print("Deteled temp folder for {}!".format(os.path.dirname(compressed_path)))
shutil.rmtree(os.path.dirname(compressed_path))
return True

# 删除指定文件夹下的老备份
def delete_old_backups(backup_dir, max_backup_count):
# 获取备份文件的列表,并按照时间戳排序
file_list_output = subprocess.check_output(["rclone", "--config", rclone_config_file, "lsf", backup_dir], universal_newlines=True)
file_list = sorted(file_list_output.splitlines())

# 计算需要删除的备份文件数量
file_count = len(file_list)
files_to_delete = file_count - max_backup_count

# 如果要删除的文件数量小于等于 0 ,直接返回
if files_to_delete <= 0:
print("No old backups need to be deleted for {}.".format(backup_dir))
return True

# 删除最老的若干个备份文件
oldest_files = file_list[:files_to_delete]
for file_name in oldest_files:
print("Deleting {} ...".format(file_name))
if subprocess.call(["rclone", "--config", rclone_config_file, "purge", os.path.join(backup_dir, file_name)]) != 0:
print("Failed to delete old backup {}.".format(file_name))
return False

print("Deleted {} old backups for {}.".format(files_to_delete, backup_dir))
return True

print("Backup script started at", datetime.today().strftime("%Y-%m-%d %H:%M:%S"))

# 遍历要备份的文件夹并执行备份操作
print("Starting backups...")
for item in backup_dirs:
dir_path = item.get("path")
backup_path = item.get("backup_path", config["backup_path"])
max_backup_count = item.get("max_backup_count", config["max_backup_count"])
enabled = item.get("enabled", config["enabled"])
ignore_files = item.get("ignore_files", config["ignore_files"])# 配置忽略文件
compress_rate = item.get("compress_rate", config["compress_rate"]) # 配置压缩率
volume_size = item.get("volume_size",config["volume_size"]) # 配置卷大小
temp_dir = item.get("temp_dir",config["temp_dir"]) # 临时文件夹
if not os.path.isdir(dir_path):
print("Cannot find directory {}.".format(dir_path))
continue
if not enabled:
print("Skipping backup for {} as it is not enabled.".format(dir_path))
continue
if subprocess.call(["rclone", "--config", rclone_config_file, "mkdir", os.path.join(backup_path, os.path.basename(dir_path))]) != 0:
print("Failed to create backup storage path for {}!".format(dir_path))
continue
if not backup_folder(dir_path, os.path.join(backup_path, os.path.basename(dir_path)), datetime.today().strftime("%Y%m%d%H%M"), enabled, ignore_files, compress_rate, volume_size, temp_dir) or not delete_old_backups(os.path.join(backup_path, os.path.basename(dir_path)), max_backup_count):
print("Failed to backup and delete old backups for {}.".format(dir_path))

print("Backup script completed at", datetime.today().strftime("%Y-%m-%d %H:%M:%S"))
```

```json
{
"compress_password": "password", // 压缩密码
"rclone_transfers": 4, // rclone 同步时的并发数
"rclone_config_file": "/path/to/rclone/config/file", // rclone 的配置文件路径
"backup_path": "remote:path/to/backup/folder", // 备份文件夹的远程路径
"backup_dirs": [ // 要备份的文件夹列表
{
"path": "/path/to/backup/folder1", // 要备份的文件夹路径
"backup_path": "remote:path/to/backup/folder1", // 备份文件夹的远程路径,如果不指定,则使用默认的 backup_path
"max_backup_count": 5, // 保留的备份文件数量,超过这个数量的备份文件将被删除,如果不指定,则默认为 3
"enabled": true, // 是否启用备份,如果为 false ,则跳过备份,如果不指定,则默认为 true
"ignore_files": ["*.log", "cache"], // 要忽略的文件列表,支持通配符,如果不指定,则备份所有文件
"compress_rate": 7 // 压缩率,范围为 0-9 ,0 表示不压缩,9 表示最高压缩率,如果不指定,则默认为 5
},
{
"path": "/path/to/backup/folder2", // 要备份的文件夹路径
"backup_path": "remote:path/to/backup/folder2", // 备份文件夹的远程路径,如果不指定,则使用默认的 backup_path
"max_backup_count": 3, // 保留的备份文件数量,超过这个数量的备份文件将被删除,如果不指定,则默认为 3
"enabled": false // 是否启用备份,如果为 false ,则跳过备份,如果不指定,则默认为 true
}
]
}
{
"backup_path": "remote:/backup", // 备份存储路径,需要使用 rclone 支持的远程存储方式
"max_backup_count": 7, // 每个备份文件夹最多保留的备份数量
"enabled": true, // 全局开关,控制是否启用备份
"ignore_files": ["*.log", "*.tmp"], // 需要忽略备份的文件,支持通配符
"compress_password": "123456", // 压缩密码,需要与备份脚本中的一致
"rclone_transfers": 4, // rclone 备份时的并发传输数,默认为 4
"rclone_config_file": "/path/to/rclone.conf", // rclone 配置文件路径
"backup_dirs": [ // 需要备份的文件夹列表
{
"path": "/path/to/backup/folder1", // 文件夹路径
"backup_path": "remote:/backup/folder1", // 备份存储路径,若不指定则使用全局备份路径
"max_backup_count": 30, // 最多保留的备份数量,若不指定则使用全局值
"enabled": true, // 是否启用备份,若不指定则使用全局值
"ignore_files": ["*.log"], // 忽略备份的文件,若不指定则使用全局值
"compress_rate": 5, // 压缩率,取值范围为 0 到 9 ,默认为 5
"volume_size": "1024m" // 分卷大小,默认为 1024m
},
{
"path": "/path/to/backup/folder2",
"enabled": false // 禁用备份,其他值将使用全局值
}
]
}
```
326 天前
回复了 sfdev 创建的主题 Linux 时隔多年再次体验 Linux 桌面系统
玩过各种发行版,现在用的 ArchLinux 搭配 sway ,体验不错,可定制性极强
rclone
resilio sync
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2745 人在线   最高记录 6543   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 13ms · UTC 12:25 · PVG 20:25 · LAX 05:25 · JFK 08:25
Developed with CodeLauncher
♥ Do have faith in what you're doing.