# Runbook 00 — bootstrap serwera od zera Uruchomienie świeżego Ubuntu 24.04 do stanu, w którym można stawiać usługi. Kolejność ma znaczenie — nie przeskakuj kroków. Czas: około 1–1,5 h przy pierwszym przejściu, z tłumaczeniem po drodze. --- ## Krok 0 — przed startem - [ ] Znasz IPv4 serwera (`54.38.159.156`) i masz dostęp do panelu OVH - [ ] Masz pod ręką **konsolę KVM w panelu OVH** — to droga ratunku, jeśli zablokujesz sobie SSH - [ ] Wiesz, z których urządzeń będziesz się logował Obraz OVH nie daje logowania na roota po SSH. Konto robocze to **`ubuntu`**, z `sudo`. ```bash ssh ubuntu@54.38.159.156 cat ~/.ssh/authorized_keys # jakie klucze już są hostnamectl # wersja systemu df -h / # punkt odniesienia dla dysku sudo whoami # ma zwrócić: root ``` --- ## Krok 1 — podstawy systemu ```bash sudo apt update && sudo apt upgrade -y sudo timedatectl set-timezone Europe/Warsaw sudo hostnamectl set-hostname dfkk sudo apt install -y tmux curl git ufw fail2ban unattended-upgrades ca-certificates echo "127.0.1.1 dfkk" | sudo tee -a /etc/hosts ``` --- ## Krok 2 — klucze SSH ze WSZYSTKICH urządzeń **To jest krok, którego pominięcie kończy się utratą dostępu.** Krok 3 wyłącza logowanie hasłem. Każde urządzenie, które nie ma wtedy klucza na serwerze, traci możliwość zalogowania się — bo hasło przestaje działać, a klucza nie ma. Zrób to **dla każdego urządzenia osobno**: macOS, Windows, iOS. ### Generowanie klucza (lokalnie, na danym urządzeniu) macOS / Linux: ```bash ssh-keygen -t ed25519 -C "kacper@macbook" ``` Windows (PowerShell): ```powershell ssh-keygen -t ed25519 -C "kacper@windows" ``` iOS: klucz generujesz w aplikacji (Termius lub Blink) i kopiujesz część publiczną. Komentarz na końcu (`-C`) jest istotny — po nim rozpoznasz w `authorized_keys`, który wpis usunąć, gdy zgubisz konkretny sprzęt. ### Wgranie klucza na serwer macOS / Linux: ```bash ssh-copy-id ubuntu@54.38.159.156 ``` Windows (PowerShell — nie ma `ssh-copy-id`): ```powershell type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh ubuntu@54.38.159.156 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" ``` ### Weryfikacja — obowiązkowa Z **każdego** urządzenia po kolei: ```bash ssh ubuntu@54.38.159.156 ``` Ma wpuścić **bez pytania o hasło**. Jeśli pyta o hasło — klucz nie działa. Napraw to teraz. Nie przechodź do kroku 3. Na serwerze sprawdź komplet: ```bash cat ~/.ssh/authorized_keys ``` Powinieneś zobaczyć klucz OVH plus jeden wpis na każde urządzenie, każdy z komentarzem. Wpisz je do `docs/SERVER.md`, sekcja 4. ### Hasło do sudo ```bash sudo passwd ubuntu ``` > **Decyzja do podjęcia:** `sudo` z hasłem czy `NOPASSWD`? > Z hasłem jest bezpieczniej, ale każda operacja Claude Code wymagająca uprawnień > zatrzyma się na monicie. `NOPASSWD` jest wygodniejszy i przy logowaniu wyłącznie > na klucz — akceptowalny, bo kto ma klucz, i tak ma pełny dostęp. > Rekomendacja: `NOPASSWD`, ale **hasło ustawione** jako furtka przez konsolę KVM. --- ## Krok 3 — utwardzenie SSH ```bash sudo tee /etc/ssh/sshd_config.d/99-hardening.conf > /dev/null <<'EOF' PermitRootLogin no PasswordAuthentication no KbdInteractiveAuthentication no PubkeyAuthentication yes AllowUsers ubuntu X11Forwarding no MaxAuthTries 3 ClientAliveInterval 300 ClientAliveCountMax 2 EOF sudo sshd -t # walidacja składni — MUSI przejść bez błędu sudo systemctl restart ssh ``` **Znowu: nowe okno, test logowania, i dopiero potem zamknięcie starej sesji.** `sshd -t` łapie literówki, ale nie złapie sytuacji, w której wykluczyłeś sam siebie z `AllowUsers`. --- ## Krok 4 — firewall ```bash sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable sudo ufw status verbose ``` > **Do zapamiętania:** ufw **nie chroni portów publikowanych przez Dockera.** > Docker wpisuje własne reguły do iptables wcześniej w łańcuchu. Kontener z > `ports: "5432:5432"` jest widoczny z internetu, mimo że ufw pokazuje `deny`. > Dlatego w tym projekcie kontenery nie publikują portów — patrz `CLAUDE.md`, 3.3. --- ## Krok 5 — fail2ban ```bash sudo tee /etc/fail2ban/jail.local > /dev/null <<'EOF' [DEFAULT] bantime = 1h findtime = 10m maxretry = 5 backend = systemd [sshd] enabled = true EOF sudo systemctl enable --now fail2ban sudo fail2ban-client status sshd ``` --- ## Krok 6 — automatyczne aktualizacje bezpieczeństwa ```bash sudo dpkg-reconfigure -plow unattended-upgrades ``` Sprawdź, że `Unattended-Upgrade::Automatic-Reboot` jest ustawione na `"false"`. Serwer ma się **nie restartować sam** — restart robimy świadomie, po snapshocie. --- ## Krok 7 — swap 12 GB RAM to dużo, ale swap chroni przed tym, że jeden kontener po wycieku pamięci zabije OOM-killerem coś zupełnie innego. ```bash sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swap.conf sudo sysctl --system ``` --- ## Krok 8 — Docker Z oficjalnego repozytorium Dockera, nie z repo Ubuntu (tam jest stara wersja). ```bash sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io \ docker-buildx-plugin docker-compose-plugin sudo usermod -aG docker ubuntu ``` Wyloguj się i zaloguj ponownie, żeby członkostwo w grupie `docker` zadziałało. ### Limity logów — to jest ten krok, którego pominięcie zapycha dysk ```bash sudo tee /etc/docker/daemon.json > /dev/null <<'EOF' { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" }, "live-restore": true } EOF sudo systemctl restart docker docker run --rm hello-world ``` Bez tego log jednego gadatliwego kontenera rośnie w nieskończoność. Przy 100 GB dysku to kwestia tygodni, nie lat. ### Sieć `edge` ```bash docker network create edge ``` --- ## Krok 9 — tmux Dopisujemy do `~/.tmux.conf`, nie nadpisujemy — jeśli plik już istnieje z własną konfiguracją, `tee` bez `-a` go zniszczy. ```bash tee -a ~/.tmux.conf > /dev/null <<'EOF' set -g mouse on set -g history-limit 50000 set -g base-index 1 setw -g mode-keys vi set -g status-bg colour236 set -g status-fg colour252 set -g focus-events on EOF tmux source-file ~/.tmux.conf # przeładowanie, jeśli tmux już działa ``` Od tego momentu praca zaczyna się od `tmux new -s bootstrap` albo `tmux attach -t bootstrap`. --- ## Krok 10 — struktura katalogów `/srv` i `/srv/infra` już istnieją — repo `infra` zostało wgrane przez `scp` przed bootstrapem. Brakuje `/srv/apps` i `/srv/data`. ```bash sudo mkdir -p /srv/apps /srv/data sudo chown -R ubuntu:ubuntu /srv ``` --- ## Krok 11 — snapshot **Zrób snapshot w panelu OVH teraz.** Masz czysty, utwardzony system bez usług — to najlepszy punkt powrotu, jaki będziesz miał. Zapisz datę w `SERVER.md`. --- ## Krok 12 — Claude Code **Wykonane 20-08-2026.** Instalacja natywnym instalatorem, bez Node.js: ```bash curl -fsSL https://claude.ai/install.sh | bash ``` Binarka ląduje w `~/.local/bin/claude`. Sesje odpalane **zawsze w tmuxie**, z katalogu `/srv/infra`, żeby od razu widział `CLAUDE.md`. --- ## Krok 13 — Traefik, Gitea, monitoring Osobne stacki, stawiane w tej kolejności: 1. **Traefik** — bez niego reszta nie ma jak wyjść na świat. Wymaga kluczy API OVH do DNS-01. Weryfikacja: certyfikat wildcard dla `*.dfkk.cloud` się wystawia. 2. **Gitea** na `git.dfkk.cloud` — **rejestracja wyłączona natychmiast po pierwszym logowaniu**. Potem push mirror repo `infra` na prywatne repo GitHuba. 3. **Monitoring** (Uptime Kuma) na `status.dfkk.cloud` za basic auth, alerty na maila. Każdy stack: katalog w `/srv/infra/stacks/`, `compose.yaml` w repo, `.env` poza repo, wpis w `SERVER.md`. --- ## Krok 14 — audyt po bootstrapie Konfiguracja bez weryfikacji to założenie, nie zabezpieczenie. Literówka w `sshd_config` albo reguła ufw, która nie weszła, wyglądają identycznie jak sukces. **Każdy punkt, który nie przejdzie, blokuje przejście dalej.** ### Na serwerze ```bash sudo sshd -T | grep -E "permitrootlogin|passwordauthentication|pubkeyauthentication" # oczekiwane: no / no / yes sudo ufw status verbose # tylko 22/80/443, default deny incoming sudo fail2ban-client status sshd # jail aktywny systemctl is-enabled unattended-upgrades # enabled grep Automatic-Reboot /etc/apt/apt.conf.d/50unattended-upgrades # "false" sudo swapon --show # swapfile 2G aktywny cat /etc/docker/daemon.json # limity logów obecne docker ps --format "table {{.Names}}\t{{.Ports}}" # nic na 0.0.0.0 ss -tulpn | grep LISTEN # każdy nasłuch uzasadniony, porównaj z SERVER.md sekcja 5 cat ~/.ssh/authorized_keys # tylko znane klucze, każdy z komentarzem identyfikującym urządzenie ``` ### Do wykonania przez Kacpra, z jego urządzeń (Claude nie ma do nich dostępu) - `ssh ubuntu@54.38.159.156` z macOS, Windows i iOS → wchodzi bez pytania o hasło - test, że baza nie jest wystawiona — na Windowsie w PowerShellu: ```powershell Test-NetConnection -ComputerName 54.38.159.156 -Port 5432 # oczekiwane: TcpTestSucceeded : False ``` (Windows nie ma netcata — nie używamy tu `nc -zv`.) ### Po audycie Zaktualizować `SERVER.md` sekcje 4, 5 i 10, zrobić commit. --- ## Po zakończeniu - [ ] `SERVER.md` uzupełniony: IP, lokalizacja, klucze, data snapshota - [ ] Repo `infra` zainicjowane i zacommitowane - [ ] Mirror na GitHub skonfigurowany - [ ] Test: rozłączenie SSH w trakcie działania procesu w tmux — proces przeżywa - [ ] Test: logowanie z każdego z trzech urządzeń - [ ] Snapshot po postawieniu Traefika i Gitei