Files
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

10 KiB
Raw Permalink Blame History

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.

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

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:

ssh-keygen -t ed25519 -C "kacper@macbook"

Windows (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:

ssh-copy-id ubuntu@54.38.159.156

Windows (PowerShell — nie ma ssh-copy-id):

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:

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:

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

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

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

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

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

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.

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).

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

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

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.

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.

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:

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.cloudrejestracja 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

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:

    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