笔记的加密和备份
我的笔记使用 git 进行版本控制,备份时使用 git bundle
打包,加密使用 age。
加密和备份
简要流程
- 检查是否有未 commit 的更改,如果有,则先创建临时 commit。
- 创建一个 Git bundle 文件,包含仓库的所有分支和引用。
- 撤销临时 commit(soft reset)。
- 使用 age 加密工具,根据 recipients 文件里的公钥对 Git bundle 文件进行加密,并移动到备份目录。
- 检查备份目录中文件数量,如果超过 30 个,则删除最旧的多余文件,仅保留最新的 30 个。
- 调用 onedrive 同步命令将备份目录与 OneDrive 云盘进行同步。
脚本内容
#!/bin/bash
# Exit immediately if a command exits with a non-zero status
set -e
# Configuration
REPO_DIR="/home/wanten/repos/vault" # Directory of the local Git repository
BACKUP_DIR="/home/wanten/onedrive/Backups/Vault" # Directory where backups are stored
HOSTNAME=$(hostname) # Get the hostname
TIMESTAMP=$(date +"%Y-%m%d-%H%M%S") # Current timestamp for naming the bundle file
BUNDLE="vault-$HOSTNAME-$TIMESTAMP.bundle" # Name of the git bundle file
RECIPIENTS="/home/wanten/repos/nix-config/secrets/recipients.txt" # File containing age encryption recipients
# Change to the repository directory, exit with an error message if it fails
cd "$REPO_DIR" || { echo "Repository directory not found"; exit 1; }
# Check for uncommitted changes in the repository
if [ -n "$(git status --porcelain)" ]; then
echo "Uncommitted changes found. Creating temporary commit."
git add -A # Add all changes to the staging area
git commit -m "Temporary backup commit on $TIMESTAMP" # Create a temporary commit
fi
# Create a git bundle containing all branches and refs
echo "Creating git bundle..."
mkdir -p "$BACKUP_DIR" # Create the backup directory if it doesn't exist
git bundle create "$BUNDLE" --all # Create the git bundle file
# If a temporary commit was created, remove it
if git log -1 --pretty=%B | grep -q "Temporary backup commit on $TIMESTAMP"; then
echo "Removing temporary commit."
git reset --soft HEAD~1 # Remove the temporary commit without affecting the working directory
git stash clear # Remove all stashes
echo "Cleared all git stashes"
fi
# Verify the integrity of the created git bundle
echo "Verifying the git bundle..."
git bundle verify "$BUNDLE"
# Encrypt the bundle file using age
age -e -R "$RECIPIENTS" -o "$BUNDLE.age" "$BUNDLE"
mv "$BUNDLE.age" "$BACKUP_DIR" # Move the encrypted bundle to the backup directory
rm "$BUNDLE" # Remove the original unencrypted bundle file
# Output the completion message
echo "Encrypted backup created: $BACKUP_DIR/$BUNDLE.age"
echo "Backup completed: $BACKUP_DIR/$BUNDLE.age"
echo ""
echo "$BUNDLE.age"
echo ""
ls -lh "$BACKUP_DIR/$BUNDLE.age"
echo ""
# Get the number of files in the folder
FILE_COUNT=$(ls -1q "$BACKUP_DIR" | wc -l)
# Echo the file count
echo "File count: $FILE_COUNT"
# Check if the file count is greater than 30
if [ "$FILE_COUNT" -gt 30 ]; then
# Files to delete (all except the 30 newest)
FILES_TO_DELETE=$(ls -t1 "$BACKUP_DIR" | tail -n +31)
echo "Deleting the following files:"
echo "$FILES_TO_DELETE"
# Change to the backup directory and remove the files
(cd "$BACKUP_DIR" && echo "$FILES_TO_DELETE" | xargs rm)
echo "Old files deleted."
else
echo "File count is within the limit (30)."
fi
# Synchronize the backup directory with OneDrive
onedrive --synchronize
解密和恢复
通常只在其他设备中执行恢复 (合并) 操作。
简要流程
- 从备份目录中列出所有备份文件,使用 fzf 交互式选择一个备份文件
- 使用 age 和本地私钥解密选中的备份文件,生成一个临时 Git bundle 文件
- 如果本地仓库已存在则进入仓库,否则用解密的 bundle 文件克隆出一个新仓库
- 在仓库中添加解密的 bundle 作为临时远程,拉取并获取更新
- stash 本地改动后合并备份中的 main 分支,并移除临时远程
- 软重置临时 commit,恢复原本工作区状态
脚本内容
#!/bin/bash
# Exit immediately if a command exits with a non-zero status
set -e
# Configuration
REPO_DIR="/home/wanten/repos/vault" # Directory of the local Git repository
REPOS_BASE="/home/wanten/repos" # Base directory for repositories
BACKUP_DIR="/home/wanten/onedrive/Backups/Vault" # Directory where backups are stored
BUNDLE_PATH="/home/wanten/Downloads/vault.bundle" # Path to the temporary Git bundle file
AGE_KEY_PATH="" # Path to the age decryption key
# Ensure cleanup on exit
cleanup() {
rm -f "$BUNDLE_PATH"
}
trap cleanup EXIT
# Function to select a file from a directory and return its path
select_file_from_directory() {
# Define the directory to search
local DIRECTORY="$1"
# Check if the directory exists
if [ ! -d "$DIRECTORY" ]; then
echo "Directory not found: $DIRECTORY"
return 1
fi
# Get all files in the directory sorted by modification date
local FILES
FILES=$(find "$DIRECTORY" -type f -printf '%T@ %p\n' | sort -n | awk '{print $2}')
# Check if there are any files
if [ -z "$FILES" ]; then
echo "No files found in directory: $DIRECTORY"
return 1
fi
# Create a list of file names and full paths
local FILE_LIST
FILE_LIST=$(echo "$FILES" | while read -r FILE; do
BASENAME=$(basename "$FILE")
echo "$BASENAME:$FILE"
done)
# Use fzf to create a selector, showing only file names
local SELECTED_FILE
SELECTED_FILE=$(echo "$FILE_LIST" | fzf --tac --height 15 --reverse --preview 'cat {2}' --preview-window down:3:wrap --delimiter=':' --with-nth=1)
# Extract the full path of the selected file
local SELECTED_PATH
SELECTED_PATH=$(echo "$SELECTED_FILE" | awk -F':' '{print $2}')
# Check if a file was selected
if [ -z "$SELECTED_PATH" ]; then
echo "No file selected."
return 1
fi
# Return the selected file path
echo "$SELECTED_PATH"
}
# Function to decrypt the selected backup file using age and save it to BUNDLE_PATH
decrypt_selected_backup() {
local SELECTED_FILE
echo "Please select a backup file"
SELECTED_FILE=$(select_file_from_directory "$BACKUP_DIR")
if [ $? -ne 0 ]; then
echo "File selection failed."
exit 1
fi
sudo age -d -i "$AGE_KEY_PATH" -o "$BUNDLE_PATH" "$SELECTED_FILE"
if [ ! -f "$BUNDLE_PATH" ]; then
echo "Decryption failed or no bundle file found"
exit 1
fi
}
# Remove the remote if it exists
remove_remote_if_exists() {
if git remote | grep -q "b"; then
git remote remove b
echo "Removed remote: b"
fi
}
# Add the decrypted bundle as a temporary remote and fetch updates
add_and_fetch_remote() {
git remote add b "$BUNDLE_PATH"
git fetch b
}
# Stash changes and merge fetched changes
stash_and_merge() {
git stash
git merge b/main
}
# Remove the temporary remote
remove_temporary_remote() {
git remote remove b
}
# Reset the last commit without changing the working directory
soft_reset_last_commit() {
git reset --soft HEAD~1
}
# Change to a directory, exit with an error message if it fails
change_directory() {
local DIR=$1
cd "$DIR" || { echo "Directory not found: $DIR"; exit 1; }
}
# Check and setup repository
setup_repository() {
if [ -d "$REPO_DIR" ]; then
change_directory "$REPO_DIR"
else
# Ensure base directory exists
change_directory "$REPOS_BASE"
git clone "$BUNDLE_PATH" vault
change_directory "$REPO_DIR"
soft_reset_last_commit
git remote remove origin
fi
}
# Main script execution
main() {
change_directory "$BACKUP_DIR"
decrypt_selected_backup
setup_repository
remove_remote_if_exists
add_and_fetch_remote
stash_and_merge
remove_temporary_remote
soft_reset_last_commit
}
main