mirror of
https://github.com/acedanger/shell.git
synced 2025-12-06 01:10:12 -08:00
- 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.
492 lines
15 KiB
Bash
Executable File
492 lines
15 KiB
Bash
Executable File
#!/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 "$@"
|