Files
ts3-monolith-go/README.md
T
2026-11-04 23:44:40 +01:00

159 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TS3 monolith (Go)
Jedna aplikacja zastępująca stare `sk.linuxiarz.pl` i `ts3stats.linuxiarz.pl`.
Wygląd dashboardu (topbar + sidebar, karty, panele, jasny/ciemny motyw)
jest celowo spójny ze stylem [PostfixAdminRS](https://github.com/postfixadmin/postfixadmin) —
ten sam układ tokenów kolorów, te same komponenty UI.
## Funkcjonalność
- wykrycie klienta TeamSpeak po IP widzianym przez HTTP i ServerQuery;
- utworzenie permanentnego kanału z nazwą, tematem, opcjonalnym hasłem,
limitem użytkowników (`channel_maxclients`) i jakością kodeka
(`channel_codec_quality`, 1–10);
- `setclientchannelgroup` dla wskazanego `client_database_id`;
- `clientmove` wskazanego `clid` do nowego kanału;
- dashboard statystyk: liczba online na żywo (auto-odświeżanie co 60 s przez
`GET /stats/live`), uptime/wersja/platforma serwera (`serverinfo`),
wykresy RRD: 12h, 24h, 72h, tydzień, miesiąc, rok — w kolorystyce
dopasowanej do motywu aplikacji (ciemne tło, akcentowa linia, watermark);
- przełącznik jasny/ciemny motyw (zapamiętywany w `localStorage`);
- formularz pod `/embed/channel`, przeznaczony do osadzenia na innej domenie.
Przy jednym kliencie pod danym IP wszystko działa automatycznie. Jeżeli kilka klientów TS3 ma ten sam publiczny IP (NAT), aplikacja prosi o wybór nicku i wysyła 6-cyfrowy kod prywatną wiadomością TS3. Dzięki temu nie przenosi przypadkowej osoby i nie nadaje jej Channel Admina.
## Szybki development
```
cp .env.example .env # uzupełnij sekrety
make quick # najszybszy build (bez testów/trimpath) -> bin/ts3monolith
make dev # quick build + start z lokalnym .env
make test # go test ./...
make build # pełny build produkcyjny (testy + trimpath + bez symboli)
```
## Uruchomienie — Docker
1. Skopiuj `.env.example` do `.env` i ustaw sekrety oraz dozwoloną domenę iframe.
2. Zachowaj `data/linuxiarz.rrd` (kopia dotychczasowej bazy z archiwum) albo
zainicjuj nową bazę — patrz sekcja [RRD](#rrd).
3. Zapewnij zapis do katalogu `data` dla UID 10001: `chown -R 10001:10001 data`.
4. Uruchom: `docker compose up -d --build`.
5. Reverse proxy kieruje HTTPS do `127.0.0.1:8080`.
## Uruchomienie — pakiet .deb + systemd
Alternatywa dla Dockera, zalecana na dedykowanym hoście Debian/Ubuntu:
```
make deb # scripts/build-deb.sh -> packages/ts3monolith_<wersja>_amd64.deb
sudo dpkg -i packages/ts3monolith_*.deb
sudo nano /etc/default/ts3monolith # APP_SECRET, TS_QUERY_USER, TS_QUERY_PASSWORD, ...
sudo -u ts3monolith /usr/lib/ts3monolith/init-rrd # tylko przy świeżej instalacji, bez starej bazy
sudo systemctl enable --now ts3monolith
```
`scripts/build-deb.sh` buduje wszystko w kontenerze Docker — dokładnie tak
samo jak odpowiadający skrypt w PostfixAdminRS — więc host nie potrzebuje
lokalnie zainstalowanego Go ani żadnych zależności poza samym Dockerem.
Wersja obrazu (`golang:<wersja z go.mod>-bookworm`) jest odczytywana z
`go.mod`, a sam moduł nie ma żadnych zewnętrznych zależności, więc build jest
w pełni offline po ściągnięciu obrazu. Wynikowy pakiet trafia do `packages/`.
Pakiet instaluje:
- `/usr/bin/ts3monolith` — binarka;
- `/etc/default/ts3monolith` — zmienne środowiskowe (conffile);
- `/lib/systemd/system/ts3monolith.service` — jednostka z pełnym hardeningiem
(`ProtectSystem=strict`, `NoNewPrivileges`, `RestrictAddressFamilies`,
`MemoryDenyWriteExecute`, `SystemCallFilter=@system-service`, ...), takim
samym duchem jak w PostfixAdminRS;
- `/usr/lib/ts3monolith/init-rrd` — jednorazowy skrypt tworzący świeżą bazę
RRD z RRA pokrywającymi wszystkie zakresy wykresów (12h…rok), gdy nie ma
archiwalnego pliku do skopiowania;
- dedykowanego użytkownika systemowego `ts3monolith` (bez loginu, bez home).
`install.sh` z korzenia repo zostaje jako prostszy, ręczny installer bez
budowania pakietu `.deb` (np. do szybkiego postawienia na hoście bez
uprawnień do `dpkg`).
Przykład iframe:
```html
<iframe
src="https://app.example.org/embed/channel"
title="Utwórz kanał TeamSpeak"
loading="lazy"
referrerpolicy="no-referrer"
style="width:100%;min-height:620px;border:0">
</iframe>
```
`FRAME_ANCESTORS` musi zawierać origin strony nadrzędnej, np. `https://www.example.org`.
## TeamSpeak ServerQuery
Najbezpieczniej uruchamiać aplikację na tym samym hoście lub w prywatnej sieci z TS3 i ustawić `TS_QUERY_HOST=127.0.0.1`/adres prywatny. Nie wystawiaj portu 10011 publicznie. Query powinien mieć tylko prawa potrzebne do: `clientlist -ip -uid`, `channelcreate`, `setclientchannelgroup`, `clientmove`, `sendtextmessage`, `channeldelete` (rollback), `serverinfo` (karty uptime/wersji na dashboardzie).
## RRD
Aplikacja używa systemowego `rrdtool`, a nie biblioteki Go, więc istniejący plik RRD pozostaje bez migracji. Co 60 s liczona jest liczba klientów `client_type=0` na wybranym virtual serverze i zapisywana do DS `online`. Błąd ServerQuery nie zapisuje sztucznego zera.
Świeża instalacja bez archiwalnej bazy: `packaging/ts3monolith-init-rrd`
(instalowany jako `/usr/lib/ts3monolith/init-rrd` w pakiecie `.deb`) tworzy
nową bazę `rrdtool create` z RRA pokrywającymi 12h/24h/72h/tydzień/miesiąc/rok
przy kroku 60 s — ten sam layout logiczny co istniejące wykresy aplikacji.
Wykresy renderowane są w kolorach dopasowanych do motywu dashboardu
(`--panel`/`--field`/`--accent` z `internal/web/assets/app.css`), z cienkim
wypełnieniem pod linią, watermarkiem z datą generacji i `--slope-mode` dla
gładszej linii. Każdy zakres jest renderowany dwukrotnie — `<zakres>.png`
(ciemna paleta) i `<zakres>-light.png` (jasna paleta) — a front-end
(`internal/web/assets/app.js`) przełącza `src` na właściwy wariant przy
zmianie motywu, więc wykresy zawsze dopasowane są do aktywnego trybu
jasny/ciemny.
Jeżeli docelowy serwer ma inną architekturę/endianness niż źródłowy x86_64 little-endian, przenieś RRD przez `rrdtool dump`/`rrdtool restore` zamiast kopiowania binarnego pliku.
## Bezpieczeństwo / fingerprinting
- brak PHP, frameworków frontendowych, CDN i nagłówka `X-Powered-By`;
- standardowy `net/http` nie dodaje nagłówka `Server`;
- CSP z kontrolowanym `frame-ancestors`, HSTS, `nosniff`, restrykcyjne Permissions-Policy, `style-src`/`script-src 'self'` (bez inline — cały CSS/JS w plikach statycznych);
- nagłówki proxy są ufane wyłącznie z `TRUSTED_PROXIES`;
- stateless HMAC tokeny formularzy i klientów, sprawdzanie `Origin`, limity request body i rate limiting;
- sekrety tylko przez środowisko, brak danych dostępowych w repo;
- rollback kanału, jeśli nadanie grupy lub przeniesienie klienta się nie powiedzie;
- kontener non-root, `cap_drop: ALL`, `no-new-privileges`, read-only root filesystem (Docker) albo analogiczny hardening jednostki systemd (pakiet `.deb`);
- build z `-trimpath` i usuniętymi symbolami/debug info.
Nie da się zagwarantować, że stos technologiczny będzie całkowicie nierozpoznawalny na podstawie zachowania sieci/TLS. Celem jest brak prostych bannerów, wersji i sygnatur frameworka.
## Rate limiting
Domyślnie **wyłączony** (`RATE_LIMIT_ENABLED=false`) — endpointy mają limity
zahardkodowane w kodzie od początku, ale same w sobie nie chronią niczego
krytycznego (ServerQuery i tak ma własny timeout, `channelcreate` i tak
wymaga poprawnego tokenu CSRF), a przy nietypowym ruchu (kilku odwiedzających
za tym samym IP/NAT, kilka kart, lightbox z wykresami) dawały false-positive
`429` bez realnej korzyści. Włącz (`RATE_LIMIT_ENABLED=true`), jeśli chcesz
dodatkową warstwę ochrony przed nadużyciami na konkretnym endpoincie.
Każdy limit jest per-IP (po rozwiązaniu `TRUSTED_PROXIES`/`X-Forwarded-For`,
patrz `clientIP()` w `internal/web/server.go`) i liczony w oknie czasowym
podanym w nawiasie:
| Zmienna | Endpoint | Domyślnie | Co chroni |
|---|---|---|---|
| `RATE_LIMIT_STATS` | `GET /stats` (na minutę) | 60 | render strony statystyk |
| `RATE_LIMIT_STATS_LIVE` | `GET /stats/live` (na minutę) | 60 | auto-odświeżanie licznika online co 60 s |
| `RATE_LIMIT_GRAPH` | `GET /graph/*.png` (na minutę) | 600 | wykresy RRD — PNG są cache'owane na dysku ~55 s, więc limit jest wysoki: jeden widok strony sam zużywa 6 zakresów × 2 motywy, a NAT/kilka kart to łatwo pomnoży |
| `RATE_LIMIT_CHANNEL` | `POST /channel/create` (na minutę) | 40 | tworzenie kanału |
| `RATE_LIMIT_VERIFY` | `POST /verify/start` (na minutę) | 10 | start weryfikacji nicku (NAT) |
| `RATE_LIMIT_VERIFY_DONE` | `POST /verify/complete` (na minutę) | 10 | wpisanie 6-cyfrowego kodu |
| `RATE_LIMIT_CREATE` | `POST /channel/create` po weryfikacji (na godzinę) | 5 | utworzenie kanału po weryfikacji nicku przez NAT |
Przy przekroczeniu limitu endpoint odpowiada `429 Too Many Requests` z
nagłówkiem `Retry-After: 60`. Wartości domyślne są w `.env.example` i
`packaging/ts3monolith.default` — dostosuj je do realnego ruchu na danej
instalacji zanim włączysz `RATE_LIMIT_ENABLED` na produkcji.