- 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
393 lines
10 KiB
Markdown
393 lines
10 KiB
Markdown
# 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
|