mirror of
https://github.com/acedanger/shell.git
synced 2025-12-05 22:50:18 -08:00
Commit local changes before merging with remote
This commit is contained in:
512
backup-env-files.sh
Executable file
512
backup-env-files.sh
Executable file
@@ -0,0 +1,512 @@
|
||||
#!/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
|
||||
|
||||
# Use a temp file to avoid subshell issues
|
||||
local temp_file=$(mktemp)
|
||||
find_env_files "$DOCKER_DIR" > "$temp_file"
|
||||
|
||||
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=$((count + 1))
|
||||
fi
|
||||
done < "$temp_file"
|
||||
|
||||
# Clean up temp file
|
||||
rm -f "$temp_file"
|
||||
|
||||
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 using a temp file to avoid subshell issues
|
||||
local temp_file=$(mktemp)
|
||||
find_env_files "$DOCKER_DIR" > "$temp_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=$((unchanged_count + 1))
|
||||
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=$((backup_count + 1))
|
||||
|
||||
# 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 < "$temp_file"
|
||||
|
||||
# Clean up temp file
|
||||
rm -f "$temp_file"
|
||||
|
||||
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
|
||||
|
||||
# Use a temp file to avoid subshell issues
|
||||
local temp_file=$(mktemp)
|
||||
find docker-containers -name "*.env" -type f 2>/dev/null > "$temp_file"
|
||||
|
||||
while IFS= read -r backup_file; do
|
||||
if [ -n "$backup_file" ]; then
|
||||
# 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=$((restore_count + 1))
|
||||
else
|
||||
echo -e "${RED}✗ Failed to restore: $rel_path${NC}"
|
||||
error_count=$((error_count + 1))
|
||||
fi
|
||||
fi
|
||||
done < "$temp_file"
|
||||
|
||||
# Clean up temp file
|
||||
rm -f "$temp_file"
|
||||
|
||||
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_backup_repo
|
||||
elif [ "$restore" = true ]; then
|
||||
restore_env_files
|
||||
else
|
||||
backup_env_files "$dry_run" "$force"
|
||||
fi
|
||||
}
|
||||
|
||||
# Run main function with all arguments
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user