d98ade42ce
The docs folder had drifted into a mixed-language contract: IMPLEMENTATION_SPEC.md was entirely in English while the rest was Russian with English headings. Prose is now Russian throughout; English is kept only where it is technically load-bearing (commands, tool output, config keys, file/module/table names, tc handles) and for terms with no settled Russian equivalent in this context (drift, reconcile, peer, namespace, handshake). docs/README.md now states that contract explicitly. Rewriting the spec surfaced several places where it described intent rather than the code. Corrected against traffic_core/src: - module layout: the spec showed traffic/ and firewall/ as directories with model/inspect/apply submodules. They are single files, and sysctl_tuning.rs was missing from the tree entirely. - data model: Peer was documented as carrying public key, handshake and counters. It carries only ipv4; runtime state lives in a separate PeerRuntime, and there is no desired-state struct at all - it is derived inline from config plus sorted peers. RuntimeTarget.netns_path is a String, not a PathBuf. - interface detection: the spec listed a third step taking the first non-loopback interface with an IPv4 address. No such fallback exists; detection bails with an explicit error, which is safer and is now documented as deliberate. - reconcile: the pseudo-flow folded firewall into reconcile_traffic. They are two independent reconciles with separate locks. - locking: only traffic.lock was documented. There are three locks, and sysctl_tuning.lock has a 30s timeout rather than 10s. Stale-lock recovery via /proc/<pid> was undocumented. - sysctl_tuning: the module also manages /etc/modules-load.d/90-awg2-harness-bbr.conf when BBR is active, and refuses to touch either managed file without its marker. Both docs and the acceptance checklist claimed only the sysctl.d file was written. - status contract: the documented output was outdated. All 15 printed sections are now listed in the order status.rs emits them, including the degraded path when no container is found, and the fact that the Network tuning block is printed between the System service line and the container lines. - swap: size was documented as a constant 2048 MB instead of swap.size_mb, and the /etc/fstab.awg2-harness.bak backup was undocumented. - apply/verify: the tolerated-error lists are now the actual closed sets from the code. Acceptance checklist gained checks for the daemon startup failure policy (sysctl/swap fatal, traffic/firewall not), lock contention and stale-lock recovery, degraded status, the modules-load file, and the marker guard. The stale host wg requirement was dropped from its host-tools item. Every path, table, counter and log string quoted in the docs was verified to exist in the source. cargo test --workspace: 110 + 4 passed.
333 lines
12 KiB
Markdown
333 lines
12 KiB
Markdown
# Установка
|
||
|
||
Этот документ описывает production-порядок установки AWG2 Harness. Подробный runtime-план с SSH hardening и Amnezia preflight находится в корневом [`README.md`](../README.md).
|
||
|
||
## Контракт развёртывания
|
||
|
||
- Целевой production-хост: Debian 13 amd64.
|
||
- Amnezia ставится до AWG2 Harness официальным self-hosted способом через приложение AmneziaVPN.
|
||
- Проект рассчитан на AmneziaWG/AWG 2.0 и compatible legacy-сценарий AmneziaWG/AWG 1.5.
|
||
- SSH должен быть заранее настроен. Production-пример: порт `2323`, вход по ключу, password login disabled.
|
||
- Production-хост получает готовый Debian-пакет; Rust/cargo/source tree на нём не требуются.
|
||
- `/etc/awg2-harness/config.toml` создаётся вручную. `config.example.toml` не копируется автоматически как боевой config.
|
||
- Пакет не устанавливает Docker/Amnezia и не включает services автоматически.
|
||
- `sysctl_tuning` не применяется в `postinst`; применение только на старте daemon или вручную через CLI.
|
||
|
||
## A. Сборочная машина
|
||
|
||
### A1. Подготовить сборочное окружение
|
||
|
||
```bash
|
||
cd awg2_harness
|
||
chmod +x tools/build.sh
|
||
./tools/build.sh --prepare-build-env --bootstrap-rust --yes
|
||
```
|
||
|
||
### A2. Проверить код
|
||
|
||
```bash
|
||
cargo fmt --manifest-path Cargo.toml --all --check
|
||
cargo clippy --locked --workspace --all-targets --manifest-path Cargo.toml -- -D warnings
|
||
cargo test --locked --workspace --release --manifest-path Cargo.toml
|
||
```
|
||
|
||
То же самое выполняет build helper:
|
||
|
||
```bash
|
||
./tools/build.sh --build
|
||
```
|
||
|
||
### A3. Собрать Debian-пакет
|
||
|
||
```bash
|
||
./tools/build.sh --package --version 0.1.6
|
||
(cd dist && sha256sum -c SHA256SUMS)
|
||
dpkg-deb -I dist/awg2-harness_0.1.6_amd64.deb
|
||
dpkg-deb -c dist/awg2-harness_0.1.6_amd64.deb | sort
|
||
```
|
||
|
||
`--version` можно опустить, если версия в `traffic_core/Cargo.toml` уже равна release version. Version override влияет только на metadata `.deb`.
|
||
|
||
## B. Подготовить production-хост
|
||
|
||
### B1. SSH
|
||
|
||
Сначала сгенерируйте отдельную пару ключей для Amnezia на Windows (RSA PEM):
|
||
|
||
```cmd
|
||
ssh-keygen -t rsa -b 4096 -m PEM -f %USERPROFILE%\.ssh\amnezia_key
|
||
```
|
||
|
||
Дальше разделяйте ключи строго:
|
||
|
||
- на сервер в `/root/.ssh/authorized_keys` кладётся содержимое `%USERPROFILE%\.ssh\amnezia_key.pub`;
|
||
- в AmneziaVPN в поле `Password or SSH private key` вставляется содержимое `%USERPROFILE%\.ssh\amnezia_key` (без `.pub`).
|
||
|
||
`.pub` нельзя вставлять в поле private key в AmneziaVPN.
|
||
|
||
Установка `.pub` на сервер:
|
||
|
||
```bash
|
||
install -d -m 0700 /root/.ssh
|
||
nano /root/.ssh/authorized_keys
|
||
chmod 0600 /root/.ssh/authorized_keys
|
||
```
|
||
|
||
Production-пример:
|
||
|
||
```bash
|
||
install -d -m 0755 /etc/ssh/sshd_config.d
|
||
cat >/etc/ssh/sshd_config.d/99-awg2-production.conf <<'EOF'
|
||
Port 2323
|
||
PermitRootLogin prohibit-password
|
||
PasswordAuthentication no
|
||
KbdInteractiveAuthentication no
|
||
PubkeyAuthentication yes
|
||
EOF
|
||
sshd -t
|
||
systemctl reload ssh 2>/dev/null || systemctl reload sshd
|
||
ss -ltnp | grep ':2323'
|
||
```
|
||
|
||
Обязательно проверьте вход с нового терминала:
|
||
|
||
```cmd
|
||
ssh -i %USERPROFILE%\.ssh\amnezia_key -p 2323 root@<server-ip>
|
||
```
|
||
|
||
Проверить содержимое ключей в Windows:
|
||
|
||
```cmd
|
||
type %USERPROFILE%\.ssh\amnezia_key.pub
|
||
type %USERPROFILE%\.ssh\amnezia_key
|
||
```
|
||
|
||
### B2. Amnezia
|
||
|
||
Установите AmneziaWG/AWG через официальное приложение AmneziaVPN в режиме self-hosted server.
|
||
|
||
Заполнение полей в интерфейсе AmneziaVPN:
|
||
|
||
```text
|
||
Configure your server:
|
||
|
||
Server IP address [:port]:
|
||
<server-ip>:2323
|
||
|
||
SSH Username:
|
||
root
|
||
|
||
Password or SSH private key:
|
||
полное содержимое приватного ключа %USERPROFILE%\.ssh\amnezia_key
|
||
```
|
||
|
||
Важно: в это поле не вставляется `.pub`. Файл `.pub` нужен только для `/root/.ssh/authorized_keys` на сервере.
|
||
|
||
Проверка после установки:
|
||
|
||
```bash
|
||
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
|
||
C="$(docker ps --format '{{.Names}}' | grep -E '^(amnezia-awg2|amnezia-awg3|amnezia-awg)$' | head -n1)"
|
||
echo "container=$C"
|
||
docker port "$C"
|
||
docker exec "$C" awg show
|
||
```
|
||
|
||
Если контейнер называется иначе или интерфейс не `awg0`, отразите это в `/etc/awg2-harness/config.toml`.
|
||
|
||
`awg` — реальный бинарь в официальном образе AmneziaWG; `wg` внутри контейнера является symlink на него и тоже сработает. На хосте `wireguard-tools` устанавливать не нужно: обвязка читает peer state только через `docker exec`.
|
||
|
||
### B3. Установить AmneziaDNS
|
||
|
||
Установите AmneziaDNS из desktop-приложения AmneziaVPN:
|
||
|
||
```text
|
||
Servers → <server> → Services → AmneziaDNS → Install
|
||
```
|
||
|
||
Затем включите его для подключения:
|
||
|
||
```text
|
||
Settings → Connection → DNS servers → Use AmneziaDNS
|
||
```
|
||
|
||
Ожидаемое состояние Docker:
|
||
|
||
```bash
|
||
docker ps -a --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
|
||
```
|
||
|
||
Ожидаемая строка для DNS-контейнера:
|
||
|
||
```text
|
||
amnezia-dns amnezia-dns Up ... (healthy) 53/tcp, 53/udp
|
||
```
|
||
|
||
Проверка публикации портов:
|
||
|
||
```bash
|
||
AWG="$(docker ps --format '{{.Names}}' | grep -E '^(amnezia-awg2|amnezia-awg3|amnezia-awg)$' | head -n1)"
|
||
echo "AWG=$AWG"
|
||
|
||
docker inspect -f 'ports={{json .NetworkSettings.Ports}}' amnezia-dns
|
||
docker inspect -f 'networks={{range $n,$v := .NetworkSettings.Networks}}{{printf "%s=%s " $n $v.IPAddress}}{{end}}' amnezia-dns
|
||
docker inspect -f 'networks={{range $n,$v := .NetworkSettings.Networks}}{{printf "%s=%s " $n $v.IPAddress}}{{end}}' "$AWG"
|
||
```
|
||
|
||
Ожидаемый результат по портам:
|
||
|
||
```text
|
||
ports={"53/tcp":null,"53/udp":null}
|
||
```
|
||
|
||
Это означает, что DNS не опубликован на публичном host-интерфейсе и остаётся доступным только через VPN/Docker path.
|
||
|
||
Negative-check публичного DNS:
|
||
|
||
```bash
|
||
nslookup example.com <server-public-ip>
|
||
```
|
||
|
||
Ожидаемое поведение: публичный DNS-запрос **не должен успешно резолвиться**. Типичные безопасные варианты ответа: `connection refused`, `timeout` или `no servers could be reached`.
|
||
|
||
Optional deep-check DNS path (если нужна углублённая проверка):
|
||
|
||
```bash
|
||
apt install -y dnsutils
|
||
|
||
AWG="$(docker ps --format '{{.Names}}' | grep -E '^(amnezia-awg2|amnezia-awg3|amnezia-awg)$' | head -n1)"
|
||
DNS_IP="$(docker inspect -f '{{range $n,$v := .NetworkSettings.Networks}}{{if $v.IPAddress}}{{println $v.IPAddress}}{{end}}{{end}}' amnezia-dns | head -n1)"
|
||
DNS_PID="$(docker inspect -f '{{.State.Pid}}' amnezia-dns)"
|
||
AWG_PID="$(docker inspect -f '{{.State.Pid}}' "$AWG")"
|
||
|
||
echo "AWG=$AWG"
|
||
echo "DNS_IP=$DNS_IP"
|
||
nsenter -t "$DNS_PID" -n ss -lntup
|
||
nsenter -t "$AWG_PID" -n ip route
|
||
nsenter -t "$AWG_PID" -n dig @"$DNS_IP" example.com A +short
|
||
```
|
||
|
||
## C. Передача артефактов
|
||
|
||
Ниже два production-сценария установки.
|
||
|
||
### C1. Вариант A: конфиг передаётся вместе с `.deb`
|
||
|
||
```bash
|
||
scp -P 2323 dist/awg2-harness_*_amd64.deb root@<server-ip>:/root/
|
||
scp -P 2323 dist/SHA256SUMS root@<server-ip>:/root/
|
||
scp -P 2323 config.example.toml root@<server-ip>:/root/awg2-harness.config.toml
|
||
```
|
||
|
||
Перед установкой отредактируйте `/root/awg2-harness.config.toml`. Минимально проверьте:
|
||
|
||
- `[host_firewall].ssh_port` совпадает с реальным SSH-портом (`2323` в production-примере);
|
||
- `[host_firewall].amnezia_port` совпадает с published UDP port Amnezia;
|
||
- `[amnezia].peer_ipv4_cidr` совпадает с подсетью clients;
|
||
- `[anti_torrent].peer_ipv4_cidr` обычно равен `[amnezia].peer_ipv4_cidr`;
|
||
- `peer_limit` и `root_rate` отражают нужную traffic policy.
|
||
|
||
### C2. Вариант B (основной): конфиг создаётся на production после установки
|
||
|
||
```bash
|
||
scp -P 2323 dist/awg2-harness_*_amd64.deb root@<server-ip>:/root/
|
||
scp -P 2323 dist/SHA256SUMS root@<server-ip>:/root/
|
||
```
|
||
|
||
## D. Установка на production
|
||
|
||
### D1. Вариант A
|
||
|
||
```bash
|
||
cd /root
|
||
sha256sum -c SHA256SUMS
|
||
apt install ./awg2-harness_*_amd64.deb
|
||
install -d -m 0755 /etc/awg2-harness
|
||
install -m 0644 /root/awg2-harness.config.toml /etc/awg2-harness/config.toml
|
||
systemctl daemon-reload
|
||
```
|
||
|
||
### D2. Вариант B (основной)
|
||
|
||
```bash
|
||
cd /root
|
||
sha256sum -c SHA256SUMS
|
||
apt install ./awg2-harness_*_amd64.deb
|
||
install -d -m 0755 /etc/awg2-harness
|
||
cp /usr/share/doc/awg2-harness/repo/config.example.toml /etc/awg2-harness/config.toml
|
||
nano /etc/awg2-harness/config.toml
|
||
systemctl daemon-reload
|
||
```
|
||
|
||
Перед первым `apply-firewall` обязательно перепроверьте:
|
||
|
||
- `[host_firewall].ssh_port` совпадает с реально слушающим `sshd` портом;
|
||
- `[host_firewall].amnezia_port` совпадает с published Docker UDP port;
|
||
- `[amnezia].peer_ipv4_cidr` совпадает с peer-подсетью;
|
||
- `[anti_torrent].peer_ipv4_cidr` обычно тот же CIDR.
|
||
|
||
## E. Preflight до включения сервисов
|
||
|
||
```bash
|
||
awg2-harness --config /etc/awg2-harness/config.toml validate-config
|
||
awg2-harness --config /etc/awg2-harness/config.toml validate-runtime
|
||
awg2-harness --config /etc/awg2-harness/config.toml ensure-swap
|
||
awg2-harness --config /etc/awg2-harness/config.toml apply-firewall
|
||
awg2-harness --config /etc/awg2-harness/config.toml apply-limits
|
||
awg2-harness --config /etc/awg2-harness/config.toml status
|
||
```
|
||
|
||
Ожидаемый здоровый результат:
|
||
|
||
```text
|
||
Traffic actual:
|
||
drift: no
|
||
Host firewall:
|
||
drift: no
|
||
Anti-torrent:
|
||
drift: no
|
||
Swap:
|
||
drift: no
|
||
```
|
||
|
||
Если peers ещё нет, `apply-limits` напечатает `no peers found, skip tc apply`. Для пустого сервера это здоровое состояние.
|
||
|
||
При `swap.enabled = true` AWG2 Harness намеренно делает swap эксклюзивным: один управляемый swapfile, одна swap-запись в `/etc/fstab`, заданный `swappiness`.
|
||
|
||
## F. Включение сервисов
|
||
|
||
```bash
|
||
systemctl enable --now awg2-harness.service
|
||
systemctl enable --now awg2-reboot-scheduler.service
|
||
|
||
systemctl status awg2-harness.service --no-pager -l
|
||
systemctl status awg2-reboot-scheduler.service --no-pager -l
|
||
journalctl -u awg2-harness.service --since '5 minutes ago' --no-pager
|
||
journalctl -u awg2-reboot-scheduler.service --since '5 minutes ago' --no-pager
|
||
```
|
||
|
||
Через 30–60 секунд:
|
||
|
||
```bash
|
||
awg2-harness --config /etc/awg2-harness/config.toml status
|
||
```
|
||
|
||
## G. Проверка перезагрузкой
|
||
|
||
```bash
|
||
reboot
|
||
```
|
||
|
||
После восстановления подключения:
|
||
|
||
```bash
|
||
systemctl status docker --no-pager -l
|
||
systemctl status awg2-harness.service --no-pager -l
|
||
systemctl status awg2-reboot-scheduler.service --no-pager -l
|
||
awg2-harness --config /etc/awg2-harness/config.toml status
|
||
```
|
||
|
||
## H. Ручная установка из дерева исходников
|
||
|
||
`./tools/build.sh --install` и `./tools/build.sh --enable` существуют для staging и отладки. Это не рекомендуемый production-путь: на production используйте `.deb`.
|