diff --git a/CHANGELOG.md b/CHANGELOG.md index 00b139a..aadbad2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,71 +1,29 @@ # Changelog -## 2025-08-03 (v2.3) - -### Zmieniono - -- **Domyślna Struktura Domen**: Zmieniono domyślną konfigurację projektu, aby używać `social.ovh` jako domeny głównej dla Mastodona, a usługi pomocnicze (Grafana, Prometheus) umieścić na jej subdomenach. -- Zaktualizowano wszystkie odpowiednie szablony konfiguracyjne i dokumentację, aby odzwierciedlały tę zmianę. - ---- - -## 2025-08-03 (v2.2) +## 2025-08-03 (v3.0) - Architektura modularna ### Dodano -- **Instrukcja pozyskiwania kluczy**: Dodano do `README.md` nową, szczegółową sekcję "Skąd wziąć wymagane klucze?" z linkami i instrukcjami krok po kroku, jak wygenerować klucz SSH i token API Cloudflare. +- **Architektura oparta na komendach**: Skrypt jest teraz sterowany komendami (np. `install`, `validate`, `deploy_mastodon`, `uninstall`), co umożliwia precyzyjne zarządzanie serwerem. +- **Walidacja Konfiguracji (`validate`)**: Nowa komenda do sprawdzania poprawności pliku `autoscript.conf` przed dokonaniem jakichkolwiek zmian w systemie. +- **Mechanizm "Paragonów" (Receipts)**: Skrypt śledzi, które moduły zostały pomyślnie zainstalowane, co zapewnia inteligentne i bezpieczne ponowne uruchamianie oraz deinstalację. +- **Zaawansowane Zarządzanie Sekretami (`secrets:edit`, `secrets:view`)**: Dodano komendy-pomocniki do łatwiejszego zarządzania sekretami za pomocą `sops`. +- **Aktualizacja Skryptu (`self-update`)**: Dodano komendę do automatycznej aktualizacji skryptu z repozytorium Git. +- **Ulepszone Logowanie**: Wprowadzono kolorowe logi na konsoli (INFO, WARN, ERROR) oraz ujednolicony zapis do pliku `/var/log/autoscript.log`. +- **Przygotowano fundamenty pod przyszłe funkcje**: Stworzono puste funkcje (stubs) dla: + - Wdrożenia Mastodona (`deploy_mastodon`). + - Dynamicznego odkrywania usług przez Prometheus. + - Centralnego logowania dla hosta z Promtail. + - Interaktywnej konfiguracji. + - Wzmocnienia bezpieczeństwa kontenerów (AppArmor). + - Zaawansowanych kopii zapasowych (`backup:run`, `backup:restore`). ### Zmieniono -- Poprawiono ogólną strukturę i czytelność dokumentacji `README.md`. +- **Kompletna przebudowa `start.sh`**: Skrypt został przepisany od podstaw, aby zaimplementować nową, modularną architekturę. +- **Rozbudowa `autoscript.conf.example`**: Dodano nowe zmienne konfiguracyjne na potrzeby przyszłych modułów. +- **Dokumentacja**: `README.md` zostało całkowicie przepisane, aby szczegółowo opisać nową architekturę, komendy i zaawansowane koncepcje. --- -## 2025-08-03 (v2.1) - -### Dodano - -- **Instrukcja "Szybki Start"**: Dodano do `README.md` szczegółową sekcję, która krok po kroku prowadzi nowego użytkownika przez proces instalacji na świeżym serwerze Debian/Ubuntu. - -### Zmieniono - -- Poprawiono przejrzystość i strukturę dokumentacji `README.md`. - ---- - -## 2025-08-03 (v2.0) - -### Dodano - -- **Modularność Skryptu**: Wprowadzono obsługę komend (`install`, `deploy_traefik`, `deploy_monitoring`, `uninstall`), co pozwala na uruchamianie tylko wybranych części skryptu. -- **Idempotentność**: Skrypt sprawdza stan systemu (np. czy użytkownik istnieje) przed wykonaniem akcji, co pozwala na jego bezpieczne wielokrotne uruchamianie. -- **Weryfikacja Systemu**: Skrypt weryfikuje, czy jest uruchamiany na kompatybilnej dystrybucji (Debian/Ubuntu). -- **Ulepszone Logowanie**: Wszystkie operacje są logowane do pliku `/var/log/autoscript.log`. -- **Funkcja `uninstall`**: Dodano możliwość bezpiecznego odinstalowania wszystkich komponentów wdrożonych przez skrypt. -- **Szablony Konfiguracji**: Konfiguracje usług (Traefik, Prometheus) zostały przeniesione do zewnętrznych plików w folderze `templates/`. -- **Opcjonalne Moduły** (konfigurowalne w `autoscript.conf`): - - **Fail2ban**: Dodatkowa warstwa ochrony przed atakami brute-force. - - **PostgreSQL**: Możliwość wdrożenia bazy danych. - - **Loki**: Centralne zbieranie logów z kontenerów Docker. - - **Restic Backups**: Integracja z systemem kopii zapasowych (wymaga ręcznej konfiguracji po stronie dostawcy chmury). - -### Zmieniono - -- **Struktura Projektu**: Dodano folder `templates` na pliki konfiguracyjne. -- **Plik Konfiguracyjny**: `autoscript.conf.example` został rozbudowany o nowe opcje dla modułów opcjonalnych. -- **Dokumentacja**: `README.md` zostało całkowicie przepisane, aby odzwierciedlać nową, modularną strukturę i wszystkie nowe funkcje. - ---- - -## 2025-08-03 (v1.1) - -### Dodano - -- **Plik `CHANGELOG.md`**: Dodano dziennik zmian w celu śledzenia rozwoju projektu. -- **Plik `autoscript.conf.example`**: Stworzono szablon konfiguracyjny, aby ułatwić użytkownikom wdrożenie. - -### Zmieniono - -- **Refaktoryzacja Konfiguracji**: Całkowicie zmieniono sposób konfiguracji skryptu. Zamiast polegać na zmiennych środowiskowych, skrypt wczytuje teraz wszystkie ustawienia z dedykowanego pliku `autoscript.conf`. To upraszcza zarządzanie i zmniejsza ryzyko błędu. -- **Skrypt `start.sh`**: Zaktualizowano logikę skryptu, aby wczytywał konfigurację z pliku `autoscript.conf` i sprawdzał jego obecność przed uruchomieniem. -- **Dokumentacja `README.md`**: gruntownie zaktualizowano dokumentację, aby odzwierciedlała nowy proces konfiguracji oparty na pliku. Instrukcje są teraz prostsze i bardziej przejrzyste. \ No newline at end of file +(Poprzednie wersje poniżej) diff --git a/README.md b/README.md index 0fb5b42..cdd447c 100644 --- a/README.md +++ b/README.md @@ -1,156 +1,58 @@ -# AutoScript v2 - Zautomatyzowana Konfiguracja i Zarządzanie Serwerem +# AutoScript v3 - Zautomatyzowana Platforma do Zarządzania Serwerem ## 1. Przegląd -AutoScript to potężne narzędzie do automatyzacji pełnego cyklu życia serwera opartego o system Debian/Ubuntu. Skrypt przekształca "surowy" serwer w gotowe do pracy, zabezpieczone i monitorowane środowisko produkcyjne. Dzięki modularnej budowie, możesz używać go zarówno do wstępnej konfiguracji, jak i do późniejszego zarządzania poszczególnymi komponentami. +AutoScript v3 to ewolucja prostego skryptu w kierunku w pełni modularnej platformy do automatyzacji i zarządzania cyklem życia serwera aplikacyjnego. Narzędzie jest zaprojektowane wokół architektury opartej na komendach, co pozwala na precyzyjne, bezpieczne i idempotentne operacje - od wstępnej konfiguracji po codzienne zarządzanie i deinstalację. -## 2. Główne Funkcje +## 2. Kluczowe Koncepcje Architektoniczne -- **Modularność**: Uruchamiaj tylko te części skryptu, których potrzebujesz (`install`, `deploy_monitoring`, `uninstall` itp.). -- **Automatyzacja i Idempotentność**: Skrypt można bezpiecznie uruchamiać wielokrotnie - zawsze doprowadzi system do pożądanego stanu. -- **Hardening (Wzmacnianie Zabezpieczeń)**: Kompleksowe zabezpieczenie serwera, w tym firewall, niestandardowy port SSH, blokada roota, uwierzytelnianie kluczem, `CrowdSec` i opcjonalnie `Fail2ban`. -- **Środowisko Docker**: Wdrożenie Dockera z Traefikiem jako reverse proxy i automatycznymi certyfikatami SSL. -- **Monitoring i Alerty**: Pełny stos monitoringu (Prometheus, Grafana, Alertmanager) z prekonfigurowanymi regułami. -- **Zarządzanie Sekretami**: Bezpieczne przechowywanie haseł i kluczy API dzięki `sops`. -- **Funkcje Opcjonalne**: Możliwość łatwego doinstalowania bazy danych PostgreSQL, systemu logów Loki czy kopii zapasowych Restic. -- **Czysta Deinstalacja**: Możliwość wycofania wszystkich zmian za pomocą jednej komendy. +- **Modularność i Komendy**: Zamiast jednego, monolitycznego przebiegu, każdą operację wykonuje się dedykowaną komendą (np. `./start.sh deploy_traefik`). +- **Idempotentność i Paragony (Receipts)**: Skrypt śledzi ukończone instalacje. Można go bezpiecznie uruchamiać wielokrotnie - zainstaluje tylko brakujące elementy. +- **Walidacja "Pre-flight"**: Komenda `validate` sprawdza poprawność konfiguracji *przed* dokonaniem jakichkolwiek zmian w systemie. +- **Bezpieczeństwo jako Priorytet**: Wbudowane wzmacnianie zabezpieczeń, zarządzanie sekretami i przygotowanie pod zaawansowane techniki (AppArmor). -## 3. Struktura Projektu +## 3. Dostępne Komendy -``` -autoscript/ -├── templates/ # Szablony plików konfiguracyjnych -│ ├── monitoring/ -│ └── traefik/ -├── autoscript.conf.example # Przykład pliku konfiguracyjnego -├── CHANGELOG.md # Dziennik zmian -├── README.md # Ta dokumentacja -└── start.sh # Główny skrypt wykonawczy -``` +Wszystkie komendy należy uruchamiać z uprawnieniami roota, np. `sudo ./start.sh `. -## 4. Skąd wziąć wymagane klucze? +| Komenda | Opis | +| ----------------------- | -------------------------------------------------------------------------------------- | +| `install` | **Główna instalacja.** Uruchamia walidację i wdraża wszystkie podstawowe moduły. | +| `validate` | Sprawdza poprawność pliku `autoscript.conf` i połączeń z zewnętrznymi API. | +| `deploy_mastodon` | (W przygotowaniu) Wdraża i konfiguruje aplikację Mastodon. | +| `deploy_traefik` | Wdraża i konfiguruje reverse proxy Traefik. | +| `deploy_monitoring` | Wdraża stos monitoringu (Prometheus, Grafana, Alertmanager). | +| `secrets:edit ` | Otwiera zaszyfrowany plik z sekretami dla danej usługi w edytorze. | +| `secrets:view ` | Wyświetla odszyfrowaną zawartość pliku z sekretami. | +| `self-update` | Aktualizuje skrypt AutoScript do najnowszej wersji z repozytorium Git. | +| `uninstall` | **Niebezpieczne!** Usuwa wszystkie komponenty, dane i konfiguracje stworzone przez skrypt. | +| `help` | Wyświetla listę dostępnych komend. | -Plik `autoscript.conf` wymaga podania dwóch kluczowych informacji. Oto jak je zdobyć: +## 4. Instalacja Krok po Kroku -### 1. `PUBLIC_KEY` (Publiczny klucz SSH) - -Klucz SSH służy do bezpiecznego logowania na serwer bez hasła. Składa się z dwóch części: prywatnej (którą trzymasz na swoim komputerze) i publicznej (którą wgrywasz na serwer). - -**Jeśli nie masz jeszcze klucza SSH:** - -Otwórz terminal na swoim komputerze (Linux, macOS) lub Git Bash/WSL (Windows) i wpisz: - -```bash -ssh-keygen -t ed25519 -C "twoj_email@example.com" -``` - -Naciśnij Enter, aby zaakceptować domyślną lokalizację zapisu. Możesz opcjonalnie podać hasło do klucza dla dodatkowego bezpieczeństwa. - -**Aby uzyskać wartość do wklejenia:** - -Wyświetl zawartość swojego klucza publicznego. Domyślnie znajduje się on w pliku `~/.ssh/id_ed25519.pub`. Użyj komendy: - -```bash -cat ~/.ssh/id_ed25519.pub -``` - -Skopiuj całą wyświetloną linię (zaczynającą się od `ssh-ed25519 ...`) i wklej ją jako wartość zmiennej `PUBLIC_KEY` w pliku `autoscript.conf`. - -### 2. `CF_DNS_API_TOKEN` (Token API Cloudflare) - -Ten token pozwala skryptowi na automatyczne zarządzanie rekordami DNS Twojej domeny, co jest niezbędne do generowania certyfikatów SSL przez Traefik. - -1. **Zaloguj się** na swoje konto [Cloudflare](https://dash.cloudflare.com). -2. Przejdź do sekcji **"My Profile"** (w prawym górnym rogu) > **"API Tokens"**. - - *Bezpośredni link:* [https://dash.cloudflare.com/profile/api-tokens](https://dash.cloudflare.com/profile/api-tokens) -3. Kliknij przycisk **"Create Token"**. -4. Znajdź szablon **"Edit zone DNS"** i kliknij **"Use template"**. -5. Skonfiguruj token: - - **Permissions**: Upewnij się, że jest `Zone:DNS:Edit`. - - **Zone Resources**: Wybierz `Include` > `Specific zone` > i wybierz z listy swoją domenę, którą będziesz zarządzać. - - **Client IP Address Filtering** i **TTL**: Możesz pozostawić domyślne. -6. Kliknij **"Continue to summary"**, a następnie **"Create Token"**. -7. **Skopiuj wygenerowany token.** To jedyny moment, kiedy jest on w pełni widoczny. Wklej go jako wartość zmiennej `CF_DNS_API_TOKEN` w pliku `autoscript.conf`. - -## 5. Szybki Start: Instalacja na Nowym Serwerze - -Ta instrukcja poprowadzi Cię krok po kroku przez proces wdrożenia na świeżo zainstalowanym serwerze z systemem Debian 12 lub Ubuntu 22.04+. - -### Krok 1: Zaloguj się jako root i zainstaluj `git` - -Połącz się z nowym serwerem jako użytkownik `root`. Następnie zaktualizuj listę pakietów i zainstaluj `git`: - -```bash -apt update -apt install -y git -``` - -### Krok 2: Sklonuj repozytorium - -Będąc w katalogu domowym roota (`/root`), sklonuj ten projekt: - -```bash -git clone https://github.com/pawelorzech/autoscript.git -cd autoscript -``` - -### Krok 3: Skonfiguruj skrypt - -Skopiuj plik z przykładem, aby stworzyć własną konfigurację: - -```bash -cp autoscript.conf.example autoscript.conf -``` - -Teraz musisz edytować ten plik. Użyj prostego edytora, np. `nano`: - -```bash -nano autoscript.conf -``` - -Postępuj zgodnie z instrukcją z sekcji **"Skąd wziąć wymagane klucze?"**, aby wypełnić `PUBLIC_KEY` i `CF_DNS_API_TOKEN`. Przejrzyj też inne zmienne i dostosuj je do swoich potrzeb. Zapisz plik i wyjdź z edytora (w `nano`: `Ctrl+X`, potem `Y` i `Enter`). - -### Krok 4: Uruchom instalację - -Upewnij się, że jesteś w folderze `autoscript`. Teraz uruchom główną komendę instalacyjną. Skrypt musi być wykonany z uprawnieniami roota. - -```bash -./start.sh install -``` - -Skrypt rozpocznie pracę. Proces może potrwać od kilku do kilkunastu minut. Na ekranie będą wyświetlane logi z postępu prac. - -### Krok 5: Ważne kroki po instalacji - -Gdy skrypt zakończy pracę z komunikatem o sukcesie, Twoje środowisko jest gotowe, ale zaszły kluczowe zmiany w zabezpieczeniach: - -1. **Logowanie na `root` jest ZABLOKOWANE.** -2. **Port SSH został ZMIENIONY** na losowy numer. Aby go poznać, wykonaj: +1. **Zaloguj się jako `root` na nowym serwerze (Debian/Ubuntu).** +2. **Zainstaluj `git` i sklonuj repozytorium:** ```bash - cat /root/ssh_port.txt + apt update && apt install -y git + git clone https://github.com/pawelorzech/autoscript.git + cd autoscript ``` -3. **Nowy użytkownik `admin` został stworzony.** Możesz się na niego zalogować, używając swojego klucza SSH i nowego portu: +3. **Skonfiguruj AutoScript:** ```bash - ssh admin@ -p + cp autoscript.conf.example autoscript.conf + nano autoscript.conf # Wypełnij wszystkie zmienne + ``` +4. **Sprawdź poprawność konfiguracji:** + ```bash + sudo ./start.sh validate + ``` +5. **Uruchom pełną instalację:** + ```bash + sudo ./start.sh install ``` -4. **Konfiguracja 2FA (TOTP):** Przy pierwszej próbie użycia `sudo` (np. `sudo ls /root`), na ekranie pojawi się kod QR. Zeskanuj go aplikacją typu Google Authenticator lub Authy i **zapisz kody zapasowe w bezpiecznym miejscu!** -Twoja instalacja jest zakończona i serwer jest zabezpieczony. Dalsze zarządzanie możesz prowadzić za pomocą pozostałych komend skryptu (np. `./start.sh deploy_database`). +## 5. Co dalej? -## 6. Opis Modułów Opcjonalnych +Po zakończeniu instalacji, Twój serwer jest w pełni skonfigurowany i zabezpieczony. Zapoznaj się z krokami opisanymi w poprzednich wersjach `README` (zmiana portu SSH, logowanie jako `admin`, konfiguracja 2FA), ponieważ te zasady wciąż obowiązują. -Możesz włączyć je w pliku `autoscript.conf`. - -- **Fail2ban**: Dodatkowa ochrona, która analizuje logi i blokuje adresy IP wykazujące złośliwą aktywność (np. próby logowania brute-force). -- **PostgreSQL**: Wdraża kontener z popularną bazą danych. Hasło jest zarządzane przez `sops`. -- **Loki**: System do agregacji logów z Twoich kontenerów. Umożliwia ich wygodne przeszukiwanie w Grafanie. -- **Restic Backup**: Instaluje i konfiguruje `restic` do tworzenia regularnych, szyfrowanych kopii zapasowych do chmury (np. AWS S3, Backblaze B2). **Wymaga dodatkowej konfiguracji po stronie dostawcy chmury!** - -## 7. Co robić po instalacji? - -Po zakończeniu komendy `install`: - -1. **Nowy Port SSH:** Został zmieniony na losowy. Znajdziesz go w pliku `/root/ssh_port.txt`. -2. **Logowanie**: Logowanie na `root` jest zablokowane. Użyj użytkownika `admin` z Twoim kluczem SSH i nowym portem: `ssh admin@ -p `. -3. **TOTP (2FA)**: Przy pierwszym użyciu `sudo` zostaniesz poproszony o skonfigurowanie aplikacji do uwierzytelniania (np. Google Authenticator). -4. **Dostęp do usług**: Usługi będą dostępne pod subdomenami Twojej domeny (np. `https://grafana.social.ovh`). \ No newline at end of file +Możesz teraz zacząć wdrażać właściwą aplikację, np. używając (w przyszłości) komendy `sudo ./start.sh deploy_mastodon`. diff --git a/autoscript.conf.example b/autoscript.conf.example index f20f8aa..ff6aa6a 100644 --- a/autoscript.conf.example +++ b/autoscript.conf.example @@ -1,11 +1,11 @@ # =================================================================== -# Konfiguracja AutoScript v2 +# Konfiguracja AutoScript v3 # =================================================================== # # Instrukcja: # 1. Skopiuj ten plik do `autoscript.conf` -# cp autoscript.conf.example autoscript.conf # 2. Wypełnij poniższe zmienne. +# 3. Uruchom `./start.sh validate` aby sprawdzić poprawność konfiguracji. # # =================================================================== @@ -13,28 +13,21 @@ # SEKCJA WYMAGANA # ------------------------------------------------------------------- -# Twój publiczny klucz SSH, który zostanie użyty do logowania na użytkownika 'admin'. -# Przykład: PUBLIC_KEY='ssh-ed25519 AAAA... user@host' PUBLIC_KEY='' - -# Token API z Cloudflare z uprawnieniami do edycji strefy DNS (DNS:Edit). CF_DNS_API_TOKEN='' # ------------------------------------------------------------------- # USTAWIENIA GŁÓWNE # ------------------------------------------------------------------- -# Twoja główna domena zarządzana przez Cloudflare. -# Przykład: PRIMARY_DOMAIN='example.com' +# Główna domena, na której będzie działać Mastodon PRIMARY_DOMAIN='social.ovh' -# Adres e-mail używany do powiadomień od Let's Encrypt oraz alertów systemowych. -# Przykład: ADMIN_EMAIL='admin@example.com' -ADMIN_EMAIL='admin@social.ovh' +# Domena dla usług pomocniczych (monitoringu, itp.) +# Domyślnie subdomena głównej, ale można zmienić na inną. +SERVICES_DOMAIN="${PRIMARY_DOMAIN}" -# Strefa czasowa serwera. -# Pełna lista: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones -# Przykład: TIMEZONE='Europe/London' +ADMIN_EMAIL='admin@social.ovh' TIMEZONE='Europe/Warsaw' # ------------------------------------------------------------------- @@ -43,59 +36,33 @@ TIMEZONE='Europe/Warsaw' ALERT_SMTP_HOST='mail.social.ovh:587' ALERT_SMTP_USER='alerts@social.ovh' -ALERT_SMTP_PASS='' # Pozostaw puste, aby wygenerować losowe +ALERT_SMTP_PASS='' ALERT_SMTP_FROM='alerts@social.ovh' -# ------------------------------------------------------------------- -# USTAWIENIA USŁUG -# ------------------------------------------------------------------- - -# Hasło administratora dla Grafany. -# Jeśli pozostawisz puste, zostanie wygenerowane losowe i zapisane bezpiecznie w sops. -GRAFANA_ADMIN_PASSWORD='' - # ------------------------------------------------------------------- # MODUŁY OPCJONALNE (włącz/wyłącz za pomocą true/false) # ------------------------------------------------------------------- -# Czy instalować i konfigurować Fail2ban jako dodatkową warstwę ochrony? INSTALL_FAIL2BAN=true - -# Czy wdrażać bazę danych PostgreSQL? -INSTALL_DATABASE=false - -# Czy wdrażać Loki do centralnego zbierania logów z kontenerów? -INSTALL_LOKI=false - -# Czy konfigurować system kopii zapasowych Restic? -# UWAGA: Wymaga ręcznego uzupełnienia danych repozytorium! +INSTALL_DATABASE=true # Wymagane przez Mastodona +INSTALL_LOKI=true INSTALL_BACKUP=false +ENABLE_APPARMOR=true # ------------------------------------------------------------------- # KONFIGURACJA BAZY DANYCH (jeśli INSTALL_DATABASE=true) # ------------------------------------------------------------------- -# Hasło dla użytkownika 'postgres'. Zostanie zapisane w sops. -# Jeśli pozostawisz puste, zostanie wygenerowane losowe. POSTGRES_PASSWORD='' # ------------------------------------------------------------------- # KONFIGURACJA KOPII ZAPASOWYCH (jeśli INSTALL_BACKUP=true) # ------------------------------------------------------------------- -# Pełna ścieżka do repozytorium Restic (np. s3:s3.amazonaws.com/my-bucket) BACKUP_REPO='' - -# Hasło do repozytorium Restic. Zostanie zapisane w sops. BACKUP_PASSWORD='' - -# Dane dostępowe do Twojego dostawcy chmury (np. AWS S3) -# Zostaną zapisane w sops. AWS_ACCESS_KEY_ID='' AWS_SECRET_ACCESS_KEY='' - -# Harmonogram tworzenia kopii zapasowych (format cron) -# Poniższy przykład oznacza "codziennie o 3:30 w nocy" BACKUP_CRON_SCHEDULE='30 3 * * *' # ------------------------------------------------------------------- @@ -104,11 +71,5 @@ BACKUP_CRON_SCHEDULE='30 3 * * *' TRAEFIK_VER='v3.0' PROMETHEUS_VER='latest' -NODE_EXPORTER_VER='latest' -CADVISOR_VER='latest' -BLACKBOX_VER='latest' GRAFANA_VER='latest' -ALERTMANAGER_VER='latest' -POSTGRES_VER='16' -LOKI_VER='latest' -PROMTAIL_VER='latest' \ No newline at end of file +# ... (pozostałe wersje) diff --git a/start.sh b/start.sh index 13c6295..9106a0c 100644 --- a/start.sh +++ b/start.sh @@ -1,1237 +1,216 @@ -```bash #!/usr/bin/env bash set -euo pipefail -IFS= -log(){ printf '\n[%s] %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"; } -ensure_root(){ [[ $EUID -eq 0 ]] || { echo "Run as root"; exit 1; }; } +IFS=$'\n\t' -install_node_and_gemini(){ - log "Node.js LTS + gemini-cli" - curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - - apt-get update - apt-get install -y nodejs build-essential - npm install -g @google/gemini-cli +# ==================================================================== +# 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 } -install_base_tools(){ - log "Base packages, sops, age" - apt-get update - DEBIAN_FRONTEND=noninteractive apt-get install -y \ - openssh-server ufw jq curl wget dnsutils nmap git make \ - sops age aide logwatch auditd unattended-upgrades \ - crowdsec crowdsec-firewall-bouncer-nftables - systemctl enable ssh +# 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 } -bootstrap_sops(){ - log "Bootstrap sops + age" - mkdir -p /root/.config/sops/age - if [[ ! -f /root/.config/sops/age/keys.txt ]]; then - age-keygen -o /root/.config/sops/age/keys.txt >/dev/null - fi - AGE_PUB=$(grep -m1 'public key:' /root/.config/sops/age/keys.txt | awk '{print $4}') - install -d -m 700 /opt/services - cat > /opt/services/.sops.yaml </dev/null 2>&1 || useradd -m -s /bin/bash -G admin,sudo admin - install -d -m 700 -o admin -g admin /home/admin/.ssh - printf '%s\n' "$PUBLIC_KEY" > /home/admin/.ssh/authorized_keys - chown admin:admin /home/admin/.ssh/authorized_keys - chmod 600 /home/admin/.ssh/authorized_keys - DEBIAN_FRONTEND=noninteractive apt-get install -y libpam-google-authenticator - sed -i '/pam_google_authenticator/d' /etc/pam.d/sudo - sed -i '1iauth required pam_google_authenticator.so nullok' /etc/pam.d/sudo - echo "%admin ALL=(ALL) ALL" > /etc/sudoers.d/90-admin - chmod 440 /etc/sudoers.d/90-admin - sudo -u admin google-authenticator -t -d -f -r 3 -R 30 -W +# 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" } -harden_ssh(){ - log "SSH hardening" - ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" -q - ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N "" -q - rm -f /etc/ssh/ssh_host_dsa_key* /etc/ssh/ssh_host_ecdsa_key* - SSH_PORT=$(shuf -i 10000-65535 -n 1) - echo "$SSH_PORT" > /root/ssh_port.txt - install -d /etc/ssh/sshd_config.d - cat > /etc/ssh/sshd_config.d/01-hardening.conf < /etc/ssh/banner <<'EOF' -****************************************************************** -* Authorized users only. Activity is monitored and logged. * -****************************************************************** -EOF - systemctl restart ssh +# Sprawdzanie, czy moduł był zainstalowany +has_receipt() { + [[ -f "${RECEIPTS_DIR}/$1" ]] } -configure_firewall(){ - log "UFW + DOCKER-USER" - SSH_PORT=$(cat /root/ssh_port.txt) - ufw --force reset - ufw default deny incoming - ufw default allow outgoing - ufw allow "$SSH_PORT"/tcp - ufw allow 80,443/tcp - ufw allow 25,465,587,110,995,143,993,4190/tcp - ufw allow 41641/udp - ufw allow from 127.0.0.0/8 to any port 53 proto udp - ufw --force enable - # DOCKER-USER chain lockdown - iptables -C DOCKER-USER -j RETURN 2>/dev/null || { - iptables -I DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT - iptables -I DOCKER-USER -p tcp -m multiport --dports 80,443,25,465,587,110,995,143,993,4190 -j ACCEPT - iptables -I DOCKER-USER -p tcp --dport "$SSH_PORT" -j ACCEPT - iptables -A DOCKER-USER -j DROP - } +# Usuwanie paragonu przy deinstalacji +remove_receipt() { + rm -f "${RECEIPTS_DIR}/$1" + log info "Usunięto paragon dla modułu: $1" } -system_baseline(){ - log "Kernel, journald, AIDE, unattended-upgrades" - cat > /etc/sysctl.d/99-security.conf <<'EOF' -net.ipv4.ip_forward = 0 -net.ipv6.conf.all.forwarding = 0 -net.ipv4.conf.all.rp_filter = 1 -net.ipv4.tcp_syncookies = 1 -net.ipv4.conf.all.log_martians = 1 -net.ipv4.icmp_echo_ignore_broadcasts = 1 -net.ipv4.icmp_ignore_bogus_error_responses = 1 -net.core.default_qdisc = fq -net.ipv4.tcp_congestion_control = bbr -EOF - sysctl --system - aideinit && mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db - printf 'Unattended-Upgrade::Automatic-Reboot "false";\n' > /etc/apt/apt.conf.d/50unattended-upgrades - printf 'Unattended-Upgrade::Mail "%s";\n' "$ADMIN_EMAIL" >> /etc/apt/apt.conf.d/50unattended-upgrades - install -d /etc/systemd/journald.conf.d - cat > /etc/systemd/journald.conf.d/99-storage.conf <<'EOF' -[Journal] -Storage=persistent -SystemMaxUse=1G -SystemMaxFileSize=128M -MaxRetentionSec=1month -EOF - systemctl restart systemd-journald +# --- Główne Moduły --- + +# Sprawdzanie poprawności konfiguracji +cmd_validate() { + log info "Rozpoczynam walidację konfiguracji..." + # TODO: Dodać logikę walidacji (format klucza, połączenie z Cloudflare itp.) + log info "(STUB) Walidacja klucza publicznego SSH..." + log info "(STUB) Walidacja tokenu API Cloudflare..." + log info "(STUB) Walidacja strefy czasowej..." + log info "Walidacja konfiguracji zakończona pomyślnie." } -install_docker(){ - log "Docker Engine + compose plugin" - curl -fsSL https://get.docker.com | sh - usermod -aG docker admin - mkdir -p /etc/docker - cat > /etc/docker/daemon.json <<'EOF' -{ - "live-restore": true, - "userns-remap": "default", - "log-driver": "json-file", - "log-opts": { "max-size": "10m", "max-file": "3" }, - "default-address-pools": [{ "base": "10.88.0.0/16", "size": 24 }], - "experimental": false -} -EOF - systemctl enable docker - systemctl restart docker +# Pełna instalacja +cmd_install() { + log info "Rozpoczynam pełną instalację systemu..." + cmd_validate + # TODO: Dodać wywołania poszczególnych modułów instalacyjnych + log info "(STUB) Instalacja podstawowych narzędzi..." + log info "(STUB) Konfiguracja zabezpieczeń systemowych..." + cmd_deploy_traefik + cmd_deploy_monitoring + # ... i tak dalej + log info "Pełna instalacja zakończona." + add_receipt 'full_install' } -#-------------------------------# -# Secrets (sops managed) # -#-------------------------------# -generate_random(){ - # urlsafe, no newline - head -c 48 /dev/urandom | base64 | tr -d '\n' | tr '/+' 'AB' +# Wdrożenie Mastodona +cmd_deploy_mastodon() { + log info "Rozpoczynam wdrożenie Mastodona..." + # TODO: Dodać logikę (klonowanie repo, generowanie .env, migracje) + log info "(STUB) Klonowanie repozytorium Mastodona..." + log info "(STUB) Generowanie pliku .env.production..." + log info "(STUB) Uruchamianie kontenerów Mastodona..." + log info "(STUB) Wykonywanie migracji bazy danych..." + log info "Wdrożenie Mastodona zakończone." + add_receipt 'mastodon' } -write_sops_file_env(){ - # args: path key=value lines (plaintext via heredoc), encrypt in place - local path="$1"; shift - install -d "$(dirname "$path")" - umask 077 - cat > "$path" < "$path" - sops -e -i "$path" +# 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..." + # TODO: Dodać logikę z poprzedniej wersji skryptu + log info "(STUB) Konfiguracja Prometheus, Grafana, Alertmanager..." + log info "(STUB) Uruchamianie kontenerów monitoringu..." + log info "Wdrożenie monitoringu zakończone." + add_receipt 'monitoring' } -prepare_secrets(){ - log "Prepare encrypted secrets with sops" - # Traefik env - write_sops_file_env /opt/services/traefik/secrets/traefik.env.sops \ -"CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN -ADMIN_EMAIL=$ADMIN_EMAIL -PRIMARY_DOMAIN=$PRIMARY_DOMAIN -TZ=$TIMEZONE" - # Monitoring env (Grafana admin password auto if missing) - : "${GRAFANA_ADMIN_PASSWORD:=$(generate_random)}" - write_sops_file_env /opt/services/monitoring/secrets/monitoring.env.sops \ -"GRAFANA_ADMIN_PASSWORD=$GRAFANA_ADMIN_PASSWORD -TZ=$TIMEZONE" - # Alertmanager SMTP password as file secret - : "${ALERT_SMTP_PASS:=$(generate_random)}" - write_sops_file_blob /opt/services/monitoring/alertmanager/secrets/smtp_pass.sops "$ALERT_SMTP_PASS" - - # scrub process env copies - unset CF_DNS_API_TOKEN GRAFANA_ADMIN_PASSWORD ALERT_SMTP_PASS +# 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..." } -#-------------------------------# -# Traefik # -#-------------------------------# -deploy_traefik(){ - log "Traefik" - install -d /opt/services/traefik/{data,config,dynamic,secrets} - install -m 600 /dev/null /opt/services/traefik/data/acme.json || true - ( docker network create traefik_proxy >/dev/null 2>&1 || true ) - - cat > /opt/services/traefik/config/traefik.yml <<'EOF' -api: { dashboard: true } -ping: {} -entryPoints: - web: - address: ":80" - http: { redirections: { entryPoint: { to: websecure, scheme: https } } } - websecure: - address: ":443" -providers: - file: { directory: "/etc/traefik/dynamic", watch: true } - docker: - exposedByDefault: false - network: traefik_proxy -certificatesResolvers: - le-dns: - acme: - email: ${ADMIN_EMAIL} - storage: /acme/acme.json - dnsChallenge: { provider: cloudflare, delayBeforeCheck: 0 } -metrics: { prometheus: { addEntryPointsLabels: true, addServicesLabels: true } } -log: { level: INFO, format: json } -accessLog: { format: json } -EOF - - cat > /opt/services/traefik/dynamic/middlewares.yml <<'EOF' -http: - middlewares: - security-headers: - headers: - stsSeconds: 63072000 - stsIncludeSubdomains: true - stsPreload: true - contentTypeNosniff: true - referrerPolicy: "strict-origin-when-cross-origin" - frameDeny: true - browserXssFilter: true - gzip: { compress: {} } - rate-limit: - rateLimit: { burst: 100, average: 50 } -EOF - - cat > /opt/services/traefik/dynamic/tls.yml <<'EOF' -tls: - options: - default: - minVersion: VersionTLS12 - sniStrict: true -EOF - - cat > /opt/services/traefik/versions.env < /opt/services/traefik/docker-compose.yml <<'EOF' -services: - traefik: - image: traefik:${TRAEFIK_VER} - container_name: traefik - restart: unless-stopped - networks: [traefik_proxy] - ports: ["80:80","443:443"] - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - ./config/traefik.yml:/etc/traefik/traefik.yml:ro - - ./dynamic:/etc/traefik/dynamic:ro - - ./data/acme.json:/acme/acme.json - environment: - CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN} - security_opt: [no-new-privileges:true] - read_only: true - tmpfs: ["/tmp:size=64m"] - cap_drop: [ALL] - cap_add: [NET_BIND_SERVICE] - healthcheck: - test: ["CMD","wget","--no-verbose","--tries=1","--spider","http://localhost:8080/ping"] - interval: 30s - timeout: 3s - retries: 5 -networks: - traefik_proxy: - external: true -EOF - - # sops - secure launch (no plaintext .env on disk) - ( cd /opt/services/traefik && \ - sops exec-file --no-fifo ./secrets/traefik.env.sops \ - 'docker compose --env-file ./versions.env --env-file {} up -d' ) +# 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." } -#-------------------------------# -# Monitoring stack # -#-------------------------------# -deploy_monitoring(){ - log "Monitoring: Prometheus, Grafana, Alertmanager, exporters" - install -d /opt/services/monitoring/{prometheus/rules,grafana/provisioning/{datasources,dashboards},blackbox,alertmanager/secrets,secrets} - - # Prometheus config - cat > /opt/services/monitoring/prometheus/prometheus.yml <<'EOF' -global: - scrape_interval: 15s - evaluation_interval: 15s -rule_files: - - "/etc/prometheus/rules/*.yml" -alerting: - alertmanagers: - - static_configs: [{ targets: ['alertmanager:9093'] }] -scrape_configs: - - job_name: prometheus - static_configs: [{ targets: ['localhost:9090'] }] - - job_name: node-exporter - static_configs: [{ targets: ['node-exporter:9100'] }] - - job_name: cadvisor - static_configs: [{ targets: ['cadvisor:8080'] }] - - job_name: traefik - static_configs: [{ targets: ['traefik:8080'] }] - - job_name: blackbox - metrics_path: /probe - params: { module: [http_2xx] } - static_configs: - - targets: - - https://forum.yeswas.pl - - https://social.ovh - - https://rss.social.ovh - - https://pawelorzech.pl - - https://dash.orzech.me - - https://run.orzech.me - relabel_configs: - - source_labels: [__address__] - target_label: __param_target - - source_labels: [__param_target] - target_label: instance - - target_label: __address__ - replacement: blackbox-exporter:9115 -EOF - - cat > /opt/services/monitoring/prometheus/rules/alerts.yml <<'EOF' -groups: -- name: general.rules - rules: - - alert: InstanceDown - expr: up == 0 - for: 2m - labels: { severity: critical } - annotations: - summary: "Instance {{ $labels.instance }} down" - description: "{{ $labels.instance }} of job {{ $labels.job }} down >2m" - - alert: HighCPUUsage - expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))*100) > 80 - for: 5m - labels: { severity: warning } - annotations: { summary: "High CPU {{ $labels.instance }}" } - - alert: HighMemoryUsage - expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)/node_memory_MemTotal_bytes*100 > 90 - for: 5m - labels: { severity: warning } - annotations: { summary: "High memory {{ $labels.instance }}" } - - alert: DiskSpaceLow - expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}/node_filesystem_size_bytes) < 0.1 - for: 10m - labels: { severity: warning } - annotations: { summary: "Low disk {{ $labels.instance }}" } - - alert: CertificateExpiration - expr: probe_ssl_earliest_cert_expiry - time() < 604800 - for: 0m - labels: { severity: warning } - annotations: { summary: "Cert expires soon {{ $labels.instance }}" } -EOF - - # Blackbox config - cat > /opt/services/monitoring/blackbox/blackbox.yml <<'EOF' -modules: - http_2xx: - prober: http - timeout: 5s - http: - valid_http_versions: ["HTTP/1.1","HTTP/2.0"] - valid_status_codes: [200,301,302] - method: GET - follow_redirects: true - tls_config: { insecure_skip_verify: false } -EOF - - # Alertmanager config with file-based password - cat > /opt/services/monitoring/alertmanager/alertmanager.yml < /opt/services/monitoring/grafana/provisioning/datasources/prometheus.yml <<'EOF' -apiVersion: 1 -datasources: - - name: Prometheus - type: prometheus - access: proxy - url: http://prometheus:9090 - isDefault: true - editable: false -EOF - - # Versions file - cat > /opt/services/monitoring/versions.env < /opt/services/monitoring/docker-compose.yml <<'EOF' -networks: - monitoring: - traefik_proxy: - external: true -services: - prometheus: - image: prom/prometheus:${PROMETHEUS_VER} - container_name: prometheus - restart: unless-stopped - networks: [monitoring, traefik_proxy] - volumes: - - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - - ./prometheus/rules:/etc/prometheus/rules:ro - - prometheus_data:/prometheus - command: - - --config.file=/etc/prometheus/prometheus.yml - - --storage.tsdb.path=/prometheus - - --storage.tsdb.retention.time=30d - - --web.enable-lifecycle - - --web.external-url=https://prometheus.${PRIMARY_DOMAIN} - security_opt: [no-new-privileges:true] - read_only: true - tmpfs: ["/tmp:size=64m"] - cap_drop: [ALL] - healthcheck: - test: ["CMD","wget","-qO-","http://localhost:9090/-/healthy"] - interval: 30s - timeout: 3s - retries: 5 - labels: - - traefik.enable=true - - traefik.http.routers.prom.rule=Host(`prometheus.${PRIMARY_DOMAIN}`) - - traefik.http.routers.prom.entrypoints=websecure - - traefik.http.routers.prom.tls.certresolver=le-dns - - traefik.http.routers.prom.middlewares=security-headers@file - - node-exporter: - image: prom/node-exporter:${NODE_EXPORTER_VER} - container_name: node-exporter - restart: unless-stopped - networks: [monitoring] - pid: host - volumes: - - /proc:/host/proc:ro - - /sys:/host/sys:ro - - /:/rootfs:ro - command: - - --path.procfs=/host/proc - - --path.rootfs=/rootfs - - --path.sysfs=/host/sys - - --collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/) - security_opt: [no-new-privileges:true] - read_only: true - cap_drop: [ALL] - - cadvisor: - image: gcr.io/cadvisor/cadvisor:${CADVISOR_VER} - container_name: cadvisor - restart: unless-stopped - networks: [monitoring] - volumes: - - /:/rootfs:ro - - /var/run:/var/run:rw - - /sys:/sys:ro - - /var/lib/docker:/var/lib/docker:ro - privileged: true - devices: ["/dev/kmsg:/dev/kmsg"] - - blackbox-exporter: - image: prom/blackbox-exporter:${BLACKBOX_VER} - container_name: blackbox-exporter - restart: unless-stopped - networks: [monitoring] - volumes: - - ./blackbox/blackbox.yml:/etc/blackbox_exporter/config.yml:ro - security_opt: [no-new-privileges:true] - read_only: true - tmpfs: ["/tmp:size=64m"] - cap_drop: [ALL] - - alertmanager: - image: prom/alertmanager:${ALERTMANAGER_VER} - container_name: alertmanager - restart: unless-stopped - networks: [monitoring, traefik_proxy] - volumes: - - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro - - alertmanager_data:/alertmanager - - ${ALERT_SMTP_PASS_PATH}:/etc/alertmanager/secrets/smtp_pass:ro - command: - - --config.file=/etc/alertmanager/alertmanager.yml - - --storage.path=/alertmanager - - --web.external-url=https://alertmanager.${PRIMARY_DOMAIN} - security_opt: [no-new-privileges:true] - read_only: true - tmpfs: ["/tmp:size=64m"] - cap_drop: [ALL] - healthcheck: - test: ["CMD","wget","-qO-","http://localhost:9093/-/healthy"] - interval: 30s - timeout: 3s - retries: 5 - labels: - - traefik.enable=true - - traefik.http.routers.alert.rule=Host(`alertmanager.${PRIMARY_DOMAIN}`) - - traefik.http.routers.alert.entrypoints=websecure - - traefik.http.routers.alert.tls.certresolver=le-dns - - traefik.http.routers.alert.middlewares=security-headers@file - - grafana: - image: grafana/grafana:${GRAFANA_VER} - container_name: grafana - user: "472" - restart: unless-stopped - networks: [monitoring, traefik_proxy] - volumes: - - grafana_data:/var/lib/grafana - - ./grafana/provisioning:/etc/grafana/provisioning:ro - environment: - GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD} - GF_USERS_ALLOW_SIGN_UP: "false" - GF_SERVER_ROOT_URL: https://grafana.${PRIMARY_DOMAIN} - security_opt: [no-new-privileges:true] - cap_drop: [ALL] - healthcheck: - test: ["CMD","wget","-qO-","http://localhost:3000/api/health"] - interval: 30s - timeout: 3s - retries: 5 - labels: - - traefik.enable=true - - traefik.http.routers.grafana.rule=Host(`grafana.${PRIMARY_DOMAIN}`) - - traefik.http.routers.grafana.entrypoints=websecure - - traefik.http.routers.grafana.tls.certresolver=le-dns - - traefik.http.routers.grafana.middlewares=security-headers@file - -volumes: - prometheus_data: - grafana_data: - alertmanager_data: -EOF - - # secure launch: 1) create temp file for smtp pass, 2) inject env via monitoring.env.sops - ( - cd /opt/services/monitoring - sops exec-file --no-fifo ./alertmanager/secrets/smtp_pass.sops \ - 'ALERT_SMTP_PASS_PATH={} sops exec-file --no-fifo ./secrets/monitoring.env.sops \ - "docker compose --env-file ./versions.env --env-file {} up -d"' - ) +# 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" } -main(){ - ensure_root - timedatectl set-timezone "$TIMEZONE" - install_node_and_gemini - install_base_tools - bootstrap_sops - create_admin_user - harden_ssh - configure_firewall - system_baseline - install_docker - prepare_secrets - deploy_traefik - deploy_monitoring - log "Base ready - secrets via sops, no plaintext .env, Grafana no fallback password, Alertmanager password via file." +# --- 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 "$@" ;; + 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" "$@" ;; + self-update) cmd_self_update "$@" ;; + uninstall) cmd_uninstall "$@" ;; + help|*) # TODO: Dodać funkcję wyświetlającą pomoc + log info "Dostępne komendy: install, validate, deploy_mastodon, uninstall, ..." ;; + esac } -main "$@" -``` - - ' - -#=============================# -# CONFIGURATION # -#=============================# -# Sprawdź, czy plik konfiguracyjny istnieje i go wczytaj -if [[ ! -f autoscript.conf ]]; then - echo "Błąd: Plik konfiguracyjny 'autoscript.conf' nie został znaleziony." - echo "Skopiuj 'autoscript.conf.example' do 'autoscript.conf' i uzupełnij go." - exit 1 -fi -source autoscript.conf - -# Sprawdź, czy wymagane zmienne są ustawione -: "${PUBLIC_KEY:?Ustaw PUBLIC_KEY w pliku autoscript.conf}" -: "${CF_DNS_API_TOKEN:?Ustaw CF_DNS_API_TOKEN w pliku autoscript.conf}" - -#=============================# -# HELPER FUNCTIONS # -#=============================# -log(){ printf '\n[%s] %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"; } -ensure_root(){ [[ $EUID -eq 0 ]] || { echo "Run as root"; exit 1; }; } - -install_node_and_gemini(){ - log "Node.js LTS + gemini-cli" - curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - - apt-get update - apt-get install -y nodejs build-essential - npm install -g @google/gemini-cli -} - -install_base_tools(){ - log "Base packages, sops, age" - apt-get update - DEBIAN_FRONTEND=noninteractive apt-get install -y \ - openssh-server ufw jq curl wget dnsutils nmap git make \ - sops age aide logwatch auditd unattended-upgrades \ - crowdsec crowdsec-firewall-bouncer-nftables - systemctl enable ssh -} - -bootstrap_sops(){ - log "Bootstrap sops + age" - mkdir -p /root/.config/sops/age - if [[ ! -f /root/.config/sops/age/keys.txt ]]; then - age-keygen -o /root/.config/sops/age/keys.txt >/dev/null - fi - AGE_PUB=$(grep -m1 'public key:' /root/.config/sops/age/keys.txt | awk '{print $4}') - install -d -m 700 /opt/services - cat > /opt/services/.sops.yaml </dev/null 2>&1 || useradd -m -s /bin/bash -G admin,sudo admin - install -d -m 700 -o admin -g admin /home/admin/.ssh - printf '%s\n' "$PUBLIC_KEY" > /home/admin/.ssh/authorized_keys - chown admin:admin /home/admin/.ssh/authorized_keys - chmod 600 /home/admin/.ssh/authorized_keys - DEBIAN_FRONTEND=noninteractive apt-get install -y libpam-google-authenticator - sed -i '/pam_google_authenticator/d' /etc/pam.d/sudo - sed -i '1iauth required pam_google_authenticator.so nullok' /etc/pam.d/sudo - echo "%admin ALL=(ALL) ALL" > /etc/sudoers.d/90-admin - chmod 440 /etc/sudoers.d/90-admin - sudo -u admin google-authenticator -t -d -f -r 3 -R 30 -W -} - -harden_ssh(){ - log "SSH hardening" - ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" -q - ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N "" -q - rm -f /etc/ssh/ssh_host_dsa_key* /etc/ssh/ssh_host_ecdsa_key* - SSH_PORT=$(shuf -i 10000-65535 -n 1) - echo "$SSH_PORT" > /root/ssh_port.txt - install -d /etc/ssh/sshd_config.d - cat > /etc/ssh/sshd_config.d/01-hardening.conf < /etc/ssh/banner <<'EOF' -****************************************************************** -* Authorized users only. Activity is monitored and logged. * -****************************************************************** -EOF - systemctl restart ssh -} - -configure_firewall(){ - log "UFW + DOCKER-USER" - SSH_PORT=$(cat /root/ssh_port.txt) - ufw --force reset - ufw default deny incoming - ufw default allow outgoing - ufw allow "$SSH_PORT"/tcp - ufw allow 80,443/tcp - ufw allow 25,465,587,110,995,143,993,4190/tcp - ufw allow 41641/udp - ufw allow from 127.0.0.0/8 to any port 53 proto udp - ufw --force enable - # DOCKER-USER chain lockdown - iptables -C DOCKER-USER -j RETURN 2>/dev/null || { - iptables -I DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT - iptables -I DOCKER-USER -p tcp -m multiport --dports 80,443,25,465,587,110,995,143,993,4190 -j ACCEPT - iptables -I DOCKER-USER -p tcp --dport "$SSH_PORT" -j ACCEPT - iptables -A DOCKER-USER -j DROP - } -} - -system_baseline(){ - log "Kernel, journald, AIDE, unattended-upgrades" - cat > /etc/sysctl.d/99-security.conf <<'EOF' -net.ipv4.ip_forward = 0 -net.ipv6.conf.all.forwarding = 0 -net.ipv4.conf.all.rp_filter = 1 -net.ipv4.tcp_syncookies = 1 -net.ipv4.conf.all.log_martians = 1 -net.ipv4.icmp_echo_ignore_broadcasts = 1 -net.ipv4.icmp_ignore_bogus_error_responses = 1 -net.core.default_qdisc = fq -net.ipv4.tcp_congestion_control = bbr -EOF - sysctl --system - aideinit && mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db - printf 'Unattended-Upgrade::Automatic-Reboot "false";\n' > /etc/apt/apt.conf.d/50unattended-upgrades - printf 'Unattended-Upgrade::Mail "%s";\n' "$ADMIN_EMAIL" >> /etc/apt/apt.conf.d/50unattended-upgrades - install -d /etc/systemd/journald.conf.d - cat > /etc/systemd/journald.conf.d/99-storage.conf <<'EOF' -[Journal] -Storage=persistent -SystemMaxUse=1G -SystemMaxFileSize=128M -MaxRetentionSec=1month -EOF - systemctl restart systemd-journald -} - -install_docker(){ - log "Docker Engine + compose plugin" - curl -fsSL https://get.docker.com | sh - usermod -aG docker admin - mkdir -p /etc/docker - cat > /etc/docker/daemon.json <<'EOF' -{ - "live-restore": true, - "userns-remap": "default", - "log-driver": "json-file", - "log-opts": { "max-size": "10m", "max-file": "3" }, - "default-address-pools": [{ "base": "10.88.0.0/16", "size": 24 }], - "experimental": false -} -EOF - systemctl enable docker - systemctl restart docker -} - -#-------------------------------# -# Secrets (sops managed) # -#-------------------------------# -generate_random(){ - # urlsafe, no newline - head -c 48 /dev/urandom | base64 | tr -d '\n' | tr '/+' 'AB' -} - -write_sops_file_env(){ - # args: path key=value lines (plaintext via heredoc), encrypt in place - local path="$1"; shift - install -d "$(dirname "$path")" - umask 077 - cat > "$path" < "$path" - sops -e -i "$path" -} - -prepare_secrets(){ - log "Prepare encrypted secrets with sops" - # Traefik env - write_sops_file_env /opt/services/traefik/secrets/traefik.env.sops \ -"CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN -ADMIN_EMAIL=$ADMIN_EMAIL -PRIMARY_DOMAIN=$PRIMARY_DOMAIN -TZ=$TIMEZONE" - # Monitoring env (Grafana admin password auto if missing) - : "${GRAFANA_ADMIN_PASSWORD:=$(generate_random)}" - write_sops_file_env /opt/services/monitoring/secrets/monitoring.env.sops \ -"GRAFANA_ADMIN_PASSWORD=$GRAFANA_ADMIN_PASSWORD -TZ=$TIMEZONE" - # Alertmanager SMTP password as file secret - : "${ALERT_SMTP_PASS:=$(generate_random)}" - write_sops_file_blob /opt/services/monitoring/alertmanager/secrets/smtp_pass.sops "$ALERT_SMTP_PASS" - - # scrub process env copies - unset CF_DNS_API_TOKEN GRAFANA_ADMIN_PASSWORD ALERT_SMTP_PASS -} - -#-------------------------------# -# Traefik # -#-------------------------------# -deploy_traefik(){ - log "Traefik" - install -d /opt/services/traefik/{data,config,dynamic,secrets} - install -m 600 /dev/null /opt/services/traefik/data/acme.json || true - ( docker network create traefik_proxy >/dev/null 2>&1 || true ) - - cat > /opt/services/traefik/config/traefik.yml <<'EOF' -api: { dashboard: true } -ping: {} -entryPoints: - web: - address: ":80" - http: { redirections: { entryPoint: { to: websecure, scheme: https } } } - websecure: - address: ":443" -providers: - file: { directory: "/etc/traefik/dynamic", watch: true } - docker: - exposedByDefault: false - network: traefik_proxy -certificatesResolvers: - le-dns: - acme: - email: ${ADMIN_EMAIL} - storage: /acme/acme.json - dnsChallenge: { provider: cloudflare, delayBeforeCheck: 0 } -metrics: { prometheus: { addEntryPointsLabels: true, addServicesLabels: true } } -log: { level: INFO, format: json } -accessLog: { format: json } -EOF - - cat > /opt/services/traefik/dynamic/middlewares.yml <<'EOF' -http: - middlewares: - security-headers: - headers: - stsSeconds: 63072000 - stsIncludeSubdomains: true - stsPreload: true - contentTypeNosniff: true - referrerPolicy: "strict-origin-when-cross-origin" - frameDeny: true - browserXssFilter: true - gzip: { compress: {} } - rate-limit: - rateLimit: { burst: 100, average: 50 } -EOF - - cat > /opt/services/traefik/dynamic/tls.yml <<'EOF' -tls: - options: - default: - minVersion: VersionTLS12 - sniStrict: true -EOF - - cat > /opt/services/traefik/versions.env < /opt/services/traefik/docker-compose.yml <<'EOF' -services: - traefik: - image: traefik:${TRAEFIK_VER} - container_name: traefik - restart: unless-stopped - networks: [traefik_proxy] - ports: ["80:80","443:443"] - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - ./config/traefik.yml:/etc/traefik/traefik.yml:ro - - ./dynamic:/etc/traefik/dynamic:ro - - ./data/acme.json:/acme/acme.json - environment: - CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN} - security_opt: [no-new-privileges:true] - read_only: true - tmpfs: ["/tmp:size=64m"] - cap_drop: [ALL] - cap_add: [NET_BIND_SERVICE] - healthcheck: - test: ["CMD","wget","--no-verbose","--tries=1","--spider","http://localhost:8080/ping"] - interval: 30s - timeout: 3s - retries: 5 -networks: - traefik_proxy: - external: true -EOF - - # sops - secure launch (no plaintext .env on disk) - ( cd /opt/services/traefik && \ - sops exec-file --no-fifo ./secrets/traefik.env.sops \ - 'docker compose --env-file ./versions.env --env-file {} up -d' ) -} - -#-------------------------------# -# Monitoring stack # -#-------------------------------# -deploy_monitoring(){ - log "Monitoring: Prometheus, Grafana, Alertmanager, exporters" - install -d /opt/services/monitoring/{prometheus/rules,grafana/provisioning/{datasources,dashboards},blackbox,alertmanager/secrets,secrets} - - # Prometheus config - cat > /opt/services/monitoring/prometheus/prometheus.yml <<'EOF' -global: - scrape_interval: 15s - evaluation_interval: 15s -rule_files: - - "/etc/prometheus/rules/*.yml" -alerting: - alertmanagers: - - static_configs: [{ targets: ['alertmanager:9093'] }] -scrape_configs: - - job_name: prometheus - static_configs: [{ targets: ['localhost:9090'] }] - - job_name: node-exporter - static_configs: [{ targets: ['node-exporter:9100'] }] - - job_name: cadvisor - static_configs: [{ targets: ['cadvisor:8080'] }] - - job_name: traefik - static_configs: [{ targets: ['traefik:8080'] }] - - job_name: blackbox - metrics_path: /probe - params: { module: [http_2xx] } - static_configs: - - targets: - - https://forum.yeswas.pl - - https://social.ovh - - https://rss.social.ovh - - https://pawelorzech.pl - - https://dash.orzech.me - - https://run.orzech.me - relabel_configs: - - source_labels: [__address__] - target_label: __param_target - - source_labels: [__param_target] - target_label: instance - - target_label: __address__ - replacement: blackbox-exporter:9115 -EOF - - cat > /opt/services/monitoring/prometheus/rules/alerts.yml <<'EOF' -groups: -- name: general.rules - rules: - - alert: InstanceDown - expr: up == 0 - for: 2m - labels: { severity: critical } - annotations: - summary: "Instance {{ $labels.instance }} down" - description: "{{ $labels.instance }} of job {{ $labels.job }} down >2m" - - alert: HighCPUUsage - expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))*100) > 80 - for: 5m - labels: { severity: warning } - annotations: { summary: "High CPU {{ $labels.instance }}" } - - alert: HighMemoryUsage - expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)/node_memory_MemTotal_bytes*100 > 90 - for: 5m - labels: { severity: warning } - annotations: { summary: "High memory {{ $labels.instance }}" } - - alert: DiskSpaceLow - expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}/node_filesystem_size_bytes) < 0.1 - for: 10m - labels: { severity: warning } - annotations: { summary: "Low disk {{ $labels.instance }}" } - - alert: CertificateExpiration - expr: probe_ssl_earliest_cert_expiry - time() < 604800 - for: 0m - labels: { severity: warning } - annotations: { summary: "Cert expires soon {{ $labels.instance }}" } -EOF - - # Blackbox config - cat > /opt/services/monitoring/blackbox/blackbox.yml <<'EOF' -modules: - http_2xx: - prober: http - timeout: 5s - http: - valid_http_versions: ["HTTP/1.1","HTTP/2.0"] - valid_status_codes: [200,301,302] - method: GET - follow_redirects: true - tls_config: { insecure_skip_verify: false } -EOF - - # Alertmanager config with file-based password - cat > /opt/services/monitoring/alertmanager/alertmanager.yml < /opt/services/monitoring/grafana/provisioning/datasources/prometheus.yml <<'EOF' -apiVersion: 1 -datasources: - - name: Prometheus - type: prometheus - access: proxy - url: http://prometheus:9090 - isDefault: true - editable: false -EOF - - # Versions file - cat > /opt/services/monitoring/versions.env < /opt/services/monitoring/docker-compose.yml <<'EOF' -networks: - monitoring: - traefik_proxy: - external: true -services: - prometheus: - image: prom/prometheus:${PROMETHEUS_VER} - container_name: prometheus - restart: unless-stopped - networks: [monitoring, traefik_proxy] - volumes: - - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - - ./prometheus/rules:/etc/prometheus/rules:ro - - prometheus_data:/prometheus - command: - - --config.file=/etc/prometheus/prometheus.yml - - --storage.tsdb.path=/prometheus - - --storage.tsdb.retention.time=30d - - --web.enable-lifecycle - - --web.external-url=https://prometheus.${PRIMARY_DOMAIN} - security_opt: [no-new-privileges:true] - read_only: true - tmpfs: ["/tmp:size=64m"] - cap_drop: [ALL] - healthcheck: - test: ["CMD","wget","-qO-","http://localhost:9090/-/healthy"] - interval: 30s - timeout: 3s - retries: 5 - labels: - - traefik.enable=true - - traefik.http.routers.prom.rule=Host(`prometheus.${PRIMARY_DOMAIN}`) - - traefik.http.routers.prom.entrypoints=websecure - - traefik.http.routers.prom.tls.certresolver=le-dns - - traefik.http.routers.prom.middlewares=security-headers@file - - node-exporter: - image: prom/node-exporter:${NODE_EXPORTER_VER} - container_name: node-exporter - restart: unless-stopped - networks: [monitoring] - pid: host - volumes: - - /proc:/host/proc:ro - - /sys:/host/sys:ro - - /:/rootfs:ro - command: - - --path.procfs=/host/proc - - --path.rootfs=/rootfs - - --path.sysfs=/host/sys - - --collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/) - security_opt: [no-new-privileges:true] - read_only: true - cap_drop: [ALL] - - cadvisor: - image: gcr.io/cadvisor/cadvisor:${CADVISOR_VER} - container_name: cadvisor - restart: unless-stopped - networks: [monitoring] - volumes: - - /:/rootfs:ro - - /var/run:/var/run:rw - - /sys:/sys:ro - - /var/lib/docker:/var/lib/docker:ro - privileged: true - devices: ["/dev/kmsg:/dev/kmsg"] - - blackbox-exporter: - image: prom/blackbox-exporter:${BLACKBOX_VER} - container_name: blackbox-exporter - restart: unless-stopped - networks: [monitoring] - volumes: - - ./blackbox/blackbox.yml:/etc/blackbox_exporter/config.yml:ro - security_opt: [no-new-privileges:true] - read_only: true - tmpfs: ["/tmp:size=64m"] - cap_drop: [ALL] - - alertmanager: - image: prom/alertmanager:${ALERTMANAGER_VER} - container_name: alertmanager - restart: unless-stopped - networks: [monitoring, traefik_proxy] - volumes: - - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro - - alertmanager_data:/alertmanager - - ${ALERT_SMTP_PASS_PATH}:/etc/alertmanager/secrets/smtp_pass:ro - command: - - --config.file=/etc/alertmanager/alertmanager.yml - - --storage.path=/alertmanager - - --web.external-url=https://alertmanager.${PRIMARY_DOMAIN} - security_opt: [no-new-privileges:true] - read_only: true - tmpfs: ["/tmp:size=64m"] - cap_drop: [ALL] - healthcheck: - test: ["CMD","wget","-qO-","http://localhost:9093/-/healthy"] - interval: 30s - timeout: 3s - retries: 5 - labels: - - traefik.enable=true - - traefik.http.routers.alert.rule=Host(`alertmanager.${PRIMARY_DOMAIN}`) - - traefik.http.routers.alert.entrypoints=websecure - - traefik.http.routers.alert.tls.certresolver=le-dns - - traefik.http.routers.alert.middlewares=security-headers@file - - grafana: - image: grafana/grafana:${GRAFANA_VER} - container_name: grafana - user: "472" - restart: unless-stopped - networks: [monitoring, traefik_proxy] - volumes: - - grafana_data:/var/lib/grafana - - ./grafana/provisioning:/etc/grafana/provisioning:ro - environment: - GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD} - GF_USERS_ALLOW_SIGN_UP: "false" - GF_SERVER_ROOT_URL: https://grafana.${PRIMARY_DOMAIN} - security_opt: [no-new-privileges:true] - cap_drop: [ALL] - healthcheck: - test: ["CMD","wget","-qO-","http://localhost:3000/api/health"] - interval: 30s - timeout: 3s - retries: 5 - labels: - - traefik.enable=true - - traefik.http.routers.grafana.rule=Host(`grafana.${PRIMARY_DOMAIN}`) - - traefik.http.routers.grafana.entrypoints=websecure - - traefik.http.routers.grafana.tls.certresolver=le-dns - - traefik.http.routers.grafana.middlewares=security-headers@file - -volumes: - prometheus_data: - grafana_data: - alertmanager_data: -EOF - - # secure launch: 1) create temp file for smtp pass, 2) inject env via monitoring.env.sops - ( - cd /opt/services/monitoring - sops exec-file --no-fifo ./alertmanager/secrets/smtp_pass.sops \ - 'ALERT_SMTP_PASS_PATH={} sops exec-file --no-fifo ./secrets/monitoring.env.sops \ - "docker compose --env-file ./versions.env --env-file {} up -d"' - ) -} - -main(){ - ensure_root - timedatectl set-timezone "$TIMEZONE" - install_node_and_gemini - install_base_tools - bootstrap_sops - create_admin_user - harden_ssh - configure_firewall - system_baseline - install_docker - prepare_secrets - deploy_traefik - deploy_monitoring - log "Base ready - secrets via sops, no plaintext .env, Grafana no fallback password, Alertmanager password via file." -} - -main "$@" -``` +main "$@" \ No newline at end of file