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:
@@ -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 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
|
||||
Reference in New Issue
Block a user