autoscript/start.sh
Paweł Orzech ba3a481627 updatews
2025-08-03 13:46:44 +02:00

453 lines
16 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 <service>, secrets:view <service>"
echo " backup:init, backup:run, backup:list, backup:restore <snapshot_id>"
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 "$@"