From 61ade81d39532b031dcb63923256004172a150ea Mon Sep 17 00:00:00 2001 From: Jose Henrique Date: Sat, 20 Sep 2025 20:53:54 -0300 Subject: [PATCH] updating things --- README.md | 29 ++-- backup.sh | 62 -------- clean.sh | 28 +--- docker-updater.sh | 280 ------------------------------------ nas-gdrive-backup.sh | 292 -------------------------------------- scripts-download.sh | 330 ------------------------------------------- 6 files changed, 10 insertions(+), 1011 deletions(-) delete mode 100644 backup.sh delete mode 100644 docker-updater.sh delete mode 100644 nas-gdrive-backup.sh delete mode 100644 scripts-download.sh diff --git a/README.md b/README.md index 32f548a..0b02f92 100644 --- a/README.md +++ b/README.md @@ -2,29 +2,18 @@ Useful scripts for managing servers written in Bash. Supported OS includes only Ubuntu/Debian and Alpine. -They feature auto-updating, and some error handling. - -## `scripts-download.sh` - -This script is used to download selected scripts in this repository to a specified directory and install them as cron jobs. -```bash -curl -sSL https://git.ivanch.me/ivanch/server-scripts/raw/branch/main/scripts-download.sh | bash -``` - -When you run the script, you will be prompted to select which scripts you want to download and install. - -### `backup.sh` - -This script is used to backup a directory to a remote server using `rsync`. It is intended to be run as a cron job. - -#### `nas-gdrive-backup.sh` - -This script is used to both install itself and to run a periodic backup to Google Drive for files that had changed. +In the past I was using it more for maintaining my home servers, bare metal with Docker, however when I changed to k3s, I started using it less. Now I'm using this as a collection of useful scripts that I can use when needed. Also `clean.sh` is still useful. ### `clean.sh` This script is used to clean some of the files, docker dangling images, and docker stopped/unused containers. -### `docker-updater.sh` +### `windows-backup.ps1` -This script is used to update all the docker containers on the server. \ No newline at end of file +This PowerShell script is used to backup files from a Windows machine to a remote Samba server. It uses `7zip` to create the zip file, and then sends over the network using SMB protocol. + +## Haven Notify + +It's a small internal service designed to send notifications to a specified Discord channel via webhooks. It's written in Go and can be easily deployed. + +The project is defined on [/haven-notify](./haven-notify) folder. diff --git a/backup.sh b/backup.sh deleted file mode 100644 index ade3c67..0000000 --- a/backup.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash - -# Function to send notification -HOSTNAME=$(cat /etc/hostname) -NOTIFY_URL_ERROR="http://notify.haven/template/notify/error" -NOTIFY_URL_BACKUP="http://notify.haven/template/notify/backup" -send_error_notification() { - local message="$1" - local critical="$2" - curl -s -X POST "$NOTIFY_URL_ERROR" \ - -H "Content-Type: application/json" \ - -d "{\"caller\": \"Docker Backup - $HOSTNAME\", \"message\": \"$message\", \"critical\": $critical}" -} -send_backup_notification() { - local message="$1" - local backup_size="$2" - curl -s -X POST "$NOTIFY_URL_BACKUP" \ - -H "Content-Type: application/json" \ - -d "{\"title\": \"Docker Backup - $HOSTNAME\", \"message\": \"$message\", \"backupSizeInMB\": $backup_size}" -} - -#################### - -# Variables -SOURCE_DIR="/root/docker" -BACKUP_FILE="/tmp/docker_backup_$(date +%Y%m%d%H%M%S).tar.gz" -REMOTE_USER="ivanch" -REMOTE_HOST="nas.haven" -REMOTE_DIR="/export/Backup/Docker/$(cat /etc/hostname)" - -# Create a compressed backup file -zip -q -r $BACKUP_FILE $SOURCE_DIR || true -if [ $? -ne 0 ]; then - send_error_notification "⚠️ Some files or folders in $SOURCE_DIR could not be backed up (possibly in use or locked). Backup archive created with available files." false -fi - -# Check if remote path exists -if ! ssh $REMOTE_USER@$REMOTE_HOST "mkdir -p $REMOTE_DIR"; then - send_error_notification "❌ Failed to create remote directory: $REMOTE_DIR on $REMOTE_HOST" true - exit 1 -fi - -# Transfer the backup file to the remote server -if ! scp $BACKUP_FILE $REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR; then - send_error_notification "❌ Failed to transfer backup file to remote server: $REMOTE_HOST:$REMOTE_DIR" true - exit 1 -fi - -# Remove the backup file -BACKUP_SIZE=$(du -m $BACKUP_FILE | cut -f1) -rm $BACKUP_FILE - -# Erase last 7 days backups from remote server -if ! ssh $REMOTE_USER@$REMOTE_HOST "find $REMOTE_DIR -type f -name 'docker_backup_*' -mtime +7 -exec rm {} \;"; then - send_error_notification "⚠️ Failed to clean old backups on remote server: $REMOTE_HOST:$REMOTE_DIR" false -fi - -# Success notification -send_backup_notification "✅ Backup completed successfully for: $SOURCE_DIR to $REMOTE_HOST:$REMOTE_DIR" $BACKUP_SIZE -echo "Backup completed successfully" -exit 0 - diff --git a/clean.sh b/clean.sh index 23a2509..c052feb 100644 --- a/clean.sh +++ b/clean.sh @@ -1,20 +1,8 @@ #!/bin/bash # System Cleanup and Maintenance Script -# -# Description: Comprehensive system cleanup for Docker containers and Linux systems -# Features: -# - Docker resource cleanup (images, containers, volumes, networks) -# - Package manager cache cleanup (APK/APT) -# - System cache and temporary file cleanup -# - Log rotation and cleanup -# - Memory cache optimization -# - Journal cleanup (systemd) -# - Thumbnail and user cache cleanup -# Author: ivanch -# Version: 2.0 -set -euo pipefail # Exit on error, undefined vars, and pipe failures +set -euo pipefail #============================================================================== # CONFIGURATION @@ -35,9 +23,6 @@ readonly JOURNAL_RETENTION_DAYS=7 readonly TEMP_DIRS=("/tmp" "/var/tmp") readonly CACHE_DIRS=("/var/cache" "/root/.cache") -# Auto-update configuration -readonly AUTO_UPDATE_ENABLED=true - #============================================================================== # UTILITY FUNCTIONS #============================================================================== @@ -147,12 +132,6 @@ cleanup_docker() { return 0 fi - # Get initial Docker disk usage - local docker_usage_before="" - if docker system df >/dev/null 2>&1; then - docker_usage_before=$(docker system df 2>/dev/null || echo "") - fi - # Remove unused images log_info "Removing unused Docker images..." if docker image prune -af >/dev/null 2>&1; then @@ -192,11 +171,6 @@ cleanup_docker() { else log_warning "Docker system cleanup failed" fi - - # Show space freed if possible - if [[ -n "$docker_usage_before" ]] && docker system df >/dev/null 2>&1; then - log_info "Docker cleanup completed" - fi } #============================================================================== diff --git a/docker-updater.sh b/docker-updater.sh deleted file mode 100644 index 0bb018d..0000000 --- a/docker-updater.sh +++ /dev/null @@ -1,280 +0,0 @@ -#!/bin/bash - -# Docker Container Updater -# -# Description: Automatically updates Docker containers and manages Docker images -# Features: -# - Updates all Docker Compose projects in /root/docker -# - Skips containers with .ignore file -# - Removes obsolete Docker Compose version attributes -# - Cleans up unused Docker images -# Author: ivanch -# Version: 2.0 - -set -euo pipefail # Exit on error, undefined vars, and pipe failures -HOSTNAME=$(cat /etc/hostname) -NOTIFY_URL_ERROR="http://notify.haven/template/notify/error" -NOTIFY_URL_UPDATE="http://notify.haven/template/notify/update" -send_error_notification() { - local message="$1" - local critical="$2" - curl -s -X POST "$NOTIFY_URL_ERROR" \ - -H "Content-Type: application/json" \ - -d "{\"caller\": \"$HOSTNAME\", \"message\": \"$message\", \"critical\": $critical}" -} -send_update_notification() { - local script_time="$1" - curl -s -X POST "$NOTIFY_URL_UPDATE" \ - -H "Content-Type: application/json" \ - -d "{\"host\": \"$HOSTNAME\", \"asset\": \"Docker containers\", \"time\": $script_time}" -} - -#============================================================================== -# CONFIGURATION -#============================================================================== - -# Color definitions for output formatting -readonly NC='\033[0m' -readonly RED='\033[1;31m' -readonly GREEN='\033[1;32m' -readonly LIGHT_GREEN='\033[1;32m' -readonly LIGHT_BLUE='\033[1;34m' -readonly LIGHT_GREY='\033[0;37m' -readonly YELLOW='\033[1;33m' - -# Script configuration -readonly DOCKER_FOLDER="/root/docker" -readonly COMPOSE_FILES=("docker-compose.yml" "docker-compose.yaml" "compose.yaml" "compose.yml") - -# Auto-update configuration -readonly AUTO_UPDATE_ENABLED=true - -#============================================================================== -# UTILITY FUNCTIONS -#============================================================================== - -# Print formatted log messages -log_info() { echo -e "${LIGHT_GREY}[i] $1${NC}"; } -log_success() { echo -e "${LIGHT_GREEN}[✓] $1${NC}"; } -log_step() { echo -e "${LIGHT_BLUE}[i] $1${NC}"; } -log_container() { echo -e "${LIGHT_BLUE}[$1] $2${NC}"; } - -log_warning() { - echo -e "${YELLOW}[!] $1${NC}"; - send_error_notification "$1" false -} -log_error() { - echo -e "${RED}[x] $1${NC}" >&2; - send_error_notification "$1" true -} - -# Exit with error message -die() { - log_error "$1" - exit 1 -} - -# Check if a command exists -command_exists() { - command -v "$1" >/dev/null 2>&1 -} - -# Check if Docker and Docker Compose are available -check_docker_requirements() { - log_info "Checking Docker requirements..." - - if ! command_exists docker; then - die "Docker is not installed or not in PATH" - fi - - if ! docker compose version >/dev/null 2>&1; then - die "Docker Compose is not available" - fi - - log_success "Docker requirements satisfied" -} - -# Get SHA256 hash of a file -get_file_hash() { - local file="$1" - sha256sum "$file" 2>/dev/null | awk '{print $1}' || echo "" -} - -# Get SHA256 hash from URL content -get_url_hash() { - local url="$1" - curl -s "$url" 2>/dev/null | sha256sum | awk '{print $1}' || echo "" -} - -#============================================================================== -# DOCKER COMPOSE MANAGEMENT -#============================================================================== - -# Find the active Docker Compose file in current directory -find_compose_file() { - for compose_file in "${COMPOSE_FILES[@]}"; do - if [[ -f "$compose_file" ]]; then - echo "$compose_file" - return 0 - fi - done - return 1 -} - -# Remove obsolete version attribute from Docker Compose files -clean_compose_files() { - local container_name="$1" - - for compose_file in "${COMPOSE_FILES[@]}"; do - if [[ -f "$compose_file" ]]; then - log_container "$container_name" "Cleaning obsolete version attribute from $compose_file" - sed -i '/^version:/d' "$compose_file" || log_warning "Failed to clean $compose_file" - fi - done -} - -# Check if container should be skipped -should_skip_container() { - [[ -f ".ignore" ]] -} - -# Check if any containers are running in current directory -has_running_containers() { - local running_containers - running_containers=$(docker compose ps -q 2>/dev/null || echo "") - [[ -n "$running_containers" ]] -} - -# Update a single Docker Compose project -update_docker_project() { - local project_dir="$1" - local container_name - container_name=$(basename "$project_dir") - - log_container "$container_name" "Checking for updates..." - - # Change to project directory - cd "$project_dir" || { - log_error "Cannot access directory: $project_dir" - return 1 - } - - # Check if container should be skipped - if should_skip_container; then - log_container "$container_name" "Skipping (found .ignore file)" - return 0 - fi - - # Verify compose file exists - local compose_file - if ! compose_file=$(find_compose_file); then - log_container "$container_name" "No Docker Compose file found, skipping" - return 0 - fi - - # Clean compose files - clean_compose_files "$container_name" - - # Check if containers are running - if ! has_running_containers; then - log_container "$container_name" "No running containers, skipping update" - return 0 - fi - - # Stop containers - log_container "$container_name" "Stopping containers..." - if ! docker compose down >/dev/null 2>&1; then - log_error "Failed to stop containers in $container_name" - return 1 - fi - - # Pull updated images - log_container "$container_name" "Pulling updated images..." - if ! docker compose pull -q >/dev/null 2>&1; then - log_warning "Failed to pull images for $container_name, attempting to restart anyway" - fi - - # Start containers - log_container "$container_name" "Starting containers..." - if ! docker compose up -d >/dev/null 2>&1; then - log_error "Failed to start containers in $container_name" - return 1 - fi - - log_container "$container_name" "Update completed successfully!" - return 0 -} - -# Update all Docker Compose projects -update_all_docker_projects() { - log_step "Starting Docker container updates..." - - # Check if Docker folder exists - if [[ ! -d "$DOCKER_FOLDER" ]]; then - die "Docker folder not found: $DOCKER_FOLDER" - fi - - # Change to Docker folder - cd "$DOCKER_FOLDER" || die "Cannot access Docker folder: $DOCKER_FOLDER" - - # Process each subdirectory - for project_dir in */; do - if [[ -d "$project_dir" ]]; then - local project_path="$DOCKER_FOLDER/$project_dir" - - update_docker_project "$project_path" - - # Return to Docker folder for next iteration - cd "$DOCKER_FOLDER" || die "Cannot return to Docker folder" - fi - done -} - -#============================================================================== -# DOCKER CLEANUP -#============================================================================== - -# Clean up unused Docker resources -cleanup_docker_resources() { - log_step "Cleaning up unused Docker resources..." - - # Remove unused images - log_info "Removing unused Docker images..." - if docker image prune -af >/dev/null 2>&1; then - log_success "Docker image cleanup completed" - else - log_warning "Docker image cleanup failed" - fi -} - -#============================================================================== -# MAIN EXECUTION -#============================================================================== - -main() { - START_TIME=$(date +%s) - - log_step "Starting Docker Container Updater" - echo - - # Check requirements - check_docker_requirements - - # Update all Docker projects - update_all_docker_projects - - # Clean up Docker resources - cleanup_docker_resources - - echo - log_success "Docker container update process completed!" - - END_TIME=$(date +%s) - DURATION=$((END_TIME - START_TIME)) - log_info "Total duration: $DURATION seconds" - - send_update_notification $DURATION -} - -# Execute main function with all arguments -main "$@" \ No newline at end of file diff --git a/nas-gdrive-backup.sh b/nas-gdrive-backup.sh deleted file mode 100644 index 305cb07..0000000 --- a/nas-gdrive-backup.sh +++ /dev/null @@ -1,292 +0,0 @@ -#!/bin/bash - -# NAS Backup Script to Google Drive using rclone and 7zip -# For each folder on BACKUP_SOURCE, it gets the sha256 checksum of it, checks the checksum against the previous backup, and if it has changed, it creates a 7zip archive of the folder with encryption. -# It then uploads the archive to Google Drive using rclone. - -# Install: curl -sSL https://git.ivanch.me/ivanch/server-scripts/raw/branch/main/nas-gdrive-backup.sh | bash -s -- --install -# Run manually: /usr/local/bin/nas-gdrive-backup.sh - -# Configuration -BACKUP_SOURCE="/export/Backup" -META_DIR="/export/Backup/.gdrive" -TMP_DIR="/export/Backup/.gdrive/tmp" -ZIP_PASSWORD="password" -GDRIVE_REMOTE="gdrive" -GDRIVE_PATH="/NAS-Backups" -ARCHIVE_NAME="backup.7z" -LOG_FILE="/var/log/nas-gdrive-backup.log" - -# Function for logging -log() { - echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" -} - -clean_up() { - log "Cleaning up temporary files" - rm -rf "$TMP_DIR" -} - -trap clean_up EXIT - -create_7z() { - local folder="$1" - local archive_name="$2" - log "Creating 7zip archive of $folder" - 7z a -p"$ZIP_PASSWORD" -mhe=on -mx=3 "$archive_name" "$folder" - if [ $? -ne 0 ]; then - log "ERROR: Failed to create 7zip archive of $folder" - fi -} - -upload_to_gdrive() { - local archive_name="$1" - log "Uploading $archive_name to Google Drive" - - # Should replace existing file if it exists - rclone copy "$archive_name" "$GDRIVE_REMOTE:$GDRIVE_PATH" \ - --progress \ - --check-first \ - --transfers 1 \ - --checkers 1 \ - --retries 1 \ - --low-level-retries 10 - if [ $? -ne 0 ]; then - log "ERROR: Failed to upload $archive_name to Google Drive" - fi -} - -main() { - # Check if 7z is installed - if ! which 7z > /dev/null; then - log "ERROR: 7z is not installed" - exit 1 - fi - - # Check if rclone is installed - if ! which rclone > /dev/null; then - log "ERROR: rclone is not installed" - exit 1 - fi - - # Create meta directory if it doesn't exist - if [ ! -d "$META_DIR" ]; then - log "Creating meta directory: $META_DIR" - mkdir -p "$META_DIR" - fi - - # Fix permissions for the meta directory (777 recursively) - chmod -R 777 "$META_DIR" - - # Loop through each folder in the backup source - for folder in "$BACKUP_SOURCE"/*; do - if [ -d "$folder" ]; then - log "Processing folder: $folder" - # Get the sha256 checksum of the folder - CHECKSUM=$(find "$folder" -type f -exec sha256sum {} + | sha256sum | awk '{print $1}') - META_FILE="$META_DIR/$(basename "$folder").sha256" - # Check if the checksum file exists - if [ -f "$META_FILE" ]; then - # Read the previous checksum from the file - PREV_CHECKSUM=$(cat "$META_FILE") - # Compare the checksums - if [ "$CHECKSUM" != "$PREV_CHECKSUM" ]; then - log "Changes detected in $folder - creating new archive" - - create_7z "$folder" "$TMP_DIR/$(basename "$folder")_$ARCHIVE_NAME" - upload_to_gdrive "$TMP_DIR/$(basename "$folder")_$ARCHIVE_NAME" - - # Update the checksum file - echo "$CHECKSUM" > "$META_FILE" - - # Remove the temporary archive file - log "Removing temporary archive file" - rm "$TMP_DIR/$(basename "$folder")_$ARCHIVE_NAME" - else - log "No changes detected in $folder" - fi - else - log "No previous checksum found for $folder - creating new archive" - - create_7z "$folder" "$TMP_DIR/$(basename "$folder")_$ARCHIVE_NAME" - upload_to_gdrive "$TMP_DIR/$(basename "$folder")_$ARCHIVE_NAME" - - # Create a checksum file for the folder - echo "$CHECKSUM" > "$META_FILE" - - # Remove the temporary archive file - log "Removing temporary archive file" - rm "$TMP_DIR/$(basename "$folder")_$ARCHIVE_NAME" - fi - else - log "Skipping $folder, not a directory" - fi - log "" - done - - # Fix permissions for the meta directory (777 recursively) - chmod -R 777 "$META_DIR" - - log "Backup process completed successfully" - - # Exit with success - exit 0 -} - -########################### -### Installation script ### -########################### -# Function to install a dependency if not already installed -install_dependency() { - local package="$1" - if ! dpkg -l | grep -q "$package"; then - install_log_info "Installing $package" - apt-get update && apt-get install -y "$package" - if [ $? -ne 0 ]; then - install_log_error "ERROR: Failed to install $package" - exit 1 - fi - install_log_ok "$package installed successfully" - else - install_log_ok "$package is already installed" - fi -} - -install_log_ok() { - echo -e "\e[32m[✓]\e[0m $1" -} -install_log_error() { - echo -e "\e[31m[✗]\e[0m $1" -} -install_log_info() { - echo -e "\e[34m[!]\e[0m $1" -} -install_log_separator() { - echo -e "\e[36m========================================\e[0m" -} - -install_script() { - echo -e "" - install_log_separator - install_log_info "Starting installation of NAS to Google Drive backup script" - install_log_separator - echo -e "" - - install_log_separator - # Check if running as root - install_log_info "Checking if the script is running as root" - if [ "$(id -u)" -ne 0 ]; then - install_log_error "ERROR: This script must be run as root" - exit 1 - else - install_log_ok "Running as root" - fi - install_log_separator - - # Check for dependencies - install_log_info "Checking for required dependencies" - install_dependency "rclone" - install_dependency "p7zip-full" - install_log_separator - - # Check if crontab exists - install_log_info "Checking if crontab is installed" - if ! command -v crontab &>/dev/null; then - install_log_error "crontab is not installed" - exit 1 - else - install_log_ok "crontab is installed" - fi - install_log_separator - - install_log_info "Installing script to /usr/local/bin/nas-gdrive-backup.sh" - curl -sSL https://git.ivanch.me/ivanch/server-scripts/raw/branch/main/nas-gdrive-backup.sh -o /usr/local/bin/nas-gdrive-backup.sh - chmod +x /usr/local/bin/nas-gdrive-backup.sh - - install_log_info "Setting up ZIP_PASSWORD in $0" - if [ -z "$ZIP_PASSWORD" ]; then - install_log_error "ERROR: ZIP_PASSWORD is not set" - exit 1 - fi - read -p "Enter ZIP_PASSWORD: " ZIP_PASSWORD /dev/null; then - install_log_error "ERROR: rclone gdrive remote is not configured" - install_log_error "Please run 'rclone config' to set up your Google Drive remote" - exit 1 - fi - install_log_ok "rclone gdrive remote is configured" - install_log_separator - - install_log_info "Setting up cron job for backup script" - (crontab -l 2>/dev/null; echo "55 23 * * 1 /usr/local/bin/nas-gdrive-backup.sh > /tmp/nas-gdrive-backup.log") | crontab - - install_log_ok "Cron job set up to run /usr/local/bin/nas-gdrive-backup.sh every Monday at 23:55" - install_log_separator - - echo -e "" - install_log_separator - install_log_ok "Installation completed successfully!" - install_log_separator - echo -e "" - echo -e "You can now run the script manually with: \e[32mnas-gdrive-backup.sh\e[0m" - echo -e "Or it will run automatically according to the cron schedule." - - # Exit with success - exit 0 -} - -# Check for install flag -if [[ "$1" == "--install" ]]; then - install_script - exit 0 -fi - -main "$@" -exit 0 \ No newline at end of file diff --git a/scripts-download.sh b/scripts-download.sh deleted file mode 100644 index 9187304..0000000 --- a/scripts-download.sh +++ /dev/null @@ -1,330 +0,0 @@ -#!/bin/bash - -# Usage: curl -sSL https://git.ivanch.me/ivanch/server-scripts/raw/branch/main/scripts-download.sh | bash - -set -euo pipefail - -#============================================================================== -# CONFIGURATION -#============================================================================== - -# Color definitions for output formatting -readonly RED='\033[1;31m' -readonly GREEN='\033[1;32m' -readonly NC='\033[0m' -readonly LIGHT_BLUE='\033[1;34m' -readonly LIGHT_RED='\033[1;31m' -readonly LIGHT_GREEN='\033[1;32m' -readonly GREY='\033[1;30m' -readonly YELLOW='\033[1;33m' - -# Configuration -readonly FILES_URL="https://git.ivanch.me/ivanch/server-scripts/raw/branch/main" -readonly REQUIRED_PACKAGES=("zip" "unzip" "curl") -readonly REQUIRED_COMMANDS=("zip" "unzip" "sha256sum" "curl" "crontab") -readonly AVAILABLE_SCRIPTS=("clean.sh" "backup.sh" "docker-updater.sh") - -# Format: [script_name]="cron_schedule" -declare -A CRONTAB_SCHEDULES=( - ["clean.sh"]="0 3 * * *" # Daily at 3 AM - ["backup.sh"]="0 23 * * 1,5" # Monday and Friday at 11 PM - ["docker-updater.sh"]="0 3 * * 6" # Every Saturday at 3 AM -) - -#============================================================================== -# UTILITY FUNCTIONS -#============================================================================== - -# Print formatted log messages -log_info() { echo -e "${GREY}[i] $1${NC}"; } -log_success() { echo -e "${GREEN}[✓] $1${NC}"; } -log_warning() { echo -e "${YELLOW}[!] $1${NC}"; } -log_error() { echo -e "${RED}[x] $1${NC}" >&2; } -log_step() { echo -e "${LIGHT_BLUE}[i] $1${NC}"; } - -# Exit with error message -die() { - log_error "$1" - exit 1 -} - -# Check if a command exists -command_exists() { - command -v "$1" >/dev/null 2>&1 -} - -# Check if a process is running -process_running() { - pgrep "$1" >/dev/null 2>&1 -} - -#============================================================================== -# MAIN FUNCTIONS -#============================================================================== - -# Detect the operating system -detect_operating_system() { - if command_exists apk; then - echo "Alpine" - elif command_exists apt; then - echo "Debian" - else - die "Unsupported operating system. This script supports Alpine and Debian-based systems only." - fi -} - -# Check for missing packages -get_missing_packages() { - local missing=() - - # Check each required command and map to package names - if ! command_exists "zip"; then - missing+=("zip") - fi - if ! command_exists "unzip"; then - missing+=("unzip") - fi - if ! command_exists "curl"; then - missing+=("curl") - fi - # sha256sum is part of coreutils (usually pre-installed) - # crontab is part of cron package, but we'll check for cron service later - - # Only print if there are missing packages - if [[ ${#missing[@]} -gt 0 ]]; then - printf '%s\n' "${missing[@]}" - fi -} - -# Install packages based on the detected OS -install_packages() { - local os="$1" - shift - local packages=("$@") - - if [[ ${#packages[@]} -eq 0 ]]; then - log_info "No packages to install" - return 0 - fi - - log_info "Installing required packages: ${packages[*]}" - log_info "Debug: Installing ${#packages[@]} packages on $os" - - case "$os" in - "Alpine") - log_info "Updating APK package index..." - apk update >/dev/null || die "Failed to update APK package index" - - log_info "Installing packages via APK..." - apk add --no-cache "${packages[@]}" >/dev/null || die "Failed to install packages via APK" - ;; - "Debian") - log_info "Ensuring /var/cache/apt/archives/partial exists..." - mkdir -p /var/cache/apt/archives/partial || die "Failed to create /var/cache/apt/archives/partial" - log_info "Updating APT package index..." - apt-get update -y >/dev/null || die "Failed to update APT package index" - - log_info "Installing packages via APT..." - apt-get install -y "${packages[@]}" >/dev/null || die "Failed to install packages via APT" - ;; - *) - log_error "Debug info - OS variable content: '$os'" - log_error "Debug info - OS variable length: ${#os}" - die "Unknown operating system: '$os'" - ;; - esac -} - -# Verify all required packages are available -verify_packages() { - log_info "Verifying package installation..." - - local missing_packages - readarray -t missing_packages < <(get_missing_packages) - - if [[ ${#missing_packages[@]} -gt 0 ]]; then - log_error "Failed to install required packages: ${missing_packages[*]}" - die "Please install the missing packages manually and try again" - fi - - log_success "All required packages are available" -} - -# Check if crontab service is running -check_crontab_service() { - log_info "Checking crontab service status..." - - if ! process_running "cron"; then - die "Crontab service is not running. Please start the cron service first." - fi - - log_success "Crontab service is running" -} - -# Prompt user to select scripts for installation -select_scripts() { - local selected=() - - echo >&2 # Send to stderr so it doesn't get captured - echo -e "${GREY}[i] Available scripts for download and installation:${NC}" >&2 - echo >&2 - - for script in "${AVAILABLE_SCRIPTS[@]}"; do - local schedule="${CRONTAB_SCHEDULES[$script]:-"0 0 * * *"}" - echo -e " ${LIGHT_BLUE}$script${NC} - Schedule: ${GREY}$schedule${NC}" >&2 - done - - echo >&2 - echo -e "${GREY}[i] Select scripts to download and install:${NC}" >&2 - - for script in "${AVAILABLE_SCRIPTS[@]}"; do - read -p "Install $script? [Y/n]: " choice &2 - exit 1 - fi - - # Only output the selected scripts to stdout - printf '%s\n' "${selected[@]}" -} - -# Verify server connectivity for selected scripts -verify_server_connectivity() { - local scripts=("$@") - - log_info "Verifying server connectivity..." - - for script in "${scripts[@]}"; do - local url="$FILES_URL/$script" - if ! curl -s --head "$url" | head -n 1 | grep -E "HTTP/[12] [23].." >/dev/null; then - die "Script '$script' not found on server: $url" - fi - done - - log_success "Server connectivity verified" -} - -# Download selected scripts -download_scripts() { - local scripts=("$@") - - log_info "Downloading ${#scripts[@]} script(s)..." - - for script in "${scripts[@]}"; do - local url="$FILES_URL/$script" - log_step "Downloading $script..." - - if ! curl -s -o "./$script" "$url"; then - die "Failed to download $script from $url" - fi - - # Set executable permissions - chmod +x "./$script" || die "Failed to set executable permissions for $script" - done - - log_success "All scripts downloaded and configured" -} - -# Setup crontab entries for selected scripts -setup_crontab() { - local scripts=("$@") - local current_workdir - current_workdir=$(pwd) - - log_info "Setting up crontab entries..." - - for script in "${scripts[@]}"; do - local schedule="${CRONTAB_SCHEDULES[$script]:-"0 0 * * *"}" - local log_file="/tmp/${script%.*}.log" - local cron_entry="$schedule $current_workdir/$script > $log_file 2>&1" - - log_step "Configuring crontab for $script (Schedule: $schedule)..." - - # Remove existing crontab entry for this script - if crontab -l 2>/dev/null | grep -q "$script"; then - log_step "Removing existing crontab entry for $script..." - crontab -l 2>/dev/null | grep -v "$script" | crontab - || die "Failed to remove existing crontab entry" - fi - - # Add new crontab entry - (crontab -l 2>/dev/null; echo "$cron_entry") | crontab - || die "Failed to add crontab entry for $script" - - # Verify the entry was added - if ! crontab -l 2>/dev/null | grep -q "$script"; then - die "Failed to verify crontab entry for $script" - fi - - log_success "Crontab configured for $script" - done - - log_success "All crontab entries configured successfully" -} - -#============================================================================== -# MAIN EXECUTION -#============================================================================== - -main() { - log_step "Starting Server Scripts Downloader" - echo - - # System detection and validation - log_info "Detecting operating system..." - local detected_os - detected_os=$(detect_operating_system) - detected_os=$(echo "$detected_os" | tr -d '\n\r' | xargs) # Clean any whitespace/newlines - log_success "Detected $detected_os Linux" - - # Package management - local missing_packages - readarray -t missing_packages < <(get_missing_packages) - - # Filter out empty strings - local filtered_packages=() - for pkg in "${missing_packages[@]}"; do - if [[ -n "$pkg" ]]; then - filtered_packages+=("$pkg") - fi - done - missing_packages=("${filtered_packages[@]}") - - # Debug output - log_info "Debug: Found ${#missing_packages[@]} missing packages" - for i in "${!missing_packages[@]}"; do - log_info "Debug: Missing package $((i+1)): '${missing_packages[i]}'" - done - - if [[ ${#missing_packages[@]} -gt 0 ]]; then - log_warning "Missing packages detected: ${missing_packages[*]}" - install_packages "$detected_os" "${missing_packages[@]}" - else - log_success "All required packages are already installed" - fi - - verify_packages - check_crontab_service - - # Script selection and installation - local selected_scripts - readarray -t selected_scripts < <(select_scripts) - - log_info "Selected scripts: ${selected_scripts[*]}" - - verify_server_connectivity "${selected_scripts[@]}" - download_scripts "${selected_scripts[@]}" - setup_crontab "${selected_scripts[@]}" - - echo - log_success "Installation completed successfully!" - log_info "Scripts have been downloaded to: $(pwd)" - log_info "Crontab entries have been configured. Use 'crontab -l' to view them." - log_info "Log files will be created in /tmp/ directory." -} - -# Execute main function -main "$@" \ No newline at end of file