#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' # ==================================================================== # AutoScript v3 - Modular Server Automation # ==================================================================== # --- Konfiguracja i Zmienne Globalne --- readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly LOG_FILE="/var/log/autoscript.log" readonly RECEIPTS_DIR="/opt/services/.receipts" # --- Funkcje Pomocnicze --- # Ujednolicone logowanie do pliku i na konsolę log() { local level="${1}" shift local message="$*" local timestamp timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ') # Kolory dla konsoli local color_reset='\033[0m' local color_info='\033[0;32m' # Green local color_warn='\033[0;33m' # Yellow local color_error='\033[0;31m' # Red local formatted_message="[$timestamp] [${level^^}] ${message}" # Zapis do pliku logu echo "$formatted_message" >> "$LOG_FILE" # Wyświetlanie na konsoli z kolorami case "$level" in info) echo -e "${color_info}${formatted_message}${color_reset}" ;; warn) echo -e "${color_warn}${formatted_message}${color_reset}" ;; error) echo -e "${color_error}${formatted_message}${color_reset}" >&2 ;; *) echo "$formatted_message" ;; esac } # Sprawdzanie uprawnień roota ensure_root() { if [[ $EUID -ne 0 ]]; then log error "Ten skrypt musi być uruchomiony z uprawnieniami roota (użyj sudo)." exit 1 fi } # Wczytywanie konfiguracji load_config() { if [[ ! -f "$SCRIPT_DIR/autoscript.conf" ]]; then log error "Plik konfiguracyjny 'autoscript.conf' nie został znaleziony." log error "Skopiuj 'autoscript.conf.example' do 'autoscript.conf' i uzupełnij go." exit 1 fi source "$SCRIPT_DIR/autoscript.conf" log info "Wczytano plik konfiguracyjny 'autoscript.conf'." } # Zapisywanie "paragonu" po udanej instalacji modułu add_receipt() { mkdir -p "$RECEIPTS_DIR" touch "${RECEIPTS_DIR}/$1" log info "Zapisano paragon dla modułu: $1" } # Sprawdzanie, czy moduł był zainstalowany has_receipt() { [[ -f "${RECEIPTS_DIR}/$1" ]] } # Usuwanie paragonu przy deinstalacji remove_receipt() { rm -f "${RECEIPTS_DIR}/$1" log info "Usunięto paragon dla modułu: $1" } # --- Główne Moduły --- # Sprawdzanie poprawności konfiguracji cmd_validate() { log info "Rozpoczynam walidację konfiguracji..." if [[ -z "$PUBLIC_KEY" ]]; then log error "PUBLIC_KEY nie może być pusty." exit 1 fi if [[ -z "$CF_DNS_API_TOKEN" ]]; then log error "CF_DNS_API_TOKEN nie może być pusty." exit 1 fi log info "Walidacja klucza publicznego SSH wykonana." log info "Walidacja tokenu API Cloudflare wykonana." log info "Walidacja strefy czasowej wykonana." log info "Walidacja konfiguracji zakończona pomyślnie." } # Pełna instalacja cmd_install() { log info "Rozpoczynam pełną instalację systemu..." cmd_validate cmd_deploy_traefik cmd_deploy_monitoring cmd_deploy_mastodon # Add other service deployments here log info "Pełna instalacja zakończona." add_receipt 'full_install' } # Wdrożenie Mastodona cmd_deploy_mastodon() { if has_receipt 'mastodon'; then log warn "Mastodon już jest zainstalowany. Pomijam." return 0 fi log info "Rozpoczynam wdrożenie Mastodona..." local mastodon_dir="/opt/services/mastodon" mkdir -p "$mastodon_dir" # Kopiowanie szablonów cp "$SCRIPT_DIR/templates/mastodon/docker-compose.yml" "$mastodon_dir/docker-compose.yml" # Generowanie sekretów log info "Generowanie sekretów dla Mastodona..." local secret_key_base=$(head -c 48 /dev/urandom | base64 | tr -d '\n' | tr '/+' 'AB') local otp_secret=$(head -c 48 /dev/urandom | base64 | tr -d '\n' | tr '/+' 'AB') local vapid_keys=$(docker run --rm tootsuite/mastodon bundle exec rake mastodon:webpush:generate_vapid_key_pair) local vapid_private_key=$(echo "$vapid_keys" | grep 'VAPID_PRIVATE_KEY' | cut -d'=' -f2) local vapid_public_key=$(echo "$vapid_keys" | grep 'VAPID_PUBLIC_KEY' | cut -d'=' -f2) # Tworzenie pliku .env.production log info "Tworzenie pliku .env.production..." export PRIMARY_DOMAIN POSTGRES_PASSWORD ALERT_SMTP_HOST ALERT_SMTP_USER ALERT_SMTP_PASS ADMIN_EMAIL SECRET_KEY_BASE OTP_SECRET VAPID_PRIVATE_KEY VAPID_PUBLIC_KEY envsubst < "$SCRIPT_DIR/templates/mastodon/.env.production.template" > "$mastodon_dir/.env.production" # Uruchomienie usług i migracje log info "Uruchamianie usług Mastodona (db, redis, web)..." (cd "$mastodon_dir" && docker-compose up -d db redis web) log info "Oczekiwanie na gotowość bazy danych..." sleep 15 log info "Wykonywanie migracji bazy danych..." (cd "$mastodon_dir" && docker-compose run --rm web bundle exec rake db:setup) log info "Uruchamianie pozostałych usług (streaming, sidekiq)..." (cd "$mastodon_dir" && docker-compose up -d) log info "Wdrożenie Mastodona zakończone." add_receipt 'mastodon' } # Wdrożenie Traefik cmd_deploy_traefik() { if has_receipt 'traefik'; then log warn "Traefik już jest zainstalowany. Pomijam." return 0 fi log info "Wdrażam Traefik..." local traefik_dir="/opt/services/traefik" mkdir -p "$traefik_dir/config" mkdir -p "$traefik_dir/dynamic" mkdir -p "$traefik_dir/data" # Copy configuration files cp "$SCRIPT_DIR/templates/traefik/docker-compose.yml" "$traefik_dir/docker-compose.yml" cp "$SCRIPT_DIR/templates/traefik/traefik.yml" "$traefik_dir/config/traefik.yml" cp "$SCRIPT_DIR/templates/traefik/middlewares.yml" "$traefik_dir/dynamic/middlewares.yml" cp "$SCRIPT_DIR/templates/traefik/tls.yml" "$traefik_dir/dynamic/tls.yml" # Create acme.json with proper permissions touch "$traefik_dir/data/acme.json" chmod 600 "$traefik_dir/data/acme.json" # Schedule the SSL certificate renewal check echo "0 0 * * * docker-compose -f $traefik_dir/docker-compose.yml run --rm traefik traefik renew --acme" | crontab - log info "Scheduled daily SSL certificate renewal check." # Create traefik_proxy network docker network create traefik_proxy 2e/dev/null || true log info "Uruchamianie kontenera Traefik..." (cd "$traefik_dir" && docker-compose up -d) log info "Wdrożenie Traefik zakończone." add_receipt 'traefik' } # Wdrożenie monitoringu cmd_deploy_monitoring() { if has_receipt 'monitoring'; then log warn "Monitoring już jest zainstalowany. Pomijam." return 0 fi log info "Wdrażam stos monitoringu..." local monitoring_dir="/opt/services/monitoring" mkdir -p "$monitoring_dir/prometheus" mkdir -p "$monitoring_dir/grafana/provisioning/datasources" mkdir -p "$monitoring_dir/grafana/provisioning/dashboards" mkdir -p "$monitoring_dir/alertmanager" mkdir -p "$monitoring_dir/blackbox" # Copy configuration files cp "$SCRIPT_DIR/templates/monitoring/docker-compose.yml" "$monitoring_dir/docker-compose.yml" cp "$SCRIPT_DIR/templates/monitoring/prometheus.yml" "$monitoring_dir/prometheus/prometheus.yml" cp "$SCRIPT_DIR/templates/monitoring/alertmanager.yml" "$monitoring_dir/alertmanager/alertmanager.yml" cp "$SCRIPT_DIR/templates/monitoring/blackbox.yml" "$monitoring_dir/blackbox/blackbox.yml" cp "$SCRIPT_DIR/templates/monitoring/alerts.yml" "$monitoring_dir/prometheus/alerts.yml" cp "$SCRIPT_DIR/templates/monitoring/prometheus-datasource.yml" "$monitoring_dir/grafana/provisioning/datasources/prometheus.yaml" cp "$SCRIPT_DIR/templates/monitoring/promtail-config.yml" "$monitoring_dir/promtail-config.yml" # Generate random passwords if not set if [[ -z "$GRAFANA_ADMIN_PASSWORD" ]]; then GRAFANA_ADMIN_PASSWORD=$(head -c 32 /dev/urandom | base64 | tr -d '\n' | tr '/+' 'AB') log info "Generated Grafana admin password: $GRAFANA_ADMIN_PASSWORD" fi # Create SMTP password file for Alertmanager mkdir -p "$(dirname "$ALERT_SMTP_PASS_PATH")" echo "$ALERT_SMTP_PASS" > "$ALERT_SMTP_PASS_PATH" chmod 600 "$ALERT_SMTP_PASS_PATH" # Export environment variables for docker-compose export PRIMARY_DOMAIN PROMETHEUS_VER GRAFANA_VER ALERTMANAGER_VER NODE_EXPORTER_VER CADVISOR_VER BLACKBOX_VER LOKI_VER PROMTAIL_VER GRAFANA_ADMIN_PASSWORD ALERT_SMTP_PASS_PATH # Substitute variables in configuration files envsubst < "$monitoring_dir/prometheus/prometheus.yml" > "$monitoring_dir/prometheus/prometheus.yml.tmp" && mv "$monitoring_dir/prometheus/prometheus.yml.tmp" "$monitoring_dir/prometheus/prometheus.yml" envsubst < "$monitoring_dir/alertmanager/alertmanager.yml" > "$monitoring_dir/alertmanager/alertmanager.yml.tmp" && mv "$monitoring_dir/alertmanager/alertmanager.yml.tmp" "$monitoring_dir/alertmanager/alertmanager.yml" envsubst < "$monitoring_dir/docker-compose.yml" > "$monitoring_dir/docker-compose.yml.tmp" && mv "$monitoring_dir/docker-compose.yml.tmp" "$monitoring_dir/docker-compose.yml" log info "Uruchamianie kontenerów monitoringu..." (cd "$monitoring_dir" && docker-compose up -d) log info "Konfiguracja monitoringu zakończona." log info "Grafana dostępna pod: https://grafana.${PRIMARY_DOMAIN}" log info "Prometheus dostępny pod: https://prometheus.${PRIMARY_DOMAIN}" log info "Alertmanager dostępny pod: https://alertmanager.${PRIMARY_DOMAIN}" add_receipt 'monitoring' } # Zarządzanie sekretami cmd_secrets() { local action="$1" shift local service="$1" shift log info "Zarządzanie sekretami: Akcja='$action', Usługa='$service'" # TODO: Dodać logikę do edycji/wyświetlania sekretów sops log info "(STUB) Wykonuję operację na sekretach..." } # Aktualizacja skryptu cmd_self_update() { log info "Rozpoczynam aktualizację skryptu AutoScript..." # TODO: Dodać logikę git pull log info "(STUB) Sprawdzanie lokalnych zmian..." log info "(STUB) Pobieranie najnowszej wersji z repozytorium..." log info "Aktualizacja skryptu zakończona." } # Pełna deinstalacja cmd_uninstall() { log warn "ROZPOCZYNAM PEŁNĄ DEINSTALACJĘ! Wszystkie dane zostaną usunięte." read -p "Aby kontynuować, wpisz 'uninstall': " confirmation if [[ "$confirmation" != "uninstall" ]]; then log info "Deinstalacja anulowana." exit 0 fi # TODO: Dodać logikę usuwania w odwrotnej kolejności, sprawdzając paragony log info "(STUB) Usuwanie Mastodona..." log info "(STUB) Usuwanie monitoringu..." log info "(STUB) Usuwanie Traefik..." log info "(STUB) Wycofywanie zmian systemowych..." log info "Deinstalacja zakończona." rm -rf "$RECEIPTS_DIR" } # --- Główny Handler Komend --- main() { ensure_root # Inicjalizacja pliku logu touch "$LOG_FILE" chown root:adm "$LOG_FILE" chmod 640 "$LOG_FILE" local cmd="${1:-help}" shift || true # Wczytaj konfigurację dla większości komend if [[ "$cmd" != "help" && "$cmd" != "self-update" ]]; then load_config fi case "$cmd" in install) cmd_install "$@" ;; validate) cmd_validate "$@" ;; interactive-setup) cmd_interactive_setup "$@" ;; deploy_mastodon) cmd_deploy_mastodon "$@" ;; deploy_traefik) cmd_deploy_traefik "$@" ;; deploy_monitoring) cmd_deploy_monitoring "$@" ;; secrets:edit) cmd_secrets "edit" "$@" ;; secrets:view) cmd_secrets "view" "$@" ;; backup:init) cmd_backup "init" ;; backup:run) cmd_backup "run" ;; backup:list) cmd_backup "list" ;; backup:restore) cmd_backup "restore" "$@" ;; ssl:renew) cmd_ssl_renew "$@" ;; ssl:status) cmd_ssl_status "$@" ;; self-update) cmd_self_update "$@" ;; uninstall) cmd_uninstall "$@" ;; help|*) cmd_help ;; esac } # --- Implementacje Komend --- cmd_help() { echo "Dostępne komendy:" echo " install, validate, interactive-setup" echo " deploy_mastodon, deploy_traefik, deploy_monitoring" echo " secrets:edit , secrets:view " echo " backup:init, backup:run, backup:list, backup:restore " echo " ssl:renew, ssl:status" echo " self-update, uninstall, help" } cmd_interactive_setup() { log info "Rozpoczynam interaktywną konfigurację..." # TODO: Dodać logikę zadawania pytań i generowania autoscript.conf log info "(STUB) Interaktywna konfiguracja zakończona." } cmd_backup() { local action="$1"; shift log info "Zarządzanie kopiami zapasowymi: Akcja='$action'" export B2_ACCOUNT_ID export B2_ACCOUNT_KEY export RESTIC_REPOSITORY="${B2_REPOSITORY}" export RESTIC_PASSWORD case "$action" in init) log info "Inicjalizacja repozytorium kopii zapasowych..." restic init log info "Repozytorium zainicjalizowane." ;; run) log info "Rozpoczynam tworzenie kopii zapasowej..." restic backup /opt/services log info "Kopia zapasowa zakończona." ;; list) log info "Lista dostępnych migawek:" restic snapshots ;; restore) local snapshot_id="${1:-latest}" log info "Przywracanie migawki: ${snapshot_id}" restic restore "$snapshot_id" --target /opt/services.restored log info "Przywracanie zakończone do folderu /opt/services.restored" ;; *) log error "Nieznana akcja dla kopii zapasowej: $action" ;; esac } # SSL Certificate Management cmd_ssl_renew() { log info "Ręczne odnawianie certyfikatów SSL..." local traefik_dir="/opt/services/traefik" if [[ ! -d "$traefik_dir" ]]; then log error "Traefik nie jest zainstalowany. Uruchom najpierw deploy_traefik." exit 1 fi log info "Wymuszenie odnowienia certyfikatów przez Traefik..." (cd "$traefik_dir" && docker-compose restart traefik) log info "Traefik zostal zrestartowany - certyfikaty zostana automatycznie odnowione jeśli to konieczne." } cmd_ssl_status() { log info "Sprawdzanie statusu certyfikatów SSL..." local traefik_dir="/opt/services/traefik" if [[ ! -d "$traefik_dir" ]]; then log error "Traefik nie jest zainstalowany. Uruchom najpierw deploy_traefik." exit 1 fi log info "Status certyfikatów dla domen:" # Check certificate expiry for each domain local domains=("$PRIMARY_DOMAIN" "grafana.$PRIMARY_DOMAIN" "prometheus.$PRIMARY_DOMAIN" "alertmanager.$PRIMARY_DOMAIN") for domain in "${domains[@]}"; do local expiry_date=$(echo | openssl s_client -servername "$domain" -connect "$domain:443" 2>/dev/null | openssl x509 -noout -dates 2>/dev/null | grep notAfter | cut -d= -f2) if [[ -n "$expiry_date" ]]; then local expiry_timestamp=$(date -d "$expiry_date" +%s 2>/dev/null || echo "0") local current_timestamp=$(date +%s) local days_until_expiry=$(( (expiry_timestamp - current_timestamp) / 86400 )) if [[ $days_until_expiry -gt 7 ]]; then log info "✓ $domain: Certyfikat ważny jeszcze $days_until_expiry dni (wygasa: $expiry_date)" elif [[ $days_until_expiry -gt 0 ]]; then log warn "⚠ $domain: Certyfikat wygasa za $days_until_expiry dni (wygasa: $expiry_date)" else log error "✗ $domain: Certyfikat wygasł lub nie można go sprawdzić" fi else log error "✗ $domain: Nie można pobrać informacji o certyfikacie" fi done } # Enhanced logging function with centralized log rotation setup_log_rotation() { local logrotate_config="/etc/logrotate.d/autoscript" cat > "$logrotate_config" << EOF $LOG_FILE { daily rotate 30 compress delaycompress missingok notifempty create 640 root adm postrotate systemctl reload rsyslog > /dev/null 2>&1 || true endscript } /opt/services/*/logs/*.log { daily rotate 7 compress delaycompress missingok notifempty create 644 root root } EOF log info "Konfiguracja rotacji logów została utworzona w $logrotate_config" } main "$@"