mirror of
https://github.com/Coldin04/vaultwarden_backup.git
synced 2026-02-17 16:13:32 +00:00
添加 Vaultwarden 自动备份脚本及相关配置文件
This commit is contained in:
commit
90fd54a6a1
5 changed files with 358 additions and 0 deletions
20
.env-ex
Normal file
20
.env-ex
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Cloudflare R2
|
||||
R2_ACCESS_KEY_ID=
|
||||
R2_SECRET_ACCESS_KEY=
|
||||
R2_ACCOUNT_ID=
|
||||
R2_BUCKET_NAME=backup
|
||||
R2_REGION=auto
|
||||
|
||||
# Vaultwarden 和备份相关
|
||||
COMPOSE_DIR=/root/vaultwarden
|
||||
BACKUP_SOURCE_DIR=/root/vaultwarden/vw-data
|
||||
BACKUP_TEMP_FILE=/tmp/vw_backup.tar.gz
|
||||
BACKUP_ENCRYPTED_FILE=/tmp/vw_backup.tar.gz.enc
|
||||
ENCRYPT_PASSWORD=Password
|
||||
|
||||
SLOT_COUNT=3
|
||||
BACKUP_PREFIX=back-vault-s
|
||||
|
||||
# Telegram Bot
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
TELEGRAM_CHAT_ID=
|
||||
162
README.md
Normal file
162
README.md
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
# Vaultwarden 自动备份脚本
|
||||
|
||||
本项目用于自动化备份 Vaultwarden 密码管理服务的数据,支持加密、云端存储、备份轮换和 Telegram 通知,适用于个人和小型团队的数据安全需求
|
||||
|
||||
> README.md是由Copilot AI 生成的,可能包含一些不准确或不完整的信息,请根据实际情况进行调整和补充。如果遇到情况请提交 Issue 或 PR 进行修正,万分感谢!
|
||||
|
||||
---
|
||||
|
||||
## 项目背景
|
||||
Vaultwarden 是 Bitwarden 的轻量级开源实现,常用于自建密码管理服务。数据安全至关重要,定期自动备份能有效防止数据丢失。本脚本实现了备份、加密、上传、轮换和通知的全流程自动化。
|
||||
|
||||
## 功能亮点
|
||||
- 自动备份 Vaultwarden 的数据库、配置文件、私钥和附件目录
|
||||
- 使用 openssl 强加密备份文件,保障数据安全
|
||||
- 上传加密备份至 Cloudflare R2 对象存储
|
||||
- 自动轮换旧备份,保留指定数量,节省存储空间
|
||||
- 备份失败时自动通过 Telegram 推送通知
|
||||
- 支持多平台(Windows/Linux)
|
||||
|
||||
---
|
||||
|
||||
## 环境准备
|
||||
|
||||
### 1. 克隆或下载项目
|
||||
将本项目代码下载到任意目录,例如:
|
||||
```
|
||||
c:\Users\你的用户名\Downloads\vaultwarden_backup
|
||||
```
|
||||
或
|
||||
```
|
||||
/home/youruser/vaultwarden_backup
|
||||
```
|
||||
|
||||
### 2. 配置环境变量
|
||||
1. 将 `.env-ex` 文件重命名为 `backup.env`,并根据实际情况填写各项配置。
|
||||
2. 各项配置说明:
|
||||
- `R2_ACCESS_KEY_ID`:Cloudflare R2 的 Access Key
|
||||
- `R2_SECRET_ACCESS_KEY`:Cloudflare R2 的 Secret Key
|
||||
- `R2_ACCOUNT_ID`:Cloudflare R2 的账号 ID
|
||||
- `R2_BUCKET_NAME`:R2 存储桶名称
|
||||
- `R2_REGION`:R2 区域(通常填 auto)
|
||||
- `BACKUP_SOURCE_DIR`:Vaultwarden 数据目录(如 `/opt/vaultwarden/data` 或 `C:\vaultwarden\data`)
|
||||
- `BACKUP_TEMP_FILE`:临时 tar 文件路径(如 `/tmp/vaultwarden-backup.tar.gz` 或 `C:\temp\vaultwarden-backup.tar.gz`)
|
||||
- `BACKUP_ENCRYPTED_FILE`:加密后文件路径(如 `/tmp/vaultwarden-backup.tar.gz.enc`)
|
||||
- `ENCRYPT_PASSWORD`:备份加密密码(请妥善保存)
|
||||
- `SLOT_COUNT`:保留的备份数量(如 3)
|
||||
- `BACKUP_PREFIX`:备份文件前缀(如 `back-vault-s`)
|
||||
- `TELEGRAM_BOT_TOKEN`:Telegram Bot Token(可选)
|
||||
- `TELEGRAM_CHAT_ID`:Telegram Chat ID(可选)
|
||||
|
||||
3. 配置示例:
|
||||
```
|
||||
R2_ACCESS_KEY_ID=xxxxxx
|
||||
R2_SECRET_ACCESS_KEY=xxxxxx
|
||||
R2_ACCOUNT_ID=xxxxxx
|
||||
R2_BUCKET_NAME=vault-backup
|
||||
R2_REGION=auto
|
||||
BACKUP_SOURCE_DIR=/opt/vaultwarden/data
|
||||
BACKUP_TEMP_FILE=/tmp/vaultwarden-backup.tar.gz
|
||||
BACKUP_ENCRYPTED_FILE=/tmp/vaultwarden-backup.tar.gz.enc
|
||||
ENCRYPT_PASSWORD=你的强密码
|
||||
SLOT_COUNT=3
|
||||
BACKUP_PREFIX=back-vault-s
|
||||
TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
|
||||
TELEGRAM_CHAT_ID=123456789
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 依赖安装
|
||||
|
||||
### Python 依赖
|
||||
请确保已安装 Python 3.7 及以上版本。
|
||||
|
||||
#### 方式一:使用 requirements.txt
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
#### 方式二:手动安装
|
||||
```
|
||||
pip install boto3 python-dotenv requests
|
||||
```
|
||||
|
||||
### 系统依赖
|
||||
- Windows:请确保 `sqlite3.exe` 和 `openssl.exe` 已加入环境变量(可用 scoop/choco 安装)
|
||||
- Linux:
|
||||
```
|
||||
sudo apt install sqlite3 openssl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 备份流程详解
|
||||
1. 备份数据库:使用 sqlite3 的 .backup 命令生成安全副本
|
||||
2. 打包数据:将数据库、配置文件、私钥和附件目录打包为 tar.gz
|
||||
3. 加密备份:用 openssl AES-256-CBC 加密备份文件
|
||||
4. 上传备份:将加密文件上传至 Cloudflare R2
|
||||
5. 轮换备份:自动删除超出 SLOT_COUNT 的旧备份
|
||||
6. 清理临时文件:删除本地临时文件
|
||||
7. 通知推送:如有异常,自动推送 Telegram 消息
|
||||
|
||||
---
|
||||
|
||||
## 运行方法
|
||||
在项目目录下执行:
|
||||
```
|
||||
python backup.py
|
||||
```
|
||||
|
||||
如需定时自动运行,可结合系统计划任务:
|
||||
- Windows:任务计划程序
|
||||
- Linux:crontab
|
||||
|
||||
---
|
||||
|
||||
## 数据安全说明
|
||||
- 备份文件采用 AES-256-CBC 加密,密码由 ENCRYPT_PASSWORD 指定
|
||||
- 加密密码请妥善保存,遗失将无法恢复备份内容
|
||||
- 云端存储采用 Cloudflare R2,需正确配置密钥
|
||||
|
||||
---
|
||||
|
||||
## Telegram 通知配置
|
||||
如需异常通知,请在 Telegram 创建 Bot 并获取 Token,查找你的 Chat ID 并填写到 `backup.env`。
|
||||
|
||||
- Bot 创建教程:https://core.telegram.org/bots#creating-a-new-bot
|
||||
- Chat ID 获取方法:可用 @userinfobot 查询
|
||||
|
||||
---
|
||||
|
||||
## 备份轮换机制
|
||||
- 每次备份后自动检查云端备份数量,超出 SLOT_COUNT 时自动删除最旧的备份
|
||||
- 备份文件命名格式:`{BACKUP_PREFIX}{日期时间}.tar.gz.enc`
|
||||
|
||||
---
|
||||
|
||||
## 常见问题与故障排查
|
||||
- **依赖未安装**:请检查 Python 包和系统工具是否安装齐全
|
||||
- **权限问题**:请确保数据目录和临时文件路径有读写权限
|
||||
- **上传失败**:检查 R2 配置和网络连接
|
||||
- **加密失败**:确认 openssl 命令可用,密码无特殊字符
|
||||
- **Telegram 未推送**:检查 Bot Token 和 Chat ID 是否正确
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
- Q: 如何恢复备份?
|
||||
A: 下载加密备份文件,使用 openssl 解密后解包 tar 文件即可。
|
||||
- Q: 可以只备份数据库吗?
|
||||
A: 可自行修改 backup.py,只保留数据库相关打包逻辑。
|
||||
- Q: 支持多平台吗?
|
||||
A: 支持 Windows 和 Linux,MacOS 亦可。
|
||||
|
||||
---
|
||||
|
||||
## 贡献与反馈
|
||||
如有建议、问题或需求,欢迎提交 Issue 或 PR。
|
||||
|
||||
---
|
||||
|
||||
> 本项目旨在简化 Vaultwarden 的备份流程,提升数据安全性。感谢您的使用!
|
||||
154
backup.py
Normal file
154
backup.py
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
import os
|
||||
import tarfile
|
||||
import boto3
|
||||
import datetime
|
||||
import hashlib
|
||||
import shutil
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
from subprocess import run
|
||||
|
||||
# 加载配置
|
||||
load_dotenv('./backup.env') # 或 '.env',看你放哪
|
||||
|
||||
# 环境变量
|
||||
access_key = os.getenv('R2_ACCESS_KEY_ID')
|
||||
secret_key = os.getenv('R2_SECRET_ACCESS_KEY')
|
||||
account_id = os.getenv('R2_ACCOUNT_ID')
|
||||
bucket = os.getenv('R2_BUCKET_NAME')
|
||||
region = os.getenv('R2_REGION', 'auto')
|
||||
|
||||
source_dir = os.getenv('BACKUP_SOURCE_DIR')
|
||||
tar_path = os.getenv('BACKUP_TEMP_FILE')
|
||||
enc_path = os.getenv('BACKUP_ENCRYPTED_FILE')
|
||||
enc_password = os.getenv('ENCRYPT_PASSWORD')
|
||||
|
||||
slot_count = int(os.getenv('SLOT_COUNT', 3))
|
||||
backup_prefix = os.getenv('BACKUP_PREFIX', 'back-vault-s')
|
||||
|
||||
# 创建 R2 客户端
|
||||
session = boto3.session.Session()
|
||||
client = session.client(
|
||||
service_name='s3',
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key,
|
||||
endpoint_url=f'https://{account_id}.r2.cloudflarestorage.com',
|
||||
region_name=region,
|
||||
)
|
||||
|
||||
# 发送 Telegram 消息
|
||||
def send_telegram_message(text):
|
||||
bot_token = os.getenv('TELEGRAM_BOT_TOKEN')
|
||||
chat_id = os.getenv('TELEGRAM_CHAT_ID')
|
||||
if not bot_token or not chat_id:
|
||||
print('[!] 未配置 Telegram 推送,无法发送通知')
|
||||
return
|
||||
url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
|
||||
payload = {"chat_id": chat_id, "text": text}
|
||||
try:
|
||||
requests.post(url, data=payload, timeout=10)
|
||||
except Exception as e:
|
||||
print(f'[!] Telegram 推送失败: {e}')
|
||||
|
||||
"""
|
||||
def tar_backup():
|
||||
print('[*] 打包数据...')
|
||||
with tarfile.open(tar_path, "w:gz") as tar:
|
||||
tar.add(source_dir, arcname=os.path.basename(source_dir))
|
||||
"""
|
||||
def sqlite_backup():
|
||||
print('[*] 使用 sqlite3 .backup 命令备份数据库...')
|
||||
db_file = os.path.join(source_dir, 'db.sqlite3')
|
||||
backup_db_file = os.path.join(source_dir, 'db-backup.sqlite3')
|
||||
if os.path.exists(db_file):
|
||||
cmd = [
|
||||
"sqlite3", db_file,
|
||||
f".backup '{backup_db_file}'"
|
||||
]
|
||||
run(cmd, check=True)
|
||||
else:
|
||||
print('[!] 未找到数据库文件,跳过数据库备份')
|
||||
return backup_db_file
|
||||
|
||||
def tar_backup():
|
||||
print('[*] 只备份必要数据...')
|
||||
backup_db_file = sqlite_backup()
|
||||
with tarfile.open(tar_path, "w:gz") as tar:
|
||||
# 主数据库
|
||||
db_file = os.path.join(source_dir, 'db.sqlite3')
|
||||
if os.path.exists(db_file):
|
||||
tar.add(db_file, arcname='db.sqlite3')
|
||||
# 配置文件
|
||||
config_file = os.path.join(source_dir, 'config.json')
|
||||
if os.path.exists(config_file):
|
||||
tar.add(config_file, arcname='config.json')
|
||||
# 私钥
|
||||
rsa_file = os.path.join(source_dir, 'rsa_key.pem')
|
||||
if os.path.exists(rsa_file):
|
||||
tar.add(rsa_file, arcname='rsa_key.pem')
|
||||
# 附件目录(如有需要)
|
||||
attachments_dir = os.path.join(source_dir, 'attachments')
|
||||
if os.path.exists(attachments_dir):
|
||||
tar.add(attachments_dir, arcname='attachments')
|
||||
if os.path.exists(backup_db_file):
|
||||
os.remove(backup_db_file)
|
||||
|
||||
def encrypt_backup():
|
||||
print('[*] 加密备份...')
|
||||
cmd = [
|
||||
"openssl", "enc", "-aes-256-cbc", "-salt", "-pbkdf2",
|
||||
"-in", tar_path,
|
||||
"-out", enc_path,
|
||||
"-k", enc_password
|
||||
]
|
||||
run(cmd, check=True)
|
||||
|
||||
def upload_backup():
|
||||
now = datetime.datetime.utcnow().strftime('%Y%m%d-%H%M%S')
|
||||
object_key = f"{backup_prefix}{now}.tar.gz.enc"
|
||||
print(f'[*] 上传备份 {object_key} ...')
|
||||
with open(enc_path, 'rb') as f:
|
||||
client.upload_fileobj(f, bucket, object_key)
|
||||
return object_key
|
||||
|
||||
def rotate_backups():
|
||||
print('[*] 检查并轮换旧备份...')
|
||||
res = client.list_objects_v2(Bucket=bucket, Prefix=backup_prefix)
|
||||
if 'Contents' not in res:
|
||||
print('[+] 无需轮换,当前仅有一个备份')
|
||||
return
|
||||
|
||||
backups = sorted(
|
||||
[obj['Key'] for obj in res['Contents']],
|
||||
key=lambda k: k
|
||||
)
|
||||
if len(backups) <= slot_count:
|
||||
print(f'[+] 当前备份数 {len(backups)} 未超过 {slot_count},无需删除')
|
||||
return
|
||||
|
||||
to_delete = backups[0:len(backups)-slot_count]
|
||||
for key in to_delete:
|
||||
print(f'[-] 删除过旧备份 {key}')
|
||||
client.delete_object(Bucket=bucket, Key=key)
|
||||
|
||||
def cleanup():
|
||||
print('[*] 清理临时文件')
|
||||
for f in [tar_path, enc_path]:
|
||||
if os.path.exists(f):
|
||||
os.remove(f)
|
||||
|
||||
def main():
|
||||
try:
|
||||
tar_backup()
|
||||
encrypt_backup()
|
||||
upload_backup()
|
||||
rotate_backups()
|
||||
cleanup()
|
||||
print('[✓] 所有备份流程完成')
|
||||
except Exception as e:
|
||||
err_msg = f'[!] 备份流程出错: {e}'
|
||||
print(err_msg)
|
||||
send_telegram_message(err_msg)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
19
backup.sh
Normal file
19
backup.sh
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# 读取配置
|
||||
source ./backup.env
|
||||
|
||||
echo "[INFO] 停止 Vaultwarden 容器"
|
||||
cd "$COMPOSE_DIR"
|
||||
docker compose stop vaultwarden
|
||||
|
||||
echo "[INFO] 运行备份 Python 脚本"
|
||||
python3 backup.py
|
||||
|
||||
echo "[INFO] 启动 Vaultwarden 容器"
|
||||
docker compose start vaultwarden
|
||||
|
||||
echo "[INFO] 备份完成"
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
boto3
|
||||
python-dotenv
|
||||
requests
|
||||
Loading…
Add table
Add a link
Reference in a new issue