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
This commit is contained in:
2026-08-20 15:53:51 +00:00
commit 6ed7a7f4d2
13 changed files with 1733 additions and 0 deletions
+392
View File
@@ -0,0 +1,392 @@
# 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