Files
infra/docs/runbooks/00-bootstrap.md
T
kacperor 6ed7a7f4d2 Init repo infra + poprawki runbooka bootstrap i HANDOFF
- Krok 9 (tmux): tee -> tee -a (nie nadpisuj configu), dodano
  focus-events on, poprawiono nazwę sesji na "bootstrap"
- Krok 10: struktura katalogów odzwierciedla stan faktyczny
  (/srv i /srv/infra już istnieją, brakuje apps i data)
- Krok 12: Claude Code oznaczony jako wykonany 20-08-2026,
  instalator natywny bez Node.js
- Nowy Krok 14: audyt po bootstrapie (SSH, ufw, fail2ban,
  unattended-upgrades, swap, Docker, porty, klucze)
- HANDOFF.md: poprawiona numeracja sekcji 6
2026-08-20 15:53:51 +00:00

393 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 11,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