mirror of
https://github.com/acedanger/shell.git
synced 2025-12-06 02:20:11 -08:00
Add .env backup system for Docker containers
- backup-env-files.sh: Main backup script with Gitea integration - validate-env-backups.sh: Validation and integrity checking - env-backup-integration.sh: Integration with existing backup system - completions/env-backup-completion.bash: Tab completion support - docs/env-backup-system.md: Documentation for the backup system These scripts provide secure backup of .env files to private Gitea repository.
This commit is contained in:
491
backup-env-files.sh
Executable file
491
backup-env-files.sh
Executable file
@@ -0,0 +1,491 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# backup-env-files.sh - Backup .env files to private Gitea repository
|
||||||
|
# Author: Shell Repository
|
||||||
|
# Description: Securely backup and version control .env files from ~/docker/* directories
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[0;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
DOCKER_DIR="$HOME/docker"
|
||||||
|
BACKUP_REPO_NAME="docker-env-backup"
|
||||||
|
BACKUP_DIR="$HOME/.env-backup"
|
||||||
|
LOG_FILE="$SCRIPT_DIR/logs/env-backup.log"
|
||||||
|
|
||||||
|
# Ensure logs directory exists
|
||||||
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
|
||||||
|
# Logging function
|
||||||
|
log() {
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Display usage information
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 [OPTIONS]"
|
||||||
|
echo ""
|
||||||
|
echo "Backup .env files from ~/docker/* to private Gitea repository"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " -h, --help Show this help message"
|
||||||
|
echo " -i, --init Initialize the backup repository"
|
||||||
|
echo " -f, --force Force overwrite existing files"
|
||||||
|
echo " -d, --dry-run Show what would be backed up without doing it"
|
||||||
|
echo " -r, --restore Restore .env files from backup"
|
||||||
|
echo " -l, --list List all .env files found"
|
||||||
|
echo " -g, --gitea-url URL Set Gitea instance URL"
|
||||||
|
echo " -u, --username USER Set Gitea username"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 --init # First time setup"
|
||||||
|
echo " $0 # Regular backup"
|
||||||
|
echo " $0 --dry-run # See what would be backed up"
|
||||||
|
echo " $0 --restore # Restore files from backup"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check dependencies
|
||||||
|
check_dependencies() {
|
||||||
|
local missing_deps=()
|
||||||
|
|
||||||
|
command -v git >/dev/null 2>&1 || missing_deps+=("git")
|
||||||
|
command -v find >/dev/null 2>&1 || missing_deps+=("find")
|
||||||
|
|
||||||
|
if [ ${#missing_deps[@]} -ne 0 ]; then
|
||||||
|
echo -e "${RED}Error: Missing required dependencies: ${missing_deps[*]}${NC}"
|
||||||
|
echo "Please install the missing dependencies and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find all .env files in docker directories
|
||||||
|
find_env_files() {
|
||||||
|
local base_dir="$1"
|
||||||
|
|
||||||
|
if [ ! -d "$base_dir" ]; then
|
||||||
|
echo -e "${YELLOW}Warning: Docker directory $base_dir does not exist${NC}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find all .env files, including hidden ones and those with different extensions
|
||||||
|
find "$base_dir" -type f \( -name "*.env" -o -name ".env*" -o -name "env.*" \) 2>/dev/null | sort
|
||||||
|
}
|
||||||
|
|
||||||
|
# List all .env files
|
||||||
|
list_env_files() {
|
||||||
|
echo -e "${BLUE}=== Environment Files Found ===${NC}"
|
||||||
|
local count=0
|
||||||
|
|
||||||
|
while IFS= read -r env_file; do
|
||||||
|
if [ -n "$env_file" ]; then
|
||||||
|
local rel_path="${env_file#$DOCKER_DIR/}"
|
||||||
|
local size=$(du -h "$env_file" 2>/dev/null | cut -f1)
|
||||||
|
local modified=$(stat -c %y "$env_file" 2>/dev/null | cut -d' ' -f1)
|
||||||
|
|
||||||
|
echo -e "${GREEN}📄 $rel_path${NC}"
|
||||||
|
echo " Size: $size | Modified: $modified"
|
||||||
|
echo " Full path: $env_file"
|
||||||
|
echo ""
|
||||||
|
((count++))
|
||||||
|
fi
|
||||||
|
done < <(find_env_files "$DOCKER_DIR")
|
||||||
|
|
||||||
|
echo -e "${BLUE}Total .env files found: $count${NC}"
|
||||||
|
|
||||||
|
if [ $count -eq 0 ]; then
|
||||||
|
echo -e "${YELLOW}No .env files found in $DOCKER_DIR${NC}"
|
||||||
|
echo "Make sure you have Docker containers with .env files in subdirectories."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize backup repository
|
||||||
|
init_backup_repo() {
|
||||||
|
echo -e "${YELLOW}Initializing .env backup repository...${NC}"
|
||||||
|
|
||||||
|
# Prompt for Gitea details if not provided
|
||||||
|
if [ -z "$GITEA_URL" ]; then
|
||||||
|
read -p "Enter your Gitea instance URL (e.g., https://git.yourdomain.com): " GITEA_URL
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$GITEA_USERNAME" ]; then
|
||||||
|
read -p "Enter your Gitea username: " GITEA_USERNAME
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create backup directory
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
cd "$BACKUP_DIR"
|
||||||
|
|
||||||
|
# Initialize git repository if not already done
|
||||||
|
if [ ! -d ".git" ]; then
|
||||||
|
git init
|
||||||
|
echo -e "${GREEN}Initialized local git repository${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create .gitignore for additional security
|
||||||
|
cat > .gitignore << 'EOF'
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create README with important information
|
||||||
|
cat > README.md << 'EOF'
|
||||||
|
# Docker Environment Files Backup
|
||||||
|
|
||||||
|
This repository contains backup copies of .env files from Docker containers.
|
||||||
|
|
||||||
|
## ⚠️ SECURITY WARNING ⚠️
|
||||||
|
|
||||||
|
This repository contains sensitive configuration files including:
|
||||||
|
- API keys
|
||||||
|
- Database passwords
|
||||||
|
- Secret tokens
|
||||||
|
- Private configurations
|
||||||
|
|
||||||
|
**NEVER make this repository public!**
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-containers/
|
||||||
|
├── container1/
|
||||||
|
│ ├── .env
|
||||||
|
│ └── docker-compose.yml (reference only)
|
||||||
|
├── container2/
|
||||||
|
│ └── .env
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
- Files are organized by container/service name
|
||||||
|
- Only .env files are backed up (no other sensitive files)
|
||||||
|
- Restore using the backup-env-files.sh script
|
||||||
|
|
||||||
|
## Last Backup
|
||||||
|
|
||||||
|
This information is updated automatically by the backup script.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create directory structure
|
||||||
|
mkdir -p docker-containers
|
||||||
|
|
||||||
|
# Set up remote if URL provided
|
||||||
|
if [ -n "$GITEA_URL" ] && [ -n "$GITEA_USERNAME" ]; then
|
||||||
|
local remote_url="${GITEA_URL%/}/${GITEA_USERNAME}/${BACKUP_REPO_NAME}.git"
|
||||||
|
|
||||||
|
# Check if remote already exists
|
||||||
|
if ! git remote get-url origin >/dev/null 2>&1; then
|
||||||
|
git remote add origin "$remote_url"
|
||||||
|
echo -e "${GREEN}Added remote origin: $remote_url${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Save configuration
|
||||||
|
cat > .env-backup-config << EOF
|
||||||
|
GITEA_URL="$GITEA_URL"
|
||||||
|
GITEA_USERNAME="$GITEA_USERNAME"
|
||||||
|
BACKUP_REPO_NAME="$BACKUP_REPO_NAME"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Configuration saved to .env-backup-config${NC}"
|
||||||
|
echo -e "${BLUE}Next steps:${NC}"
|
||||||
|
echo "1. Create a private repository '$BACKUP_REPO_NAME' in your Gitea instance"
|
||||||
|
echo "2. Run the backup script to perform your first backup"
|
||||||
|
echo "3. The script will attempt to push to the remote repository"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initial commit
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial setup of .env backup repository" || echo "Nothing to commit"
|
||||||
|
|
||||||
|
log "Backup repository initialized at $BACKUP_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load configuration
|
||||||
|
load_config() {
|
||||||
|
local config_file="$BACKUP_DIR/.env-backup-config"
|
||||||
|
|
||||||
|
if [ -f "$config_file" ]; then
|
||||||
|
source "$config_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Backup .env files
|
||||||
|
backup_env_files() {
|
||||||
|
local dry_run="$1"
|
||||||
|
local force="$2"
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Starting .env files backup...${NC}"
|
||||||
|
|
||||||
|
# Check if backup directory exists
|
||||||
|
if [ ! -d "$BACKUP_DIR" ]; then
|
||||||
|
echo -e "${RED}Backup directory not found. Run with --init first.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$BACKUP_DIR"
|
||||||
|
load_config
|
||||||
|
|
||||||
|
# Create timestamp
|
||||||
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
local backup_count=0
|
||||||
|
local unchanged_count=0
|
||||||
|
|
||||||
|
# Process each .env file
|
||||||
|
while IFS= read -r env_file; do
|
||||||
|
if [ -n "$env_file" ]; then
|
||||||
|
# Determine relative path and backup location
|
||||||
|
local rel_path="${env_file#$DOCKER_DIR/}"
|
||||||
|
local backup_path="docker-containers/$rel_path"
|
||||||
|
local backup_dir=$(dirname "$backup_path")
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
echo -e "${BLUE}Would backup: $rel_path${NC}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create backup directory structure
|
||||||
|
mkdir -p "$backup_dir"
|
||||||
|
|
||||||
|
# Check if file has changed
|
||||||
|
local needs_backup=true
|
||||||
|
if [ -f "$backup_path" ] && [ "$force" != "true" ]; then
|
||||||
|
if cmp -s "$env_file" "$backup_path"; then
|
||||||
|
needs_backup=false
|
||||||
|
((unchanged_count++))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$needs_backup" = "true" ]; then
|
||||||
|
# Copy the file
|
||||||
|
cp "$env_file" "$backup_path"
|
||||||
|
echo -e "${GREEN}✓ Backed up: $rel_path${NC}"
|
||||||
|
((backup_count++))
|
||||||
|
|
||||||
|
# Also create a reference docker-compose.yml if it exists
|
||||||
|
local compose_file=$(dirname "$env_file")/docker-compose.yml
|
||||||
|
local compose_backup="$backup_dir/docker-compose.yml.ref"
|
||||||
|
|
||||||
|
if [ -f "$compose_file" ] && [ ! -f "$compose_backup" ]; then
|
||||||
|
cp "$compose_file" "$compose_backup"
|
||||||
|
echo -e "${BLUE} + Reference: docker-compose.yml${NC}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}- Unchanged: $rel_path${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <(find_env_files "$DOCKER_DIR")
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
echo -e "${BLUE}Dry run completed. No files were actually backed up.${NC}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update README with backup information
|
||||||
|
sed -i "/^## Last Backup/,$ d" README.md
|
||||||
|
cat >> README.md << EOF
|
||||||
|
|
||||||
|
## Last Backup
|
||||||
|
|
||||||
|
- **Date**: $timestamp
|
||||||
|
- **Files backed up**: $backup_count
|
||||||
|
- **Files unchanged**: $unchanged_count
|
||||||
|
- **Total files**: $((backup_count + unchanged_count))
|
||||||
|
|
||||||
|
Generated by backup-env-files.sh
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Commit changes
|
||||||
|
git add .
|
||||||
|
|
||||||
|
if git diff --staged --quiet; then
|
||||||
|
echo -e "${YELLOW}No changes to commit${NC}"
|
||||||
|
log "Backup completed - no changes detected"
|
||||||
|
else
|
||||||
|
git commit -m "Backup .env files - $timestamp
|
||||||
|
|
||||||
|
- Files backed up: $backup_count
|
||||||
|
- Files unchanged: $unchanged_count
|
||||||
|
- Total files: $((backup_count + unchanged_count))"
|
||||||
|
|
||||||
|
echo -e "${GREEN}Changes committed to local repository${NC}"
|
||||||
|
|
||||||
|
# Push to remote if configured
|
||||||
|
if git remote get-url origin >/dev/null 2>&1; then
|
||||||
|
echo -e "${YELLOW}Pushing to remote repository...${NC}"
|
||||||
|
if git push origin main 2>/dev/null || git push origin master 2>/dev/null; then
|
||||||
|
echo -e "${GREEN}✓ Successfully pushed to remote repository${NC}"
|
||||||
|
log "Backup completed and pushed to remote - $backup_count files backed up, $unchanged_count unchanged"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}Warning: Could not push to remote repository${NC}"
|
||||||
|
echo "You may need to:"
|
||||||
|
echo "1. Create the repository in Gitea first"
|
||||||
|
echo "2. Set up authentication (SSH key or token)"
|
||||||
|
log "Backup completed locally but failed to push to remote - $backup_count files backed up"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}No remote repository configured${NC}"
|
||||||
|
log "Backup completed locally - $backup_count files backed up, $unchanged_count unchanged"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Backup completed!${NC}"
|
||||||
|
echo -e "${BLUE}Summary:${NC}"
|
||||||
|
echo " - Files backed up: $backup_count"
|
||||||
|
echo " - Files unchanged: $unchanged_count"
|
||||||
|
echo " - Backup location: $BACKUP_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restore .env files
|
||||||
|
restore_env_files() {
|
||||||
|
echo -e "${YELLOW}Starting .env files restore...${NC}"
|
||||||
|
|
||||||
|
if [ ! -d "$BACKUP_DIR" ]; then
|
||||||
|
echo -e "${RED}Backup directory not found at $BACKUP_DIR${NC}"
|
||||||
|
echo "Either run --init first or clone your backup repository to this location."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$BACKUP_DIR"
|
||||||
|
load_config
|
||||||
|
|
||||||
|
# Pull latest changes if remote is configured
|
||||||
|
if git remote get-url origin >/dev/null 2>&1; then
|
||||||
|
echo -e "${YELLOW}Pulling latest changes from remote...${NC}"
|
||||||
|
git pull origin main 2>/dev/null || git pull origin master 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
local restore_count=0
|
||||||
|
local error_count=0
|
||||||
|
|
||||||
|
# Find all backed up .env files
|
||||||
|
find docker-containers -name "*.env" -type f 2>/dev/null | while IFS= read -r backup_file; do
|
||||||
|
# Determine target path
|
||||||
|
local rel_path="${backup_file#docker-containers/}"
|
||||||
|
local target_file="$DOCKER_DIR/$rel_path"
|
||||||
|
local target_dir=$(dirname "$target_file")
|
||||||
|
|
||||||
|
# Create target directory if it doesn't exist
|
||||||
|
if [ ! -d "$target_dir" ]; then
|
||||||
|
echo -e "${YELLOW}Creating directory: $target_dir${NC}"
|
||||||
|
mkdir -p "$target_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ask for confirmation if file exists and is different
|
||||||
|
if [ -f "$target_file" ]; then
|
||||||
|
if ! cmp -s "$backup_file" "$target_file"; then
|
||||||
|
echo -e "${YELLOW}File exists and differs: $rel_path${NC}"
|
||||||
|
read -p "Overwrite? (y/N): " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo -e "${YELLOW}Skipped: $rel_path${NC}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}Identical: $rel_path${NC}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy the file
|
||||||
|
if cp "$backup_file" "$target_file"; then
|
||||||
|
echo -e "${GREEN}✓ Restored: $rel_path${NC}"
|
||||||
|
((restore_count++))
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Failed to restore: $rel_path${NC}"
|
||||||
|
((error_count++))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e "${GREEN}Restore completed!${NC}"
|
||||||
|
echo -e "${BLUE}Summary:${NC}"
|
||||||
|
echo " - Files restored: $restore_count"
|
||||||
|
echo " - Errors: $error_count"
|
||||||
|
|
||||||
|
log "Restore completed - $restore_count files restored, $error_count errors"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main function
|
||||||
|
main() {
|
||||||
|
local init_repo=false
|
||||||
|
local dry_run=false
|
||||||
|
local force=false
|
||||||
|
local restore=false
|
||||||
|
local list_files=false
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-i|--init)
|
||||||
|
init_repo=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-f|--force)
|
||||||
|
force=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-d|--dry-run)
|
||||||
|
dry_run=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-r|--restore)
|
||||||
|
restore=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-l|--list)
|
||||||
|
list_files=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-g|--gitea-url)
|
||||||
|
GITEA_URL="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-u|--username)
|
||||||
|
GITEA_USERNAME="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check dependencies
|
||||||
|
check_dependencies
|
||||||
|
|
||||||
|
# Execute requested action
|
||||||
|
if [ "$list_files" = true ]; then
|
||||||
|
list_env_files
|
||||||
|
elif [ "$init_repo" = true ]; then
|
||||||
|
init_repo
|
||||||
|
elif [ "$restore" = true ]; then
|
||||||
|
restore_env_files
|
||||||
|
else
|
||||||
|
backup_env_files "$dry_run" "$force"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function with all arguments
|
||||||
|
main "$@"
|
||||||
50
completions/env-backup-completion.bash
Normal file
50
completions/env-backup-completion.bash
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# env-backup-completion.bash - Bash completion for environment backup scripts
|
||||||
|
# Source this file or copy to ~/.local/share/bash-completion/completions/
|
||||||
|
|
||||||
|
_backup_env_files() {
|
||||||
|
local cur prev opts
|
||||||
|
COMPREPLY=()
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
|
||||||
|
opts="--help --init --force --dry-run --restore --list --gitea-url --username"
|
||||||
|
|
||||||
|
case ${prev} in
|
||||||
|
--gitea-url|-g)
|
||||||
|
# Suggest common gitea URL patterns
|
||||||
|
COMPREPLY=( $(compgen -W "https://git. https://gitea. https://code." -- ${cur}) )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--username|-u)
|
||||||
|
# No completion for username
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_validate_env_backups() {
|
||||||
|
local cur prev opts
|
||||||
|
COMPREPLY=()
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
|
||||||
|
opts="--help --verbose --summary-only --missing-only --diff"
|
||||||
|
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Register completion functions
|
||||||
|
complete -F _backup_env_files backup-env-files.sh
|
||||||
|
complete -F _validate_env_backups validate-env-backups.sh
|
||||||
|
|
||||||
|
# Also register for the full path versions
|
||||||
|
complete -F _backup_env_files ./backup-env-files.sh
|
||||||
|
complete -F _validate_env_backups ./validate-env-backups.sh
|
||||||
315
docs/env-backup-system.md
Normal file
315
docs/env-backup-system.md
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
# Environment Files Backup System
|
||||||
|
|
||||||
|
This document describes the secure backup system for `.env` files from Docker containers to a private Gitea repository.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The environment files backup system provides:
|
||||||
|
|
||||||
|
- **Automated discovery** of all `.env` files in `~/docker/*` directories
|
||||||
|
- **Secure version control** using private Git repository
|
||||||
|
- **Change tracking** with timestamps and commit history
|
||||||
|
- **Easy restoration** of backed up configurations
|
||||||
|
- **Validation tools** to ensure backup integrity
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### Scripts
|
||||||
|
|
||||||
|
1. **backup-env-files.sh** - Main backup script
|
||||||
|
2. **validate-env-backups.sh** - Validation and integrity checking
|
||||||
|
|
||||||
|
### Repository Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.env-backup/
|
||||||
|
├── .git/ # Git repository
|
||||||
|
├── .gitignore # Security-focused gitignore
|
||||||
|
├── README.md # Repository documentation
|
||||||
|
├── .env-backup-config # Configuration file
|
||||||
|
└── docker-containers/ # Backed up files
|
||||||
|
├── container1/
|
||||||
|
│ ├── .env # Environment file
|
||||||
|
│ └── docker-compose.yml.ref # Reference compose file
|
||||||
|
├── container2/
|
||||||
|
│ └── .env
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### 🔒 Critical Security Points
|
||||||
|
|
||||||
|
1. **Repository Privacy**: The backup repository MUST be private
|
||||||
|
2. **Access Control**: Only you should have access to the repository
|
||||||
|
3. **Network Security**: Use HTTPS or SSH for Git operations
|
||||||
|
4. **Local Security**: Backup directory should have restricted permissions
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
- Use SSH keys for Git authentication (more secure than passwords)
|
||||||
|
- Regularly rotate any exposed credentials
|
||||||
|
- Monitor repository access logs
|
||||||
|
- Consider encrypting the entire backup repository
|
||||||
|
|
||||||
|
## Setup Instructions
|
||||||
|
|
||||||
|
### 1. Initial Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# First time setup
|
||||||
|
./backup-env-files.sh --init
|
||||||
|
|
||||||
|
# Follow prompts to configure:
|
||||||
|
# - Gitea instance URL
|
||||||
|
# - Username
|
||||||
|
# - Repository name
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create Repository in Gitea
|
||||||
|
|
||||||
|
1. Log into your Gitea instance
|
||||||
|
2. Create a new **private** repository named `docker-env-backup`
|
||||||
|
3. Do not initialize with README (the script handles this)
|
||||||
|
|
||||||
|
### 3. Configure Authentication
|
||||||
|
|
||||||
|
#### Option A: SSH Key (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate SSH key if you don't have one
|
||||||
|
ssh-keygen -t ed25519 -C "your_email@domain.com"
|
||||||
|
|
||||||
|
# Add public key to Gitea:
|
||||||
|
# 1. Go to Settings → SSH/GPG Keys
|
||||||
|
# 2. Add the content of ~/.ssh/id_ed25519.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option B: Personal Access Token
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In Gitea: Settings → Applications → Generate Token
|
||||||
|
# Configure Git to use token:
|
||||||
|
git config --global credential.helper store
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. First Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all .env files that will be backed up
|
||||||
|
./backup-env-files.sh --list
|
||||||
|
|
||||||
|
# Perform dry run to see what would happen
|
||||||
|
./backup-env-files.sh --dry-run
|
||||||
|
|
||||||
|
# Execute actual backup
|
||||||
|
./backup-env-files.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Regular Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Standard backup (only backs up changed files)
|
||||||
|
./backup-env-files.sh
|
||||||
|
|
||||||
|
# Force backup all files
|
||||||
|
./backup-env-files.sh --force
|
||||||
|
|
||||||
|
# See what would be backed up
|
||||||
|
./backup-env-files.sh --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic validation
|
||||||
|
./validate-env-backups.sh
|
||||||
|
|
||||||
|
# Detailed validation with file differences
|
||||||
|
./validate-env-backups.sh --diff --verbose
|
||||||
|
|
||||||
|
# Show only missing files
|
||||||
|
./validate-env-backups.sh --missing-only
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restoration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restore all .env files from backup
|
||||||
|
./backup-env-files.sh --restore
|
||||||
|
|
||||||
|
# This will:
|
||||||
|
# 1. Pull latest changes from remote
|
||||||
|
# 2. Prompt before overwriting existing files
|
||||||
|
# 3. Create directory structure as needed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Automation
|
||||||
|
|
||||||
|
### Cron Job Setup
|
||||||
|
|
||||||
|
Add to your crontab for automated backups:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backup .env files daily at 2 AM
|
||||||
|
0 2 * * * /home/yourusername/shell/backup-env-files.sh >/dev/null 2>&1
|
||||||
|
|
||||||
|
# Validate backups weekly on Sundays at 3 AM
|
||||||
|
0 3 * * 0 /home/yourusername/shell/validate-env-backups.sh --summary-only
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration with Existing Backup System
|
||||||
|
|
||||||
|
Add to your main backup script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In your existing backup script
|
||||||
|
echo "Backing up environment files..."
|
||||||
|
/home/yourusername/shell/backup-env-files.sh
|
||||||
|
|
||||||
|
# Validate the backup
|
||||||
|
if ! /home/yourusername/shell/validate-env-backups.sh --summary-only; then
|
||||||
|
echo "Warning: .env backup validation failed"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Discovery
|
||||||
|
|
||||||
|
The system automatically finds:
|
||||||
|
|
||||||
|
- `*.env` files (e.g., `production.env`, `staging.env`)
|
||||||
|
- `.env*` files (e.g., `.env`, `.env.local`, `.env.production`)
|
||||||
|
- `env.*` files (e.g., `env.development`, `env.local`)
|
||||||
|
|
||||||
|
### Example Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
~/docker/
|
||||||
|
├── traefik/
|
||||||
|
│ ├── .env # ✓ Backed up
|
||||||
|
│ └── docker-compose.yml
|
||||||
|
├── nextcloud/
|
||||||
|
│ ├── .env.production # ✓ Backed up
|
||||||
|
│ ├── .env.local # ✓ Backed up
|
||||||
|
│ └── docker-compose.yml
|
||||||
|
├── grafana/
|
||||||
|
│ ├── env.grafana # ✓ Backed up
|
||||||
|
│ └── docker-compose.yml
|
||||||
|
└── plex/
|
||||||
|
├── config.env # ✓ Backed up
|
||||||
|
└── docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **Git Push Fails**
|
||||||
|
```bash
|
||||||
|
# Check remote URL
|
||||||
|
cd ~/.env-backup && git remote -v
|
||||||
|
|
||||||
|
# Test connectivity
|
||||||
|
git ls-remote origin
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Missing Files**
|
||||||
|
```bash
|
||||||
|
# List what would be found
|
||||||
|
./backup-env-files.sh --list
|
||||||
|
|
||||||
|
# Check file permissions
|
||||||
|
ls -la ~/docker/*/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Repository Not Found**
|
||||||
|
- Ensure repository exists in Gitea
|
||||||
|
- Check repository name matches configuration
|
||||||
|
- Verify you have access permissions
|
||||||
|
|
||||||
|
### Recovery Scenarios
|
||||||
|
|
||||||
|
#### Disaster Recovery
|
||||||
|
|
||||||
|
If you lose your entire system:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Clone your backup repository
|
||||||
|
git clone https://git.yourdomain.com/username/docker-env-backup.git ~/.env-backup
|
||||||
|
|
||||||
|
# 2. Restore all files
|
||||||
|
cd /path/to/shell
|
||||||
|
./backup-env-files.sh --restore
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Selective Recovery
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restore specific file manually
|
||||||
|
cp ~/.env-backup/docker-containers/traefik/.env ~/docker/traefik/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
### Log Files
|
||||||
|
|
||||||
|
- **backup-env-files.sh**: `logs/env-backup.log`
|
||||||
|
- **validate-env-backups.sh**: `logs/env-backup-validation.log`
|
||||||
|
|
||||||
|
### Health Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Weekly health check script
|
||||||
|
#!/bin/bash
|
||||||
|
echo "=== .env Backup Health Check ==="
|
||||||
|
./validate-env-backups.sh --summary-only
|
||||||
|
|
||||||
|
# Check last backup time
|
||||||
|
cd ~/.env-backup
|
||||||
|
echo "Last backup: $(git log -1 --format='%ci')"
|
||||||
|
|
||||||
|
# Check repository status
|
||||||
|
git status --porcelain
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Enhancements
|
||||||
|
|
||||||
|
### Additional Security Measures
|
||||||
|
|
||||||
|
1. **GPG Encryption** (Optional)
|
||||||
|
```bash
|
||||||
|
# Encrypt sensitive files before committing
|
||||||
|
gpg --symmetric --cipher-algo AES256 file.env
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Restricted Permissions**
|
||||||
|
```bash
|
||||||
|
# Secure backup directory
|
||||||
|
chmod 700 ~/.env-backup
|
||||||
|
chmod 600 ~/.env-backup/.env-backup-config
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Audit Trail**
|
||||||
|
```bash
|
||||||
|
# Monitor repository access
|
||||||
|
git log --oneline --graph --all
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Regular Testing**: Test restoration process monthly
|
||||||
|
2. **Version Control**: Never force push; preserve history
|
||||||
|
3. **Documentation**: Keep README.md updated with changes
|
||||||
|
4. **Monitoring**: Set up alerts for failed backups
|
||||||
|
5. **Security**: Regularly review repository access permissions
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues or questions:
|
||||||
|
|
||||||
|
1. Check the troubleshooting section
|
||||||
|
2. Review log files for error details
|
||||||
|
3. Validate your Gitea configuration
|
||||||
|
4. Test Git connectivity manually
|
||||||
181
env-backup-integration.sh
Executable file
181
env-backup-integration.sh
Executable file
@@ -0,0 +1,181 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# env-backup-integration.sh - Integration script for adding .env backup to existing backup system
|
||||||
|
# Author: Shell Repository
|
||||||
|
# Description: Add .env backup functionality to existing backup scripts
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[0;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
echo -e "${BLUE}=== Environment Files Backup Integration ===${NC}"
|
||||||
|
|
||||||
|
# Function to add .env backup to a script
|
||||||
|
integrate_env_backup() {
|
||||||
|
local target_script="$1"
|
||||||
|
local integration_point="$2"
|
||||||
|
|
||||||
|
if [ ! -f "$target_script" ]; then
|
||||||
|
echo -e "${YELLOW}Target script not found: $target_script${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if already integrated
|
||||||
|
if grep -q "backup-env-files.sh" "$target_script"; then
|
||||||
|
echo -e "${GREEN}✓ Already integrated with $target_script${NC}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Integrating with $target_script...${NC}"
|
||||||
|
|
||||||
|
# Create backup of original script
|
||||||
|
cp "$target_script" "$target_script.backup"
|
||||||
|
|
||||||
|
# Integration code
|
||||||
|
local integration_code="
|
||||||
|
# === Environment Files Backup Integration ===
|
||||||
|
echo -e \"\${YELLOW}Backing up environment files...\${NC}\"
|
||||||
|
if [ -f \"\$SCRIPT_DIR/backup-env-files.sh\" ]; then
|
||||||
|
if \"\$SCRIPT_DIR/backup-env-files.sh\"; then
|
||||||
|
echo -e \"\${GREEN}✓ Environment files backed up successfully\${NC}\"
|
||||||
|
else
|
||||||
|
echo -e \"\${YELLOW}Warning: Environment files backup had issues\${NC}\"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e \"\${YELLOW}Warning: backup-env-files.sh not found\${NC}\"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate the backup
|
||||||
|
if [ -f \"\$SCRIPT_DIR/validate-env-backups.sh\" ]; then
|
||||||
|
if \"\$SCRIPT_DIR/validate-env-backups.sh\" --summary-only; then
|
||||||
|
echo -e \"\${GREEN}✓ Environment backup validation passed\${NC}\"
|
||||||
|
else
|
||||||
|
echo -e \"\${YELLOW}Warning: Environment backup validation failed\${NC}\"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo \"\"
|
||||||
|
# === End Environment Files Backup Integration ===
|
||||||
|
"
|
||||||
|
|
||||||
|
# Add integration based on integration point
|
||||||
|
case "$integration_point" in
|
||||||
|
"after_docker")
|
||||||
|
# Add after Docker backup section
|
||||||
|
if grep -q "docker" "$target_script" || grep -q "backup.*docker" "$target_script"; then
|
||||||
|
# Find a good insertion point after docker backup
|
||||||
|
local line_num=$(grep -n -i "docker.*backup\|backup.*docker" "$target_script" | tail -1 | cut -d: -f1)
|
||||||
|
if [ -n "$line_num" ]; then
|
||||||
|
sed -i "${line_num}a\\${integration_code}" "$target_script"
|
||||||
|
echo -e "${GREEN}✓ Integrated after Docker backup section${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}Could not find Docker backup section, adding at end${NC}"
|
||||||
|
echo "$integration_code" >> "$target_script"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}No Docker backup section found, adding at end${NC}"
|
||||||
|
echo "$integration_code" >> "$target_script"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"before_end")
|
||||||
|
# Add before the end of the script
|
||||||
|
local last_line=$(wc -l < "$target_script")
|
||||||
|
sed -i "${last_line}i\\${integration_code}" "$target_script"
|
||||||
|
echo -e "${GREEN}✓ Integrated before end of script${NC}"
|
||||||
|
;;
|
||||||
|
"manual")
|
||||||
|
echo -e "${BLUE}Manual integration code:${NC}"
|
||||||
|
echo "$integration_code"
|
||||||
|
echo -e "${YELLOW}Please add this code manually to your script at the appropriate location${NC}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${YELLOW}Unknown integration point, adding at end${NC}"
|
||||||
|
echo "$integration_code" >> "$target_script"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo -e "${GREEN}Integration completed. Backup saved as $target_script.backup${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find and integrate with existing backup scripts
|
||||||
|
echo -e "${YELLOW}Scanning for backup scripts to integrate with...${NC}"
|
||||||
|
|
||||||
|
# Common backup script patterns
|
||||||
|
declare -a backup_scripts=(
|
||||||
|
"$SCRIPT_DIR/backup-docker.sh"
|
||||||
|
"$SCRIPT_DIR/backup-media.sh"
|
||||||
|
"$SCRIPT_DIR/update.sh"
|
||||||
|
"$SCRIPT_DIR/backup.sh"
|
||||||
|
"$SCRIPT_DIR/daily-backup.sh"
|
||||||
|
)
|
||||||
|
|
||||||
|
found_scripts=()
|
||||||
|
|
||||||
|
for script in "${backup_scripts[@]}"; do
|
||||||
|
if [ -f "$script" ]; then
|
||||||
|
found_scripts+=("$script")
|
||||||
|
echo -e "${GREEN}Found: $(basename "$script")${NC}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#found_scripts[@]} -eq 0 ]; then
|
||||||
|
echo -e "${YELLOW}No backup scripts found to integrate with${NC}"
|
||||||
|
echo -e "${BLUE}You can manually add the .env backup to your backup routine:${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "# Add to your backup script:"
|
||||||
|
echo "$SCRIPT_DIR/backup-env-files.sh"
|
||||||
|
echo "$SCRIPT_DIR/validate-env-backups.sh --summary-only"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo -e "${BLUE}Select scripts to integrate with (or 'all' for all, 'none' to skip):${NC}"
|
||||||
|
for i in "${!found_scripts[@]}"; do
|
||||||
|
echo "$((i+1)). $(basename "${found_scripts[$i]}")"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "Enter your choice: " choice
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
"all")
|
||||||
|
for script in "${found_scripts[@]}"; do
|
||||||
|
integrate_env_backup "$script" "after_docker"
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
"none")
|
||||||
|
echo -e "${YELLOW}Skipping integration${NC}"
|
||||||
|
;;
|
||||||
|
[0-9]*)
|
||||||
|
if [ "$choice" -ge 1 ] && [ "$choice" -le ${#found_scripts[@]} ]; then
|
||||||
|
script_index=$((choice-1))
|
||||||
|
integrate_env_backup "${found_scripts[$script_index]}" "after_docker"
|
||||||
|
else
|
||||||
|
echo -e "${RED}Invalid choice${NC}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Invalid choice${NC}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a simple cron entry suggestion
|
||||||
|
echo -e "${BLUE}=== Automation Suggestions ===${NC}"
|
||||||
|
echo "Add to crontab for automated backups:"
|
||||||
|
echo ""
|
||||||
|
echo "# Daily .env backup at 2 AM"
|
||||||
|
echo "0 2 * * * $SCRIPT_DIR/backup-env-files.sh >/dev/null 2>&1"
|
||||||
|
echo ""
|
||||||
|
echo "# Weekly validation on Sundays at 3 AM"
|
||||||
|
echo "0 3 * * 0 $SCRIPT_DIR/validate-env-backups.sh --summary-only"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -e "${GREEN}Integration setup completed!${NC}"
|
||||||
|
echo -e "${BLUE}Next steps:${NC}"
|
||||||
|
echo "1. Run: $SCRIPT_DIR/backup-env-files.sh --init"
|
||||||
|
echo "2. Create private repository in Gitea"
|
||||||
|
echo "3. Run first backup: $SCRIPT_DIR/backup-env-files.sh"
|
||||||
|
echo "4. Test restoration: $SCRIPT_DIR/backup-env-files.sh --restore"
|
||||||
227
validate-env-backups.sh
Executable file
227
validate-env-backups.sh
Executable file
@@ -0,0 +1,227 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# validate-env-backups.sh - Validate .env file backups
|
||||||
|
# Author: Shell Repository
|
||||||
|
# Description: Verify integrity and consistency of .env file backups
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[0;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
DOCKER_DIR="$HOME/docker"
|
||||||
|
BACKUP_DIR="$HOME/.env-backup"
|
||||||
|
LOG_FILE="$SCRIPT_DIR/logs/env-backup-validation.log"
|
||||||
|
|
||||||
|
# Ensure logs directory exists
|
||||||
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
|
||||||
|
# Logging function
|
||||||
|
log() {
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Display usage information
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 [OPTIONS]"
|
||||||
|
echo ""
|
||||||
|
echo "Validate .env file backups against source files"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " -h, --help Show this help message"
|
||||||
|
echo " -v, --verbose Verbose output"
|
||||||
|
echo " -s, --summary-only Show only summary"
|
||||||
|
echo " -m, --missing-only Show only missing files"
|
||||||
|
echo " -d, --diff Show differences between files"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 # Basic validation"
|
||||||
|
echo " $0 --verbose # Detailed validation"
|
||||||
|
echo " $0 --missing-only # Show only missing backups"
|
||||||
|
echo " $0 --diff # Show file differences"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate backups
|
||||||
|
validate_backups() {
|
||||||
|
local verbose="$1"
|
||||||
|
local summary_only="$2"
|
||||||
|
local missing_only="$3"
|
||||||
|
local show_diff="$4"
|
||||||
|
|
||||||
|
echo -e "${BLUE}=== .env Backup Validation ===${NC}"
|
||||||
|
echo "Source: $DOCKER_DIR"
|
||||||
|
echo "Backup: $BACKUP_DIR"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ ! -d "$BACKUP_DIR" ]; then
|
||||||
|
echo -e "${RED}Error: Backup directory not found at $BACKUP_DIR${NC}"
|
||||||
|
echo "Run backup-env-files.sh --init first"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local total_source=0
|
||||||
|
local total_backup=0
|
||||||
|
local missing_backup=0
|
||||||
|
local outdated_backup=0
|
||||||
|
local identical_files=0
|
||||||
|
local different_files=0
|
||||||
|
local backup_only=0
|
||||||
|
|
||||||
|
# Arrays to store file lists
|
||||||
|
declare -a missing_files=()
|
||||||
|
declare -a outdated_files=()
|
||||||
|
declare -a different_files_list=()
|
||||||
|
declare -a backup_only_files=()
|
||||||
|
|
||||||
|
# Count and validate source files
|
||||||
|
echo -e "${YELLOW}Scanning source files...${NC}"
|
||||||
|
while IFS= read -r source_file; do
|
||||||
|
if [ -n "$source_file" ]; then
|
||||||
|
((total_source++))
|
||||||
|
|
||||||
|
# Determine backup path
|
||||||
|
local rel_path="${source_file#$DOCKER_DIR/}"
|
||||||
|
local backup_file="$BACKUP_DIR/docker-containers/$rel_path"
|
||||||
|
|
||||||
|
if [ ! -f "$backup_file" ]; then
|
||||||
|
((missing_backup++))
|
||||||
|
missing_files+=("$rel_path")
|
||||||
|
[ "$summary_only" != true ] && [ "$missing_only" != true ] && echo -e "${RED}✗ Missing backup: $rel_path${NC}"
|
||||||
|
else
|
||||||
|
# Compare files
|
||||||
|
if cmp -s "$source_file" "$backup_file"; then
|
||||||
|
((identical_files++))
|
||||||
|
[ "$verbose" = true ] && [ "$summary_only" != true ] && [ "$missing_only" != true ] && echo -e "${GREEN}✓ Identical: $rel_path${NC}"
|
||||||
|
else
|
||||||
|
# Check if backup is older
|
||||||
|
if [ "$source_file" -nt "$backup_file" ]; then
|
||||||
|
((outdated_backup++))
|
||||||
|
outdated_files+=("$rel_path")
|
||||||
|
[ "$summary_only" != true ] && [ "$missing_only" != true ] && echo -e "${YELLOW}⚠ Outdated backup: $rel_path${NC}"
|
||||||
|
else
|
||||||
|
((different_files++))
|
||||||
|
different_files_list+=("$rel_path")
|
||||||
|
[ "$summary_only" != true ] && [ "$missing_only" != true ] && echo -e "${BLUE}△ Different: $rel_path${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show diff if requested
|
||||||
|
if [ "$show_diff" = true ] && [ "$summary_only" != true ] && [ "$missing_only" != true ]; then
|
||||||
|
echo -e "${YELLOW} Differences:${NC}"
|
||||||
|
diff -u "$backup_file" "$source_file" | head -20 || true
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <(find "$DOCKER_DIR" -type f \( -name "*.env" -o -name ".env*" -o -name "env.*" \) 2>/dev/null | sort)
|
||||||
|
|
||||||
|
# Check for backup-only files
|
||||||
|
echo -e "${YELLOW}Scanning backup files...${NC}"
|
||||||
|
if [ -d "$BACKUP_DIR/docker-containers" ]; then
|
||||||
|
while IFS= read -r backup_file; do
|
||||||
|
if [ -n "$backup_file" ]; then
|
||||||
|
((total_backup++))
|
||||||
|
|
||||||
|
# Determine source path
|
||||||
|
local rel_path="${backup_file#$BACKUP_DIR/docker-containers/}"
|
||||||
|
local source_file="$DOCKER_DIR/$rel_path"
|
||||||
|
|
||||||
|
if [ ! -f "$source_file" ]; then
|
||||||
|
((backup_only++))
|
||||||
|
backup_only_files+=("$rel_path")
|
||||||
|
[ "$summary_only" != true ] && [ "$missing_only" != true ] && echo -e "${BLUE}⚡ Backup only: $rel_path${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <(find "$BACKUP_DIR/docker-containers" -type f \( -name "*.env" -o -name ".env*" -o -name "env.*" \) 2>/dev/null | sort)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Display missing files if requested
|
||||||
|
if [ "$missing_only" = true ] && [ ${#missing_files[@]} -gt 0 ]; then
|
||||||
|
echo -e "${RED}=== Missing Backup Files ===${NC}"
|
||||||
|
for file in "${missing_files[@]}"; do
|
||||||
|
echo -e "${RED}✗ $file${NC}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
echo -e "${BLUE}=== Validation Summary ===${NC}"
|
||||||
|
echo -e "Source files: ${BLUE}$total_source${NC}"
|
||||||
|
echo -e "Backup files: ${BLUE}$total_backup${NC}"
|
||||||
|
echo -e "Identical: ${GREEN}$identical_files${NC}"
|
||||||
|
echo -e "Missing backups: ${RED}$missing_backup${NC}"
|
||||||
|
echo -e "Outdated backups: ${YELLOW}$outdated_backup${NC}"
|
||||||
|
echo -e "Different files: ${BLUE}$different_files${NC}"
|
||||||
|
echo -e "Backup only: ${BLUE}$backup_only${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Recommendations
|
||||||
|
if [ $missing_backup -gt 0 ] || [ $outdated_backup -gt 0 ]; then
|
||||||
|
echo -e "${YELLOW}=== Recommendations ===${NC}"
|
||||||
|
if [ $missing_backup -gt 0 ]; then
|
||||||
|
echo -e "${YELLOW}• Run backup-env-files.sh to backup missing files${NC}"
|
||||||
|
fi
|
||||||
|
if [ $outdated_backup -gt 0 ]; then
|
||||||
|
echo -e "${YELLOW}• Run backup-env-files.sh to update outdated backups${NC}"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Log summary
|
||||||
|
log "Validation completed - Source: $total_source, Backup: $total_backup, Missing: $missing_backup, Outdated: $outdated_backup"
|
||||||
|
|
||||||
|
# Exit with error code if issues found
|
||||||
|
if [ $missing_backup -gt 0 ] || [ $outdated_backup -gt 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main function
|
||||||
|
main() {
|
||||||
|
local verbose=false
|
||||||
|
local summary_only=false
|
||||||
|
local missing_only=false
|
||||||
|
local show_diff=false
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-v|--verbose)
|
||||||
|
verbose=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-s|--summary-only)
|
||||||
|
summary_only=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-m|--missing-only)
|
||||||
|
missing_only=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-d|--diff)
|
||||||
|
show_diff=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
validate_backups "$verbose" "$summary_only" "$missing_only" "$show_diff"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function with all arguments
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user