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:
+109
@@ -0,0 +1,109 @@
|
||||
# HANDOFF.md — przekazanie kontekstu
|
||||
|
||||
Dokument jednorazowy. Powstał w rozmowie na claude.ai przed pierwszym uruchomieniem
|
||||
serwera i przenosi ustalenia do sesji Claude Code na maszynie.
|
||||
|
||||
**Czytasz go raz, na starcie pierwszej sesji.** Trwałe zasady są w `CLAUDE.md`,
|
||||
stan serwera w `docs/SERVER.md`, procedury w `docs/runbooks/`.
|
||||
Po zakończeniu bootstrapu ten plik można usunąć.
|
||||
|
||||
---
|
||||
|
||||
## 1. Z kim pracujesz
|
||||
|
||||
**Kacper**, `k.kruszka@hotmail.com`. Pracuje z macOS, Windows i iOS.
|
||||
|
||||
Kluczowe: **to projekt edukacyjny z produkcyjnymi konsekwencjami.** Kacper uczy się
|
||||
pracy z Claude i administracji serwerem — więc tłumaczysz co robisz i dlaczego,
|
||||
nie tylko wykonujesz. Ale docelowo mogą tu stanąć rzeczy komercyjne, więc traktujesz
|
||||
maszynę jak produkcję od pierwszego dnia.
|
||||
|
||||
Prosił wprost, żeby **proaktywnie podpowiadać** o narzędziach, skillach i funkcjach,
|
||||
które mogą pomóc — nie czekać, aż sam zapyta.
|
||||
|
||||
---
|
||||
|
||||
## 2. Maszyna
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| OVH VPS-2 2026 | 6 vCore, 12 GB RAM, 100 GB SSD |
|
||||
| Host | `vps-c936b594.vps.ovh.net`, IPv4 `54.38.159.156` |
|
||||
| System | Ubuntu 24.04 LTS, świeży |
|
||||
| Panel OVH | region **CA** — ma znaczenie przy generowaniu tokenów API |
|
||||
| Snapshot | włączony, 1 slot, ręczny, nadpisywany |
|
||||
| Domena techniczna | `dfkk.cloud` (OVH, DNS w OVH) |
|
||||
| Pozostałe domeny | dwie, nazwy jeszcze nieustalone, zostają wolne pod projekty |
|
||||
|
||||
---
|
||||
|
||||
## 3. Stan na teraz
|
||||
|
||||
- Kacper loguje się jako **`ubuntu`** (konto domyślne obrazu OVH, z `sudo`) — roota po SSH nie ma
|
||||
- Bootstrap **nie został rozpoczęty**
|
||||
- Pliki repo `infra` wgrane przez `scp` do `/srv/infra`
|
||||
- Na serwerze jest klucz SSH wgrany przez OVH — **do weryfikacji przy kroku 0**
|
||||
- **Logowanie hasłem jest nadal włączone.** Windows łączył się hasłem, więc nie ma tam
|
||||
jeszcze klucza. Krok 2 bootstrapu musi to domknąć **przed** krokiem 3.
|
||||
- Nie ma jeszcze: użytkownika `ubuntu`, Dockera, Traefika, Gitei, firewalla, niczego
|
||||
|
||||
**Następny krok: `docs/runbooks/00-bootstrap.md`, krok 0.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Decyzje podjęte i uzasadnienie
|
||||
|
||||
Podjęte świadomie, po omówieniu alternatyw. **Nie otwieraj ich ponownie bez powodu** —
|
||||
jeśli widzisz problem z którąś, powiedz to wprost, ale nie zaczynaj od zera.
|
||||
|
||||
| Decyzja | Dlaczego |
|
||||
|---|---|
|
||||
| Docker + Traefik, ręcznie | Zamiast panelu Coolify/Dokploy — pełna kontrola i więcej nauki. Świadomy wybór trudniejszej drogi. |
|
||||
| Gitea self-hosted na tej maszynie | Wybór Kacpra. **Z warunkiem:** mirror repo `infra` na prywatne repo GitHuba, bo inaczej awaria zabiera kod i konfigurację naraz. |
|
||||
| Certyfikaty przez DNS-01 (API OVH) | Wildcard `*.dfkk.cloud` — nowa subdomena nie wymaga żadnej akcji przy certyfikacie. Możliwe też usługi niepubliczne. |
|
||||
| Praca przez SSH + tmux | Sesja przeżywa rozłączenie. Ten sam `tmux attach` z Maca, Windowsa i iPhone'a. VS Code Remote SSH jako dodatek, nie zamiennik. |
|
||||
| Osobny klucz SSH na urządzenie | Zgubiony telefon = usunięcie jednej linii z `authorized_keys`. |
|
||||
| Konto robocze `ubuntu`, bez tworzenia `deploy` | Obraz OVH daje `ubuntu` z `sudo` i działającym kluczem. Tworzenie nowego konta to dodatkowe ryzyko lockoutu przy marginalnym zysku — realną ochroną jest brak haseł, ufw i fail2ban, nie nietypowa nazwa użytkownika. |
|
||||
| Kontenery nie publikują portów | Docker omija ufw — opublikowany port jest widoczny z internetu mimo `deny` w firewallu. Wejście wyłącznie przez Traefika. |
|
||||
| Wersje obrazów przypięte, nigdy `:latest` | Żeby `docker compose pull` nie podmienił działającej aplikacji na zepsutą, i żeby był rollback. |
|
||||
| Brak serwera pocztowego | OVH blokuje port 25, IP bez reputacji trafia do spamu. Maile przez zewnętrzny SMTP (Resend/Brevo). |
|
||||
| Backup off-site odłożony | Kacper ma wykupione chmury, wrócimy do restica **zanim** pojawią się pierwsze prawdziwe dane. Do tego czasu snapshot OVH wystarcza. |
|
||||
| Skille napisane później | Najpierw przejść runbooki ręcznie raz czy dwa, potem zamienić w skille to, co się faktycznie powtarza. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Decyzje otwarte
|
||||
|
||||
- **`sudo` z hasłem czy `NOPASSWD` dla `ubuntu`?** Do rozstrzygnięcia w kroku 2 bootstrapu.
|
||||
Rekomendacja z runbooka: `NOPASSWD`, ale z ustawionym hasłem jako furtka przez konsolę KVM.
|
||||
- **Nazwy dwóch pozostałych domen** — do wpisania w `SERVER.md` gdy będą potrzebne.
|
||||
- **Gdzie backup off-site** — Kacper ma wykupione chmury, do ustalenia który dostawca.
|
||||
|
||||
---
|
||||
|
||||
## 6. Rzeczy, o których trzeba pamiętać
|
||||
|
||||
Wynikły z rozmowy, łatwo je przeoczyć:
|
||||
|
||||
1. **Kacper nie zna jeszcze wszystkich mechanizmów.** Warto przy okazji wyjaśniać —
|
||||
np. dlaczego `claude` odpalamy z `/srv/infra` (inaczej `CLAUDE.md` się nie załaduje),
|
||||
po co `/context` i `/memory`.
|
||||
2. **Repo `infra` to kanał między rozmowami.** Ustalenie, które nie trafi do
|
||||
`CLAUDE.md`, `SERVER.md` albo runbooka, umiera razem z sesją. Ta zasada jest
|
||||
powodem, dla którego `SERVER.md` ma być aktualizowany w tej samej sesji, co zmiana.
|
||||
3. **`scp` był jednorazowy.** Od momentu postawienia Gitei repo idzie przez git.
|
||||
Ręczne kopiowanie plików na serwer to nawyk, przez który po miesiącu nikt nie wie,
|
||||
która wersja konfiguracji jest prawdziwa.
|
||||
4. **Lista braków jest w `docs/SECURITY.md`, sekcja 8.** Najpilniejsze: mirror na GitHub
|
||||
i 2FA na koncie OVH — panel OVH może maszynę zreinstalować, więc w praktyce
|
||||
jest ważniejszy niż dostęp SSH.
|
||||
|
||||
---
|
||||
|
||||
## 7. Czego nie robić
|
||||
|
||||
- Nie startuj bootstrapu bez potwierdzenia — Kacper może chcieć najpierw przeczytać runbooki
|
||||
- Nie zmieniaj `sshd_config` bez otwartej drugiej sesji SSH
|
||||
- Nie zakładaj, że coś działa — sprawdzaj (`curl`, `docker ps`, logi)
|
||||
- Nie rób `docker system prune -a --volumes` odruchowo
|
||||
- Nie wracaj do rozstrzygniętych decyzji z sekcji 4 bez konkretnego powodu
|
||||
@@ -0,0 +1,149 @@
|
||||
# SECURITY.md — bezpieczeństwo serwera
|
||||
|
||||
Zasada porządkująca: **serwer jest tak bezpieczny, jak jego najsłabsza wystawiona usługa.**
|
||||
Utwardzony SSH nie ma znaczenia, jeśli obok stoi panel administracyjny bez hasła.
|
||||
|
||||
---
|
||||
|
||||
## 1. Warstwy
|
||||
|
||||
| Warstwa | Czym chronimy | Gdzie skonfigurowane |
|
||||
|---|---|---|
|
||||
| Dostęp do maszyny | SSH tylko na klucz, root wyłączony | `/etc/ssh/sshd_config.d/99-hardening.conf` |
|
||||
| Sieć | ufw: deny incoming, allow 22/80/443 | `ufw status verbose` |
|
||||
| Automatyczne ataki | fail2ban na sshd | `/etc/fail2ban/jail.local` |
|
||||
| Aktualizacje | unattended-upgrades (security) | `/etc/apt/apt.conf.d/50unattended-upgrades` |
|
||||
| Izolacja aplikacji | osobne sieci Dockera, brak publikowanych portów | compose każdego stacka |
|
||||
| Transport | TLS z Let's Encrypt na wszystkim | Traefik |
|
||||
| Sekrety | pliki `.env`, `chmod 600`, poza repo | obok każdego stacka |
|
||||
|
||||
---
|
||||
|
||||
## 2. SSH
|
||||
|
||||
Docelowa konfiguracja:
|
||||
|
||||
```
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
AllowUsers ubuntu
|
||||
X11Forwarding no
|
||||
MaxAuthTries 3
|
||||
```
|
||||
|
||||
**Reguła bezwzględna przy każdej zmianie w sshd:** otwierasz drugą sesję SSH i sprawdzasz,
|
||||
że logowanie działa, **zanim** zamkniesz pierwszą. Błąd w `sshd_config` przy zamkniętej
|
||||
sesji oznacza odzyskiwanie dostępu przez konsolę KVM w panelu OVH.
|
||||
|
||||
Port zostawiamy na 22. Przenoszenie na 2222 to kosmetyka — odsiewa hałas w logach,
|
||||
ale nie zatrzymuje nikogo, kto celuje w konkretną maszynę. fail2ban robi tu realną robotę.
|
||||
|
||||
### Klucze
|
||||
|
||||
- osobny klucz na każde urządzenie, `ed25519`, z komentarzem identyfikującym sprzęt
|
||||
- klucz prywatny **nigdy** nie trafia na serwer ani do repo
|
||||
- rejestr kluczy: `SERVER.md`, sekcja 4
|
||||
- przy zgubieniu urządzenia: usuwasz jego linię z `~/.ssh/authorized_keys`, koniec
|
||||
|
||||
iOS: Termius lub Blink. Wchodzisz przez `tmux attach -t claude` do tej samej sesji,
|
||||
którą zostawiłeś na Macu.
|
||||
|
||||
---
|
||||
|
||||
## 3. Sekrety
|
||||
|
||||
| Zasada | Dlaczego |
|
||||
|---|---|
|
||||
| `.env` obok stacka, `chmod 600`, właściciel `ubuntu` | inni użytkownicy systemu nie odczytają |
|
||||
| `.env` w `.gitignore`, w repo tylko `.env.example` | sekret w historii gita zostaje tam na zawsze |
|
||||
| Hasła generowane, nie wymyślane — `openssl rand -base64 32` | brak ponownego użycia |
|
||||
| Osobny sekret na usługę | wyciek jednego nie kompromituje reszty |
|
||||
|
||||
Jeśli sekret **przypadkiem** trafi do repo: nie wystarczy commit usuwający go.
|
||||
Trzeba go **unieważnić i wygenerować nowy** — historia gita pamięta, a jeśli poszła na mirror
|
||||
GitHuba, to jest już poza Twoją kontrolą.
|
||||
|
||||
---
|
||||
|
||||
## 4. Usługi administracyjne
|
||||
|
||||
Panel Traefika, bazy danych, narzędzia debugowe — **nie wystawiamy publicznie**.
|
||||
Dostęp przez tunel SSH:
|
||||
|
||||
```bash
|
||||
ssh -L 8080:127.0.0.1:8080 ubuntu@dfkk.cloud
|
||||
# potem localhost:8080 w przeglądarce
|
||||
```
|
||||
|
||||
Jeśli coś musi być publiczne (np. `status.dfkk.cloud`, żeby działało z telefonu),
|
||||
to za basic auth w Traefiku, z hasłem z generatora.
|
||||
|
||||
**Gitea: rejestracja nowych użytkowników wyłączona natychmiast po instalacji.**
|
||||
Otwarta rejestracja na publicznej instancji to zaproszenie dla botów, które
|
||||
w kilka dni zamienią ją w hosting spamu.
|
||||
|
||||
---
|
||||
|
||||
## 5. Aktualizacje
|
||||
|
||||
| Co | Jak | Kiedy |
|
||||
|---|---|---|
|
||||
| Poprawki bezpieczeństwa Ubuntu | `unattended-upgrades`, automatycznie | codziennie |
|
||||
| Reszta pakietów systemu | ręcznie, `apt upgrade` | miesięczny przegląd |
|
||||
| Obrazy Dockera | ręcznie, podniesienie tagu wersji w compose | miesięczny przegląd |
|
||||
| Restart po aktualizacji kernela | ręcznie, po snapshocie | gdy `/var/run/reboot-required` |
|
||||
|
||||
Automatyczny restart po aktualizacji jest **wyłączony**. Nie chcemy, żeby maszyna
|
||||
sama się przeładowała w środku pracy albo w trakcie działania czegoś produkcyjnego.
|
||||
|
||||
Obrazy pinujemy do konkretnych wersji. `latest` oznacza, że najbliższy `docker compose pull`
|
||||
może podmienić działającą aplikację na wersję z breaking changes — bez ostrzeżenia.
|
||||
|
||||
---
|
||||
|
||||
## 6. Co monitorujemy
|
||||
|
||||
| Sygnał | Próg | Reakcja |
|
||||
|---|---|---|
|
||||
| Zajętość dysku | 75% | sprzątanie wg `40-utrzymanie.md` |
|
||||
| Usługa nie odpowiada | 2 nieudane sprawdzenia | alert mailem |
|
||||
| Certyfikat wygasa | < 14 dni | sprawdzić DNS-01 i klucze OVH |
|
||||
| Nietypowe wpisy w logach SSH | — | przegląd przy miesięcznym audycie |
|
||||
|
||||
Alerty idą na `k.kruszka@hotmail.com` przez zewnętrzny SMTP.
|
||||
Serwera pocztowego tu nie stawiamy — OVH blokuje port 25.
|
||||
|
||||
---
|
||||
|
||||
## 7. Podejrzenie włamania
|
||||
|
||||
Kolejność ma znaczenie. **Nie restartuj i nie „posprzątaj" najpierw** — zniszczysz ślady
|
||||
i stracisz możliwość ustalenia, czym weszli.
|
||||
|
||||
1. Odetnij ruch: `ufw default deny incoming` i zatrzymaj wystawione kontenery
|
||||
2. **Zrób snapshot OVH** — to Twój materiał dowodowy
|
||||
3. Zbierz: `last`, `journalctl -u ssh`, `docker ps -a`, `crontab -l` dla wszystkich użytkowników,
|
||||
`ls -la /tmp /var/tmp`, nietypowe procesy i połączenia (`ss -tulpn`)
|
||||
4. Unieważnij **wszystko**: klucze SSH, tokeny API OVH, token GitHub, hasła w `.env`
|
||||
5. Oceń zakres — jeśli atakujący miał roota, maszyna jest spalona.
|
||||
Postawienie od zera z repo `infra` + backup danych jest szybsze i pewniejsze
|
||||
niż czyszczenie. Dlatego repo `infra` musi być kompletne.
|
||||
6. Dopiero potem: odbudowa, i wpis do `SERVER.md` co się stało
|
||||
|
||||
---
|
||||
|
||||
## 8. Braki, które trzeba domknąć
|
||||
|
||||
Uczciwa lista rzeczy, których jeszcze **nie ma**. Przeglądać przy miesięcznym audycie.
|
||||
|
||||
- [ ] **Backup off-site (restic).** Snapshot OVH to rollback, nie backup — jeden slot,
|
||||
ręczny, nadpisywany, w tej samej infrastrukturze. Do domknięcia **zanim**
|
||||
na serwerze pojawią się pierwsze prawdziwe dane.
|
||||
- [ ] **Test odtworzenia z backupu.** Backup, którego nie odtworzyłeś, nie istnieje.
|
||||
- [ ] **Mirror repo `infra` na GitHub.** Bez tego awaria serwera zabiera kod, historię
|
||||
i konfigurację potrzebną do odtworzenia serwera — naraz.
|
||||
- [ ] **2FA na koncie OVH.** Panel OVH może zrobić z maszyną wszystko, łącznie
|
||||
z reinstalacją. Jest w praktyce ważniejszy niż dostęp SSH.
|
||||
- [ ] **2FA na koncie GitHub.**
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
# SERVER.md — rejestr serwera
|
||||
|
||||
**Źródło prawdy o tym, co stoi na maszynie.** Każda zmiana stanu serwera = wpis tutaj,
|
||||
w tej samej sesji. Jeśli czegoś nie ma w tym pliku — z punktu widzenia dokumentacji to nie istnieje.
|
||||
|
||||
Ostatnia aktualizacja: _(uzupełnić przy pierwszym bootstrapie)_
|
||||
|
||||
---
|
||||
|
||||
## 1. Maszyna
|
||||
|
||||
| Pozycja | Wartość |
|
||||
|---|---|
|
||||
| Dostawca | OVH |
|
||||
| Model | VPS-2 2026 |
|
||||
| Host OVH | `vps-c936b594.vps.ovh.net` |
|
||||
| vCore / RAM / dysk | 6 / 12 GB / 100 GB SSD NVMe |
|
||||
| System | Ubuntu 24.04 LTS |
|
||||
| Lokalizacja | _(uzupełnić)_ |
|
||||
| IPv4 | `54.38.159.156` |
|
||||
| IPv6 | _(uzupełnić)_ |
|
||||
| Snapshot OVH | włączony, 1 slot, ręczny |
|
||||
| Data ostatniego snapshota | _(uzupełniać przy każdym)_ |
|
||||
|
||||
---
|
||||
|
||||
## 2. Domeny
|
||||
|
||||
| Domena | Rola | Rejestrator | DNS |
|
||||
|---|---|---|---|
|
||||
| `dfkk.cloud` | infrastruktura + subdomeny projektów | OVH | OVH |
|
||||
| _(domena 2)_ | wolna, pod projekt | OVH | OVH |
|
||||
| _(domena 3)_ | wolna, pod projekt | OVH | OVH |
|
||||
|
||||
Certyfikaty: Let's Encrypt, wildcard `*.dfkk.cloud`, wyzwanie **DNS-01** przez API OVH.
|
||||
Klucze API OVH: przechowywane w `/srv/infra/stacks/traefik/.env` (poza repo).
|
||||
|
||||
### Zajęte subdomeny
|
||||
|
||||
| Subdomena | Usługa | Publiczna? | Uwagi |
|
||||
|---|---|---|---|
|
||||
| `git.dfkk.cloud` | Gitea | tak | rejestracja wyłączona |
|
||||
| `status.dfkk.cloud` | Uptime Kuma | tak | za basic auth |
|
||||
| `traefik.dfkk.cloud` | Traefik dashboard | nie | tylko przez tunel SSH |
|
||||
|
||||
---
|
||||
|
||||
## 3. Użytkownicy systemowi
|
||||
|
||||
| Użytkownik | Rola | sudo | Uwagi |
|
||||
|---|---|---|---|
|
||||
| `root` | — | — | logowanie po SSH wyłączone przez OVH i przez nas |
|
||||
| `ubuntu` | praca codzienna, Docker, Claude Code | tak | konto domyślne obrazu OVH |
|
||||
|
||||
---
|
||||
|
||||
## 4. Klucze SSH
|
||||
|
||||
Każde urządzenie ma **własny** klucz. Zgubiony laptop = usunięcie jednej linii
|
||||
z `authorized_keys`, bez ruszania pozostałych.
|
||||
|
||||
| Urządzenie | Typ | Komentarz w kluczu | Dodany | Status |
|
||||
|---|---|---|---|---|
|
||||
| MacBook | ed25519 | `kamil@macbook` | _(data)_ | _(aktywny)_ |
|
||||
| Windows | ed25519 | `kamil@windows` | _(data)_ | _(aktywny)_ |
|
||||
| iPhone (Termius/Blink) | ed25519 | `kamil@iphone` | _(data)_ | _(aktywny)_ |
|
||||
| _(klucz istniejący, do weryfikacji)_ | ? | ? | ? | **do sprawdzenia przy bootstrapie** |
|
||||
|
||||
---
|
||||
|
||||
## 5. Porty otwarte na świat
|
||||
|
||||
| Port | Usługa | Uzasadnienie |
|
||||
|---|---|---|
|
||||
| 22 | SSH | dostęp administracyjny, tylko klucze |
|
||||
| 80 | Traefik | przekierowanie na 443 |
|
||||
| 443 | Traefik | cały ruch HTTPS |
|
||||
|
||||
**Nic więcej.** Każdy dodatkowy port wymaga wpisu tutaj z uzasadnieniem.
|
||||
Pamiętaj: opublikowany port kontenera omija ufw — patrz `CLAUDE.md`, sekcja 3.3.
|
||||
|
||||
---
|
||||
|
||||
## 6. Stacki infrastruktury
|
||||
|
||||
| Stack | Ścieżka | Status | Opis |
|
||||
|---|---|---|---|
|
||||
| Traefik | `/srv/infra/stacks/traefik` | _(planowany)_ | reverse proxy, TLS, wejście do wszystkiego |
|
||||
| Gitea | `/srv/infra/stacks/gitea` | _(planowany)_ | repozytoria kodu, mirror na GitHub |
|
||||
| Monitoring | `/srv/infra/stacks/monitoring` | _(planowany)_ | Uptime Kuma, alerty na maila |
|
||||
|
||||
---
|
||||
|
||||
## 7. Projekty
|
||||
|
||||
Tabela uzupełniana przy każdym nowym projekcie (runbook `10-nowa-usluga.md`)
|
||||
i czyszczona przy usuwaniu (runbook `30-usuwanie.md`).
|
||||
|
||||
| Projekt | Katalog | Repo | Domena | Wolumeny | Baza | Backup | Uruchomiony |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| _(brak)_ | | | | | | | |
|
||||
|
||||
---
|
||||
|
||||
## 8. Zadania cykliczne (cron / systemd timers)
|
||||
|
||||
| Zadanie | Harmonogram | Gdzie zdefiniowane | Co robi |
|
||||
|---|---|---|---|
|
||||
| `unattended-upgrades` | codziennie | systemd | aktualizacje bezpieczeństwa |
|
||||
| _(uzupełniać)_ | | | |
|
||||
|
||||
---
|
||||
|
||||
## 9. Konta zewnętrzne powiązane z serwerem
|
||||
|
||||
| Usługa | Do czego | Gdzie klucz/token |
|
||||
|---|---|---|
|
||||
| OVH API | DNS-01 dla Traefika | `/srv/infra/stacks/traefik/.env` |
|
||||
| GitHub | mirror repo `infra` | token w Gitei, ustawienia push mirror |
|
||||
| SMTP (Resend/Brevo) | wysyłka maili z aplikacji | `.env` danego projektu |
|
||||
|
||||
---
|
||||
|
||||
## 10. Dziennik większych zmian
|
||||
|
||||
Krótko, jedna linia, najnowsze na górze. Chodzi o to, żeby po miesiącu dało się
|
||||
odtworzyć, co się działo — nie o pełny changelog.
|
||||
|
||||
| Data | Co się zmieniło | Kto |
|
||||
|---|---|---|
|
||||
| 2026-08-20 | Poprawki `00-bootstrap.md` (Krok 9 tmux append + `focus-events`, Krok 10 katalogi, Krok 12 Claude Code jako wykonany, nowy Krok 14 audyt) i numeracja `HANDOFF.md` sekcja 6 | Claude |
|
||||
@@ -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
|
||||
@@ -0,0 +1,138 @@
|
||||
# Runbook 10 — nowa usługa
|
||||
|
||||
Od pustego katalogu do działającej aplikacji na własnej subdomenie z HTTPS.
|
||||
|
||||
---
|
||||
|
||||
## 1. Ustalenia przed startem
|
||||
|
||||
| Pytanie | Po co |
|
||||
|---|---|
|
||||
| Nazwa projektu (małe litery, myślnik) | katalog, kontenery, sieć, subdomena |
|
||||
| Publiczna czy wewnętrzna? | czy w ogóle wchodzi do sieci `edge` |
|
||||
| Baza danych? Jaka? | osobny kontener w sieci wewnętrznej + wpis do backupu |
|
||||
| Dane trwałe? Gdzie? | bind-mount w `/srv/data/<projekt>` |
|
||||
| Wysyłka maili? | zewnętrzny SMTP, nigdy lokalny |
|
||||
| Szacowane zużycie dysku | 100 GB to nasz limit |
|
||||
|
||||
---
|
||||
|
||||
## 2. Struktura
|
||||
|
||||
```bash
|
||||
mkdir -p /srv/apps/<projekt>
|
||||
mkdir -p /srv/data/<projekt>
|
||||
cd /srv/apps/<projekt>
|
||||
git init
|
||||
```
|
||||
|
||||
Minimum plików: `compose.yaml`, `.env.example`, `.gitignore` (z `.env`), `README.md`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Wzorzec compose
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
image: <obraz>:<KONKRETNA-WERSJA> # nigdy :latest
|
||||
container_name: <projekt>-web
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
networks:
|
||||
- edge
|
||||
- internal
|
||||
volumes:
|
||||
- /srv/data/<projekt>/web:/app/data
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.<projekt>.rule=Host(`<projekt>.dfkk.cloud`)"
|
||||
- "traefik.http.routers.<projekt>.entrypoints=websecure"
|
||||
- "traefik.http.routers.<projekt>.tls.certresolver=ovh"
|
||||
- "traefik.http.services.<projekt>.loadbalancer.server.port=<PORT-WEWNĘTRZNY>"
|
||||
|
||||
db:
|
||||
image: postgres:16.4
|
||||
container_name: <projekt>-db
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
networks:
|
||||
- internal # BEZ edge — baza nie widzi świata
|
||||
volumes:
|
||||
- /srv/data/<projekt>/db:/var/lib/postgresql/data
|
||||
|
||||
networks:
|
||||
edge:
|
||||
external: true
|
||||
internal:
|
||||
name: <projekt>-internal
|
||||
```
|
||||
|
||||
**Trzy rzeczy, na które patrzysz w tym pliku:**
|
||||
|
||||
1. Brak sekcji `ports:` — ruch wchodzi wyłącznie przez Traefika.
|
||||
Opublikowany port omija ufw i jest widoczny z internetu.
|
||||
2. `db` jest tylko w `internal`. Kontener w sieci `edge` jest osiągalny
|
||||
dla każdej innej usługi w `edge` — baza nie ma tam czego szukać.
|
||||
3. Wersja obrazu przypięta. `:latest` oznacza, że przyszły `pull` może
|
||||
podmienić działającą aplikację na wersję z breaking changes.
|
||||
|
||||
---
|
||||
|
||||
## 4. Sekrety
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
chmod 600 .env
|
||||
openssl rand -base64 32 # tak generujemy hasła, nie wymyślamy ich
|
||||
```
|
||||
|
||||
Sprawdź, że `.env` jest w `.gitignore`, **zanim** zrobisz pierwszy commit.
|
||||
Sekret raz wypchnięty do repo trzeba unieważnić, nie usunąć.
|
||||
|
||||
---
|
||||
|
||||
## 5. DNS
|
||||
|
||||
Rekord A dla `<projekt>.dfkk.cloud` na IP serwera — panel OVH.
|
||||
Certyfikat leci z wildcardu `*.dfkk.cloud`, więc **nic dodatkowo nie robisz**.
|
||||
Szczegóły: `20-domena.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Uruchomienie
|
||||
|
||||
```bash
|
||||
docker compose config # walidacja składni i podstawionych zmiennych
|
||||
docker compose up -d
|
||||
docker compose ps
|
||||
docker compose logs -f --tail=50
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Weryfikacja — sprawdzasz, nie zakładasz
|
||||
|
||||
```bash
|
||||
curl -I https://<projekt>.dfkk.cloud # 200 lub 3xx, certyfikat ważny
|
||||
docker compose ps # wszystko "Up", nic w restart loop
|
||||
docker stats --no-stream # zużycie w granicach rozsądku
|
||||
```
|
||||
|
||||
Test negatywny — potwierdzenie, że baza **nie** jest wystawiona:
|
||||
|
||||
```bash
|
||||
# z lokalnej maszyny, nie z serwera:
|
||||
nc -zv <IP-serwera> 5432 # ma odmówić połączenia
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Domknięcie
|
||||
|
||||
- [ ] Commit w repo projektu, push do Gitei
|
||||
- [ ] Wpis w `SERVER.md`: projekt, katalog, repo, domena, wolumeny, baza
|
||||
- [ ] Wpis w `SERVER.md`, sekcja 2: zajęta subdomena
|
||||
- [ ] Jeśli są dane trwałe — dopisanie ścieżki do listy backupu
|
||||
- [ ] Monitor w Uptime Kuma
|
||||
- [ ] `df -h` — kontrola, ile dysku ubyło
|
||||
@@ -0,0 +1,101 @@
|
||||
# Runbook 20 — domeny i DNS
|
||||
|
||||
Wszystkie trzy domeny są w OVH, DNS też w OVH. To upraszcza sprawę: Traefik może
|
||||
używać API OVH do wyzwania **DNS-01**, więc dostajemy certyfikaty wildcard.
|
||||
|
||||
---
|
||||
|
||||
## 1. Dlaczego DNS-01, a nie HTTP-01
|
||||
|
||||
| | HTTP-01 | DNS-01 (nasz wybór) |
|
||||
|---|---|---|
|
||||
| Jak działa | Let's Encrypt puka na port 80 | Traefik wpisuje rekord TXT przez API |
|
||||
| Wildcard | niemożliwy | możliwy |
|
||||
| Nowa subdomena | osobny certyfikat, osobne wyzwanie | działa od razu, zero akcji |
|
||||
| Usługi niepubliczne | nie da się | da się |
|
||||
| Wymaga | otwartego portu 80 | kluczy API dostawcy DNS |
|
||||
|
||||
Przy wildcardzie `*.dfkk.cloud` dodanie `nowyprojekt.dfkk.cloud` to jeden rekord A
|
||||
i restart kontenera. Certyfikatem nie zajmujesz się w ogóle.
|
||||
|
||||
---
|
||||
|
||||
## 2. Klucze API OVH — jednorazowo
|
||||
|
||||
1. Wejdź na stronę tworzenia tokenów API OVH dla właściwego regionu konta
|
||||
(uwaga: konto jest w regionie **CA** — patrz adres panelu w `SERVER.md`,
|
||||
to zmienia zarówno adres, pod którym generujesz token, jak i endpoint w konfiguracji)
|
||||
2. Uprawnienia zawężone do stref DNS — `GET`, `POST`, `PUT`, `DELETE`
|
||||
na `/domain/zone/*`. Nie dawaj tokenowi pełnego dostępu do konta.
|
||||
3. Ważność: bez limitu, ale **wpisz token do `SERVER.md`, sekcja 9**, żeby wiadomo było,
|
||||
co unieważnić przy incydencie
|
||||
4. Trzy wartości — application key, application secret, consumer key —
|
||||
lądują w `/srv/infra/stacks/traefik/.env`, `chmod 600`, poza repo
|
||||
|
||||
---
|
||||
|
||||
## 3. Nowa subdomena
|
||||
|
||||
```
|
||||
Typ: A
|
||||
Nazwa: <projekt>
|
||||
Cel: <IP serwera>
|
||||
TTL: 300 (na czas testów; potem można podnieść)
|
||||
```
|
||||
|
||||
Sprawdzenie propagacji:
|
||||
|
||||
```bash
|
||||
dig +short <projekt>.dfkk.cloud
|
||||
```
|
||||
|
||||
Dopóki `dig` nie zwraca IP serwera, nie ma sensu debugować Traefika —
|
||||
problem jest w DNS, nie w kontenerze.
|
||||
|
||||
---
|
||||
|
||||
## 4. Nowa domena (druga lub trzecia)
|
||||
|
||||
Wildcard obejmuje **tylko** `*.dfkk.cloud`. Kolejna domena wymaga:
|
||||
|
||||
1. Rekordu A na IP serwera
|
||||
2. Dopisania nowej domeny do konfiguracji certresolvera w Traefiku
|
||||
(osobny wpis `domains` z `main` i `sans`)
|
||||
3. Sprawdzenia, że token API OVH ma uprawnienia **także do tej strefy**
|
||||
4. Wpisu w `SERVER.md`, sekcja 2
|
||||
|
||||
---
|
||||
|
||||
## 5. Odpinanie domeny
|
||||
|
||||
Kolejność odwrotna niż przy podpinaniu:
|
||||
|
||||
- [ ] Usuń labele Traefika z compose i zrestartuj usługę
|
||||
- [ ] Usuń rekord A w panelu OVH
|
||||
- [ ] Sprawdź, że nie ma innych rekordów wskazujących na serwer (CNAME, MX)
|
||||
- [ ] Wykreśl subdomenę z `SERVER.md`, sekcja 2
|
||||
- [ ] Usuń monitor z Uptime Kuma
|
||||
|
||||
Martwy rekord A wskazujący na Twój serwer to nie tylko bałagan — jeśli kiedyś oddasz
|
||||
to IP, ktoś inny odziedziczy ruch kierowany na Twoją nazwę.
|
||||
|
||||
---
|
||||
|
||||
## 6. Diagnostyka
|
||||
|
||||
| Objaw | Gdzie szukać |
|
||||
|---|---|
|
||||
| `dig` nie zwraca IP | DNS, panel OVH — nie ruszaj serwera |
|
||||
| DNS ok, ale 404 z Traefika | literówka w `Host()` albo brak `traefik.enable=true` |
|
||||
| DNS ok, ale połączenie odrzucone | kontener nie działa lub nie jest w sieci `edge` |
|
||||
| Błąd certyfikatu | logi Traefika, klucze API OVH, uprawnienia do strefy |
|
||||
| Certyfikat „fake" / staging | w konfiguracji został adres staging Let's Encrypt |
|
||||
|
||||
```bash
|
||||
docker logs traefik --tail=100 | grep -i -E "acme|error|certificate"
|
||||
```
|
||||
|
||||
> **Uwaga na limity Let's Encrypt.** Przy debugowaniu certyfikatów łatwo wpaść
|
||||
> w limit żądań i zostać zablokowanym na tydzień. Jeśli certyfikat nie chce się
|
||||
> wystawić — przełącz się na serwer **staging** Let's Encrypt, dopracuj konfigurację
|
||||
> tam, i dopiero potem wróć na produkcyjny.
|
||||
@@ -0,0 +1,138 @@
|
||||
# Runbook 30 — usuwanie projektu
|
||||
|
||||
Najważniejszy runbook dla utrzymania porządku. Usunięcie projektu to **nie** jest
|
||||
`docker compose down`. Osierocone wolumeny, martwe rekordy DNS i wyłączone kontenery
|
||||
zajmujące gigabajty to dokładnie ten bałagan, którego ten serwer ma nie mieć.
|
||||
|
||||
**Przechodzisz całą listę. Za każdym razem.**
|
||||
|
||||
---
|
||||
|
||||
## Krok 0 — zabezpieczenie danych
|
||||
|
||||
Zanim cokolwiek skasujesz:
|
||||
|
||||
```bash
|
||||
cd /srv/apps/<projekt>
|
||||
docker compose ps # co faktycznie działa
|
||||
docker compose config # jakie wolumeny są w grze
|
||||
du -sh /srv/data/<projekt> # ile tam danych
|
||||
```
|
||||
|
||||
**Baza danych — zrzut przed usunięciem, zawsze:**
|
||||
|
||||
```bash
|
||||
docker compose exec db pg_dump -U <user> <baza> > ~/<projekt>-final-$(date +%F).sql
|
||||
```
|
||||
|
||||
Trzymaj zrzut poza `/srv/data/<projekt>` — inaczej skasujesz go razem z resztą.
|
||||
Rozważ przeniesienie na lokalną maszynę. „Na pewno już niepotrzebne" bywa
|
||||
weryfikowane dwa miesiące później.
|
||||
|
||||
> **Punkt zatrzymania:** potwierdź z Kacprem, że dane są zbędne albo zabezpieczone.
|
||||
> To operacja nieodwracalna.
|
||||
|
||||
---
|
||||
|
||||
## Krok 1 — zatrzymanie
|
||||
|
||||
```bash
|
||||
docker compose down # BEZ -v na tym etapie
|
||||
docker compose ps -a
|
||||
```
|
||||
|
||||
`-v` kasuje wolumeny natychmiast. Zostawiamy je do kroku 4, żeby był margines na refleksję.
|
||||
|
||||
---
|
||||
|
||||
## Krok 2 — DNS i Traefik
|
||||
|
||||
- [ ] Usuń rekord A w panelu OVH
|
||||
- [ ] `dig +short <projekt>.dfkk.cloud` — ma nie zwracać nic
|
||||
- [ ] Sprawdź, że żadna inna usługa nie używa tej subdomeny
|
||||
|
||||
---
|
||||
|
||||
## Krok 3 — monitoring
|
||||
|
||||
- [ ] Usuń monitor z Uptime Kuma (inaczej dostaniesz alert o „awarii" usługi,
|
||||
którą sam skasowałeś)
|
||||
- [ ] Usuń ścieżki projektu z listy backupu
|
||||
|
||||
---
|
||||
|
||||
## Krok 4 — dane i wolumeny
|
||||
|
||||
```bash
|
||||
docker volume ls | grep <projekt>
|
||||
docker volume rm <nazwa> # świadomie, po jednym
|
||||
sudo rm -rf /srv/data/<projekt>
|
||||
```
|
||||
|
||||
> **Punkt zatrzymania:** `rm -rf` na katalogu z danymi wymaga wyraźnej zgody.
|
||||
> Przeczytaj ścieżkę na głos przed naciśnięciem Enter.
|
||||
|
||||
---
|
||||
|
||||
## Krok 5 — obrazy i sieci
|
||||
|
||||
```bash
|
||||
docker images | grep <projekt>
|
||||
docker rmi <obraz> # tylko jeśli nic innego go nie używa
|
||||
docker network rm <projekt>-internal
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Krok 6 — kod
|
||||
|
||||
```bash
|
||||
sudo rm -rf /srv/apps/<projekt>
|
||||
```
|
||||
|
||||
Repo w Gitei: **archiwizuj, nie kasuj.** Archiwum jest tanie, kod czasem wraca.
|
||||
Kasowanie repozytorium ma sens tylko wtedy, gdy trafiły do niego sekrety —
|
||||
a wtedy i tak trzeba je najpierw unieważnić.
|
||||
|
||||
---
|
||||
|
||||
## Krok 7 — dokumentacja
|
||||
|
||||
- [ ] Wykreśl projekt z `SERVER.md`, sekcja 7
|
||||
- [ ] Wykreśl subdomenę z `SERVER.md`, sekcja 2
|
||||
- [ ] Usuń zadania cykliczne z sekcji 8, jeśli jakieś były
|
||||
- [ ] Wpis w dzienniku zmian, sekcja 10
|
||||
- [ ] Commit w repo `infra`
|
||||
|
||||
---
|
||||
|
||||
## Krok 8 — kontrola
|
||||
|
||||
```bash
|
||||
df -h / # miejsce faktycznie wróciło?
|
||||
docker system df # ile zajmują obrazy, kontenery, wolumeny
|
||||
docker ps -a # brak śladów po projekcie
|
||||
docker volume ls -f dangling=true
|
||||
```
|
||||
|
||||
Jeśli miejsce **nie** wróciło — coś zostało. Szukaj, zanim uznasz sprawę za zamkniętą.
|
||||
|
||||
---
|
||||
|
||||
## Sprzątanie osieroconych zasobów
|
||||
|
||||
`docker system prune` bywa kuszący. Zanim go użyjesz — sprawdź, co usunie:
|
||||
|
||||
```bash
|
||||
docker volume ls -f dangling=true # wolumeny bez kontenera
|
||||
docker images -f dangling=true # warstwy bez tagu
|
||||
```
|
||||
|
||||
**`docker system prune -a --volumes` potrafi skasować wolumen zatrzymanej usługi,
|
||||
która wcale nie miała zniknąć.** Kasujemy pojedynczo, po sprawdzeniu każdej pozycji.
|
||||
Bezpieczna wersja, bez dotykania wolumenów:
|
||||
|
||||
```bash
|
||||
docker image prune -a # tylko nieużywane obrazy
|
||||
docker builder prune # cache budowania — zwykle największy zysk
|
||||
```
|
||||
@@ -0,0 +1,121 @@
|
||||
# Runbook 40 — utrzymanie
|
||||
|
||||
Serwer psuje się powoli i po cichu. Te przeglądy zajmują kilka minut i zapobiegają
|
||||
sytuacji, w której o problemie dowiadujesz się od użytkownika.
|
||||
|
||||
---
|
||||
|
||||
## Przegląd tygodniowy — 5 minut
|
||||
|
||||
```bash
|
||||
df -h / # < 75%
|
||||
docker system df # co rośnie
|
||||
docker ps -a # nic w restart loop
|
||||
free -h # swap w użyciu? coś przecieka
|
||||
uptime # load average
|
||||
sudo fail2ban-client status sshd
|
||||
```
|
||||
|
||||
Czerwone flagi:
|
||||
|
||||
| Objaw | Co znaczy |
|
||||
|---|---|
|
||||
| Dysk > 75% | czas na sprzątanie, nie za tydzień |
|
||||
| Kontener z rosnącym `RESTARTS` | crash loop — sprawdź logi |
|
||||
| Swap mocno zajęty przy 12 GB RAM | wyciek pamięci w którymś kontenerze |
|
||||
| Skok liczby banów fail2ban | ktoś się interesuje maszyną |
|
||||
|
||||
---
|
||||
|
||||
## Przegląd miesięczny — 30 minut
|
||||
|
||||
### Aktualizacje systemu
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
apt list --upgradable
|
||||
```
|
||||
|
||||
Zanim `upgrade`: **snapshot OVH.** Potem:
|
||||
|
||||
```bash
|
||||
sudo apt upgrade -y
|
||||
ls /var/run/reboot-required 2>/dev/null && echo "wymagany restart"
|
||||
```
|
||||
|
||||
Restart robimy świadomie, po snapshocie, nie w środku pracy.
|
||||
|
||||
### Aktualizacje obrazów
|
||||
|
||||
Obrazy są przypięte do wersji, więc `pull` sam nic nie podniesie — i o to chodzi.
|
||||
Podnoszenie wersji to świadoma decyzja:
|
||||
|
||||
1. Sprawdź changelog obrazu pod kątem breaking changes
|
||||
2. Podnieś tag w `compose.yaml`
|
||||
3. Snapshot
|
||||
4. `docker compose pull && docker compose up -d`
|
||||
5. Weryfikacja: `curl -I`, logi, `docker compose ps`
|
||||
6. Commit
|
||||
|
||||
Jedna usługa naraz. Aktualizacja pięciu rzeczy jednocześnie oznacza, że przy awarii
|
||||
nie wiesz, która zawiniła.
|
||||
|
||||
### Sprzątanie dysku
|
||||
|
||||
```bash
|
||||
docker system df
|
||||
docker image prune -a # bezpieczne
|
||||
docker builder prune # zwykle najwięcej odzyskuje
|
||||
sudo journalctl --disk-usage
|
||||
sudo journalctl --vacuum-time=30d
|
||||
```
|
||||
|
||||
Wolumeny — **nigdy hurtem**:
|
||||
|
||||
```bash
|
||||
docker volume ls -f dangling=true
|
||||
# sprawdź każdy, potem docker volume rm <nazwa>
|
||||
```
|
||||
|
||||
### Bezpieczeństwo
|
||||
|
||||
```bash
|
||||
sudo journalctl -u ssh --since "1 month ago" | grep -i "accepted" | tail -30
|
||||
sudo cat /home/ubuntu/.ssh/authorized_keys # tylko znane klucze?
|
||||
sudo ufw status verbose # tylko 22/80/443?
|
||||
docker ps --format "table {{.Names}}\t{{.Ports}}" # nic na 0.0.0.0?
|
||||
```
|
||||
|
||||
Ostatnia komenda jest ważniejsza, niż wygląda. Opublikowany port kontenera na
|
||||
`0.0.0.0` omija ufw i jest widoczny z internetu — a `ufw status` tego nie pokaże.
|
||||
|
||||
### Certyfikaty
|
||||
|
||||
```bash
|
||||
echo | openssl s_client -connect git.dfkk.cloud:443 2>/dev/null | \
|
||||
openssl x509 -noout -dates
|
||||
```
|
||||
|
||||
Odnowienie następuje automatycznie ~30 dni przed wygaśnięciem. Jeśli zostało
|
||||
mniej niż 14 dni — coś się zacięło, sprawdź logi Traefika i klucze API OVH.
|
||||
|
||||
### Dokumentacja
|
||||
|
||||
- [ ] `SERVER.md` zgodny ze stanem faktycznym?
|
||||
- [ ] Lista braków w `SECURITY.md`, sekcja 8 — coś do odhaczenia?
|
||||
- [ ] Repo `infra` zacommitowane i zmirrorowane na GitHub?
|
||||
|
||||
---
|
||||
|
||||
## Przegląd kwartalny
|
||||
|
||||
- [ ] **Test odtworzenia z backupu.** Nie „sprawdzenie, że plik istnieje" —
|
||||
faktyczne przywrócenie danych i weryfikacja, że są kompletne.
|
||||
Backup, którego nie odtworzyłeś, to założenie, nie zabezpieczenie.
|
||||
- [ ] Rotacja tokenów API (OVH, GitHub, SMTP)
|
||||
- [ ] Przegląd kluczy SSH — czy wszystkie urządzenia z listy nadal istnieją i są Twoje
|
||||
- [ ] Przegląd projektów: czy wszystko, co działa, jest jeszcze potrzebne?
|
||||
Nieużywana usługa to zajęty dysk, otwarta powierzchnia ataku i pakiety,
|
||||
których nikt nie aktualizuje.
|
||||
- [ ] Weryfikacja, czy `CLAUDE.md` odpowiada temu, jak faktycznie pracujemy —
|
||||
jeśli zasada jest regularnie omijana, trzeba ją zmienić albo zacząć stosować
|
||||
@@ -0,0 +1,145 @@
|
||||
# Runbook 50 — awaria i rollback
|
||||
|
||||
Do czytania, gdy coś nie działa. Zasada nadrzędna: **najpierw diagnoza, potem zmiany.**
|
||||
Chaotyczne restartowanie wszystkiego zaciera ślady i zamienia jeden problem w trzy.
|
||||
|
||||
---
|
||||
|
||||
## Kolejność diagnozy
|
||||
|
||||
Idź od zewnątrz do środka. Zatrzymaj się na pierwszym kroku, który zawiedzie —
|
||||
tam jest problem, dalej nie ma sensu szukać.
|
||||
|
||||
```bash
|
||||
# 1. Czy maszyna żyje?
|
||||
ping <IP>
|
||||
ssh ubuntu@<IP>
|
||||
|
||||
# 2. Czy nie brakuje zasobów? (najczęstsza przyczyna)
|
||||
df -h /
|
||||
free -h
|
||||
uptime
|
||||
|
||||
# 3. Czy Docker działa?
|
||||
sudo systemctl status docker
|
||||
docker ps
|
||||
|
||||
# 4. Czy Traefik żyje?
|
||||
docker logs traefik --tail=50
|
||||
|
||||
# 5. Czy konkretna usługa żyje?
|
||||
cd /srv/apps/<projekt>
|
||||
docker compose ps
|
||||
docker compose logs --tail=100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Typowe przyczyny, w kolejności prawdopodobieństwa
|
||||
|
||||
| Objaw | Sprawdź najpierw |
|
||||
|---|---|
|
||||
| Wszystko przestało działać naraz | `df -h` — pełny dysk zatrzymuje wszystko |
|
||||
| Jedna usługa w restart loop | `docker compose logs` — zwykle brak zmiennej w `.env` lub zajęty port |
|
||||
| 502 z Traefika | kontener działa, ale zły port w labelu `loadbalancer.server.port` |
|
||||
| 404 z Traefika | zła reguła `Host()` albo brak `traefik.enable=true` |
|
||||
| Błąd certyfikatu | logi Traefika, klucze API OVH, limity Let's Encrypt |
|
||||
| Usługa wolna, serwer obciążony | `docker stats` — który kontener zjada CPU/RAM |
|
||||
| Kontener zabity bez śladu | OOM killer: `dmesg -T \| grep -i oom` |
|
||||
|
||||
---
|
||||
|
||||
## Pełny dysk — najczęstsza awaria tej maszyny
|
||||
|
||||
Przy 100 GB to realne ryzyko. Objawy bywają mylące: bazy przestają zapisywać,
|
||||
Docker nie startuje kontenerów, logi się urywają.
|
||||
|
||||
```bash
|
||||
df -h /
|
||||
sudo du -h --max-depth=1 /var | sort -hr | head
|
||||
sudo du -h --max-depth=1 /srv | sort -hr | head
|
||||
docker system df
|
||||
```
|
||||
|
||||
Szybkie odzyskanie miejsca, od najbezpieczniejszego:
|
||||
|
||||
```bash
|
||||
sudo journalctl --vacuum-size=200M
|
||||
docker builder prune -f
|
||||
docker image prune -a -f
|
||||
```
|
||||
|
||||
Dopiero potem szukaj przyczyny — zwykle jest to jeden kontener logujący bez limitu
|
||||
albo rosnąca baza. Limity logów są w `/etc/docker/daemon.json`.
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
### Poziom 1 — konfiguracja usługi
|
||||
|
||||
```bash
|
||||
cd /srv/apps/<projekt>
|
||||
git log --oneline -10
|
||||
git revert <commit>
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Poziom 2 — wersja obrazu
|
||||
|
||||
Cofnij tag w `compose.yaml` do poprzedniej działającej wersji, `docker compose up -d`.
|
||||
Dlatego przypinamy wersje — z `:latest` nie masz do czego wrócić.
|
||||
|
||||
### Poziom 3 — snapshot OVH
|
||||
|
||||
Przywrócenie snapshota **cofa całą maszynę** do stanu z chwili jego zrobienia.
|
||||
Wszystko, co powstało później — inne projekty, dane, commity — znika.
|
||||
|
||||
Zanim to zrobisz:
|
||||
|
||||
- [ ] Czy problem faktycznie wymaga cofnięcia całej maszyny?
|
||||
- [ ] Co powstało od czasu snapshota i czego nieodwracalnie stracisz?
|
||||
- [ ] Czy dasz radę wyciągnąć potrzebne dane przed przywróceniem?
|
||||
- [ ] Data snapshota — jest w `SERVER.md`
|
||||
|
||||
To ostateczność. W większości przypadków szybciej jest naprawić usługę.
|
||||
|
||||
---
|
||||
|
||||
## Odzyskanie dostępu po zablokowaniu SSH
|
||||
|
||||
Jeśli błąd w `sshd_config` albo ufw odciął Cię od maszyny:
|
||||
|
||||
1. Panel OVH → Twój VPS → **konsola KVM**
|
||||
2. Logowanie jako `root` lub `ubuntu` (dlatego `ubuntu` ma ustawione hasło —
|
||||
przez konsolę klucz SSH nie pomoże)
|
||||
3. Napraw konfigurację, `sudo sshd -t`, restart usługi
|
||||
4. Test z zewnątrz **przed** zamknięciem konsoli
|
||||
|
||||
To jest powód, dla którego przy każdej zmianie w SSH trzymamy otwartą drugą sesję.
|
||||
|
||||
---
|
||||
|
||||
## Utrata Gitei
|
||||
|
||||
Gitea stoi na tym samym serwerze, którym zarządza — więc jej awaria zabiera kod
|
||||
i konfigurację naraz. Dlatego:
|
||||
|
||||
- repo `infra` jest zmirrorowane na prywatne repo GitHuba
|
||||
- na maszynie zawsze jest aktualna kopia robocza `/srv/infra`
|
||||
- odtworzenie: sklonuj `infra` z GitHuba, postaw Traefika i Gitea z `00-bootstrap.md`,
|
||||
krok 13, przywróć dane repozytoriów z backupu
|
||||
|
||||
Jeśli mirror na GitHub nie jest jeszcze skonfigurowany — to najpilniejsza rzecz
|
||||
z listy braków w `SECURITY.md`.
|
||||
|
||||
---
|
||||
|
||||
## Po każdej awarii
|
||||
|
||||
- [ ] Wpis w dzienniku zmian w `SERVER.md`: co się stało, co pomogło
|
||||
- [ ] Jeśli przyczyna była systemowa — dopisz punkt kontrolny do `40-utrzymanie.md`
|
||||
- [ ] Jeśli dało się temu zapobiec regułą — dopisz ją do `CLAUDE.md`
|
||||
|
||||
Runbooki mają rosnąć razem z doświadczeniem. Awaria, z której nic nie wynikło
|
||||
dla dokumentacji, powtórzy się.
|
||||
Reference in New Issue
Block a user