Сборка: добавить production packaging flow через .deb

Добавлен package-режим в tools/build.sh: --package, --dist-dir, --version. Реализована сборка Debian-пакета, metadata/control, postinst/postrm и SHA256SUMS. Установочные пути унифицированы под /usr/sbin и /lib/systemd/system для production-модели.
This commit is contained in:
2026-05-10 17:26:11 +05:00
parent 76c6b52f4e
commit 9d2d2bc0c2
9 changed files with 307 additions and 143 deletions
+36
View File
@@ -20,6 +20,42 @@ Production reboot path — только встроенный scheduler в [`traf
Важно: ничего из legacy-деревьев не устанавливается, не вызывается и не требуется для runtime пути `awg2-harness`.
## Production deployment model
AWG2 Harness собирается на отдельной Debian 13 amd64 build-machine.
Production Amnezia-хост **не должен** требовать Rust, cargo, clippy, source tree и legacy bash-скрипты. На production-хост переносится готовый Debian-пакет и вручную подготовленный `/etc/awg2-harness/config.toml`.
Build machine:
```bash
./tools/build.sh --prepare-build-env --bootstrap-rust --yes
./tools/build.sh --package
sha256sum -c dist/SHA256SUMS
```
Копирование на production host:
```bash
scp dist/awg2-harness_*_amd64.deb root@vpn-host:/root/
scp config.toml root@vpn-host:/root/awg2-harness.config.toml
```
Production host:
```bash
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
awg2-harness validate-config --config /etc/awg2-harness/config.toml
awg2-harness apply-limits --config /etc/awg2-harness/config.toml
awg2-harness apply-firewall --config /etc/awg2-harness/config.toml
awg2-harness status --config /etc/awg2-harness/config.toml
systemctl enable --now awg2-harness.service
```
Ничего из [`Amnezia limits (old bash)/`](Amnezia%20limits%20(old%20bash)/) и [`reboot_module/`](reboot_module/) не запускается в production-пути.
Основная документация:
- [`docs/MIGRATION.md`](docs/MIGRATION.md) — исправленная миграционная стратегия bash → Rust multi-orchestrator.
+1 -1
View File
@@ -7,7 +7,7 @@ Requires=docker.service
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/awg2-harness daemon --config /etc/awg2-harness/config.toml
ExecStart=/usr/sbin/awg2-harness daemon --config /etc/awg2-harness/config.toml
Restart=always
RestartSec=5
StandardOutput=journal
+14
View File
@@ -115,3 +115,17 @@
- [ ] Известно, как остановить `awg2-harness.service`.
- [ ] Известно, как вернуть `amnezia-tc.service` и `amnezia-tc-watcher.service`.
- [ ] Rollback проверен на тестовом сервере.
## K. Packaging / release
- [ ] Package собирается на отдельной Debian 13 amd64 build-machine.
- [ ] Production host не требует Rust/cargo/source tree.
- [ ] `./tools/build.sh --package` создаёт `dist/awg2-harness_<version>_amd64.deb`.
- [ ] `dist/SHA256SUMS` валидирует checksum пакета.
- [ ] Пакет ставит бинарь в `/usr/sbin/awg2-harness`.
- [ ] Пакет ставит unit в `/lib/systemd/system/awg2-harness.service`.
- [ ] Пакет не устанавливает `/etc/awg2-harness/config.toml` автоматически.
- [ ] Пакет не включает/не стартует сервис автоматически.
- [ ] `apt install ./awg2-harness_<version>_amd64.deb` проходит на clean Debian 13 Amnezia host.
- [ ] После ручной установки config `awg2-harness validate-config` проходит.
- [ ] Сервис стартует только после явного `systemctl enable --now awg2-harness.service`.
+2 -2
View File
@@ -26,7 +26,7 @@ It does not replace Amnezia. It controls the surrounding system state that the o
One systemd service runs:
```bash
/usr/local/bin/awg2-harness daemon --config /etc/awg2-harness/config.toml
/usr/sbin/awg2-harness daemon --config /etc/awg2-harness/config.toml
```
The daemon starts as root and keeps running. On startup it performs initial reconcile. During runtime it reacts to Docker events and also runs a periodic reconcile loop.
@@ -311,7 +311,7 @@ table inet awg2_harness {
Target namespace: the same Amnezia network namespace where `tc` shaping is applied. Do not assume the initial host namespace sees original peer source addresses. If Amnezia performs NAT inside the container namespace, the host Docker bridge may only see the container address, not `10.8.1.x`.
Default threshold: `200` concurrent flows per peer IP. `250` is the conservative compatibility setting if there are complaints from unusually connection-heavy WebSocket/WebRTC/trading-browser workloads. `150` is more aggressive and should only be used after real traffic observation.
Default threshold: `100` concurrent flows per peer IP. `150` is the conservative compatibility setting if there are complaints from unusually connection-heavy WebSocket/WebRTC/trading-browser workloads. `70` is more aggressive and should only be used after real traffic observation.
Rule placement: use `forward` hook with priority `filter` or any priority after conntrack lookup. Restrict evaluation to `ct state new` so established flows continue normally and only new flow attempts beyond the threshold are dropped.
+37 -120
View File
@@ -1,156 +1,73 @@
# Установка AWG2 Harness на production-хост
# Installation
## 1. Предусловия
## Deployment contract
Хост:
- Build выполняется на отдельной Debian 13 amd64 build-machine.
- Production-хост получает готовый Debian-пакет.
- На production-хосте не требуются Rust/cargo/source tree.
- Amnezia должна быть уже установлена и работать через Docker.
- Production-конфиг создаётся вручную; [`config.example.toml`](../config.example.toml) не копируется автоматически как боевой конфиг.
- Debian 13 amd64;
- root-доступ;
- SSH настроен заранее;
- AmneziaWG/AWG 2.0 установлена официальным способом;
- контейнер Amnezia запускается через Docker;
- сервер выделенный, без соседних сервисов, чьи сетевые правила нужно сохранять.
## A. Build machine
На хосте должны быть доступны:
```bash
docker
systemctl
ip
tc
wg
nsenter
modprobe
nft
```
`tc/ip/wg` внутри контейнера полезны для fallback, но целевая модель — host tools + вход в network namespace контейнера.
## 2. Сборка бинаря
Сборку лучше выполнять на отдельной build-машине с тем же целевым ABI.
### A1. Подготовка build environment
```bash
./tools/build.sh --prepare-build-env --bootstrap-rust --yes
./tools/build.sh
```
Альтернатива (ручной запуск cargo из корня репозитория):
### A2. Build и тесты
```bash
cargo build --locked --manifest-path traffic_core/Cargo.toml --release
./tools/build.sh --build
```
Проверки перед переносом:
### A3. Сборка пакета
```bash
cargo fmt --manifest-path traffic_core/Cargo.toml --all --check
cargo clippy --locked --manifest-path traffic_core/Cargo.toml --all-targets -- -D warnings
cargo test --locked --manifest-path traffic_core/Cargo.toml --release
file traffic_core/target/release/awg2-harness
ldd traffic_core/target/release/awg2-harness
./tools/build.sh --package
sha256sum -c dist/SHA256SUMS
```
## 3. Установка файлов
## B. Transfer artifacts
```bash
scp dist/awg2-harness_*_amd64.deb root@vpn-host:/root/
scp config.toml root@vpn-host:/root/awg2-harness.config.toml
```
## C. Production host
### C1. Install package
```bash
apt install ./awg2-harness_*_amd64.deb
```
### C2. Install config
```bash
install -m 0755 traffic_core/target/release/awg2-harness /usr/local/bin/awg2-harness
install -d -m 0755 /etc/awg2-harness
install -m 0644 config.toml /etc/awg2-harness/config.toml
install -m 0644 /root/awg2-harness.config.toml /etc/awg2-harness/config.toml
```
Важно: production-установка **не должна** автоматически копировать [`config.example.toml`](../config.example.toml) как боевой конфиг.
Если используется [`tools/build.sh`](../tools/build.sh), режим `--install` завершится ошибкой при отсутствии `/etc/awg2-harness/config.toml`.
Режим `--install` копирует только бинарь и unit, выполняет `systemctl daemon-reload`, но не делает `systemctl enable --now`.
Сначала подготовьте осознанный production-конфиг вручную.
Проверка конфига:
### C3. Validate and apply
```bash
awg2-harness validate-config --config /etc/awg2-harness/config.toml
```
## 4. Первичное применение traffic topology
```bash
awg2-harness apply-limits --config /etc/awg2-harness/config.toml
awg2-harness apply-firewall --config /etc/awg2-harness/config.toml
awg2-harness status --config /etc/awg2-harness/config.toml
```
Ожидается:
- найден контейнер `amnezia-awg2` или `amnezia-awg`;
- найден network namespace контейнера;
- найден AWG/WG-интерфейс;
- найдены peers из `wg show all allowed-ips`;
- создана egress topology на AWG/WG-интерфейсе;
- создан `ifb0`;
- создан ingress redirect в `ifb0`;
- на каждого peer есть egress и ingress class/filter;
- если anti-torrent включён, в Amnezia netns есть `table inet awg2_harness` и drop counter отображается в status.
## 5. Настройка swap
### C4. Enable daemon
```bash
awg2-harness ensure-swap --config /etc/awg2-harness/config.toml
swapon --show
cat /proc/sys/vm/swappiness
```
Ожидается `/swapfile` размером 2 ГБ, права `0600`, запись в `/etc/fstab`, низкая склонность к использованию.
## 6. Установка systemd unit
`/etc/systemd/system/awg2-harness.service`:
Reference unit в репозитории: [`awg2-harness.service`](../awg2-harness.service).
```ini
[Unit]
Description=AWG2 Harness multi-orchestrator
After=network-online.target docker.service
Wants=network-online.target
Requires=docker.service
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/awg2-harness daemon --config /etc/awg2-harness/config.toml
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
```
Активация:
```bash
systemd-analyze verify /etc/systemd/system/awg2-harness.service
systemctl daemon-reload
systemctl enable --now awg2-harness.service
```
## 7. Проверка после запуска daemon
## D. Manual source-tree install (staging/debug only)
```bash
systemctl status awg2-harness.service
journalctl -u awg2-harness.service -n 100 --no-pager
awg2-harness status --config /etc/awg2-harness/config.toml
```
Режим [`./tools/build.sh --install`](../tools/build.sh) предназначен для staging/debug с checkout-репозиторием.
## 8. Проверка auto-healing
```bash
docker restart amnezia-awg2
sleep 30
awg2-harness status --config /etc/awg2-harness/config.toml
```
Затем проверить Docker restart и host reboot. После каждого события status должен показывать healthy state без ручного запуска old bash.
## 9. Rollback
Production-путь AWG2 Harness не включает возврат к old bash в штатный процесс выкладки.
Это **не** основной production deployment path.
+1 -1
View File
@@ -233,7 +233,7 @@ Requires=docker.service
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/awg2-harness daemon --config /etc/awg2-harness/config.toml
ExecStart=/usr/sbin/awg2-harness daemon --config /etc/awg2-harness/config.toml
Restart=always
RestartSec=5
StandardOutput=journal
+21 -8
View File
@@ -10,6 +10,10 @@ Unit: [`awg2-harness.service`](../awg2-harness.service).
- Debian/Ubuntu (apt-based)
- amd64/x86_64
## Purpose
Скрипт используется на build-machine для проверки, сборки и упаковки AWG2 Harness.
## Default mode
```bash
@@ -28,29 +32,38 @@ Unit: [`awg2-harness.service`](../awg2-harness.service).
- `--prepare-runtime-env` — установить недостающие runtime-пакеты;
- `--bootstrap-rust` — установить rustup и pinned toolchain;
- `--build` — запустить build gate явно;
- `--package` — собрать Debian-пакет в `dist/`;
- `--dist-dir <path>` — директория для release artifacts;
- `--version <version>` — переопределить версию пакета;
- `--install` — установить бинарь + systemd unit (без enable/start);
- `--enable` — `systemctl enable --now awg2-harness.service`;
- `--yes` — подтверждение системных изменений (apt/rustup);
- `--skip-fmt` — пропустить `cargo fmt --check`;
- `--help` — справка.
## Typical build machine flow
## Build machine flow
```bash
./tools/build.sh --prepare-build-env --bootstrap-rust --yes
./tools/build.sh
./tools/build.sh --build
./tools/build.sh --package
```
## Typical production host flow
## Production host flow
В нормальном production deployment **не нужно** клонировать репозиторий на целевой хост.
Ставится сгенерированный пакет:
```bash
./tools/build.sh --prepare-runtime-env --yes
install -d -m 0755 /etc/awg2-harness
install -m 0644 config.toml /etc/awg2-harness/config.toml
./tools/build.sh --install
./tools/build.sh --enable
apt install ./awg2-harness_*_amd64.deb
```
## Manual source-tree install
`--install` и `--enable` оставлены для staging/debug из checked-out source tree.
Это **не рекомендуемый** production path.
## What the script intentionally does not do
- не устанавливает Docker автоматически;
+141 -6
View File
@@ -11,12 +11,16 @@ PREPARE_BUILD_ENV=false
PREPARE_RUNTIME_ENV=false
BOOTSTRAP_RUST=false
DO_BUILD=false
PACKAGE=false
INSTALL=false
ENABLE=false
YES=false
SKIP_FMT=false
HAS_ACTION=false
DIST_DIR="$ROOT_DIR/dist"
PACKAGE_VERSION=""
RUN_AS_ROOT=()
BUILD_PACKAGES=(
@@ -26,6 +30,7 @@ BUILD_PACKAGES=(
pkg-config
file
shellcheck
dpkg-dev
)
RUNTIME_PACKAGES=(
@@ -46,6 +51,8 @@ Usage:
./tools/build.sh --prepare-runtime-env --yes
./tools/build.sh --bootstrap-rust --yes
./tools/build.sh --build
./tools/build.sh --package
./tools/build.sh --build --package
./tools/build.sh --install
./tools/build.sh --enable
@@ -54,27 +61,45 @@ Flags:
--prepare-runtime-env Install missing runtime dependencies via apt
--bootstrap-rust Install rustup/toolchain pinned by this repo
--build Run build gate (default when no action flags)
--package Build Debian package into dist/ (runs build gate by default)
--dist-dir <path> Output directory for package artifacts (default: ./dist)
--version <version> Override package version (default: read from Cargo.toml)
--install Install binary and systemd unit (no enable/start)
--enable Enable and start awg2-harness.service
--yes Confirm system mutations (apt/rustup)
--skip-fmt Skip cargo fmt --check
--help Show this help
Notes:
--install is for manual source-tree installs (staging/debug).
Preferred production flow is package-based deployment via apt install ./awg2-harness_<version>_amd64.deb
EOF
}
for arg in "$@"; do
case "$arg" in
while [[ $# -gt 0 ]]; do
case "$1" in
--prepare-build-env) PREPARE_BUILD_ENV=true; HAS_ACTION=true ;;
--prepare-runtime-env) PREPARE_RUNTIME_ENV=true; HAS_ACTION=true ;;
--bootstrap-rust) BOOTSTRAP_RUST=true; HAS_ACTION=true ;;
--build) DO_BUILD=true; HAS_ACTION=true ;;
--package) PACKAGE=true; HAS_ACTION=true ;;
--dist-dir)
[[ $# -ge 2 ]] || { echo "--dist-dir requires value"; exit 1; }
DIST_DIR="$2"
shift
;;
--version)
[[ $# -ge 2 ]] || { echo "--version requires value"; exit 1; }
PACKAGE_VERSION="$2"
shift
;;
--install) INSTALL=true; HAS_ACTION=true ;;
--enable) ENABLE=true; HAS_ACTION=true ;;
--yes) YES=true ;;
--skip-fmt) SKIP_FMT=true ;;
--help|-h) usage; exit 0 ;;
*) echo "Unknown arg: $arg"; usage; exit 1 ;;
*) echo "Unknown arg: $1"; usage; exit 1 ;;
esac
shift
done
if [[ "$HAS_ACTION" == false ]]; then
@@ -219,15 +244,121 @@ install_artifacts() {
fi
setup_root_runner
"${RUN_AS_ROOT[@]}" install -m 0755 "$TARGET_BIN" /usr/local/bin/awg2-harness
"${RUN_AS_ROOT[@]}" install -m 0644 "$ROOT_DIR/awg2-harness.service" /etc/systemd/system/awg2-harness.service
"${RUN_AS_ROOT[@]}" install -d -m 0755 /usr/sbin
"${RUN_AS_ROOT[@]}" install -m 0755 "$TARGET_BIN" /usr/sbin/awg2-harness
"${RUN_AS_ROOT[@]}" install -d -m 0755 /lib/systemd/system
"${RUN_AS_ROOT[@]}" install -m 0644 "$ROOT_DIR/awg2-harness.service" /lib/systemd/system/awg2-harness.service
if command -v systemd-analyze >/dev/null 2>&1; then
"${RUN_AS_ROOT[@]}" systemd-analyze verify /etc/systemd/system/awg2-harness.service
"${RUN_AS_ROOT[@]}" systemd-analyze verify /lib/systemd/system/awg2-harness.service
fi
"${RUN_AS_ROOT[@]}" systemctl daemon-reload
}
read_package_version_from_manifest() {
awk -F '"' '/^version\s*=\s*"/ { print $2; exit }' "$CRATE_MANIFEST"
}
package_deb() {
require_cmd dpkg-deb
require_cmd install
require_cmd sha256sum
require_cmd file
require_cmd ldd
require_cmd mktemp
if [[ "$DO_BUILD" != true ]]; then
prepare_build_gate
fi
if [[ ! -f "$TARGET_BIN" ]]; then
echo "Binary not found after build: $TARGET_BIN"
exit 1
fi
local version
if [[ -n "$PACKAGE_VERSION" ]]; then
version="$PACKAGE_VERSION"
else
version="$(read_package_version_from_manifest)"
fi
if [[ -z "$version" ]]; then
echo "Unable to determine package version"
exit 1
fi
local staging
staging="$(mktemp -d)"
trap 'rm -rf "$staging"' RETURN
install -d -m 0755 "$staging/DEBIAN"
install -d -m 0755 "$staging/usr/sbin"
install -d -m 0755 "$staging/lib/systemd/system"
install -d -m 0755 "$staging/usr/share/doc/awg2-harness"
install -m 0755 "$TARGET_BIN" "$staging/usr/sbin/awg2-harness"
install -m 0644 "$ROOT_DIR/awg2-harness.service" "$staging/lib/systemd/system/awg2-harness.service"
install -m 0644 "$ROOT_DIR/config.example.toml" "$staging/usr/share/doc/awg2-harness/config.example.toml"
install -m 0644 "$ROOT_DIR/README.md" "$staging/usr/share/doc/awg2-harness/README.md"
install -m 0644 "$ROOT_DIR/docs/INSTALLATION.md" "$staging/usr/share/doc/awg2-harness/INSTALLATION.md"
cat >"$staging/DEBIAN/control" <<EOF
Package: awg2-harness
Version: $version
Section: net
Priority: optional
Architecture: amd64
Maintainer: AWG2 Harness
Depends: libc6, iproute2, wireguard-tools, nftables, kmod, util-linux, procps, systemd
Recommends: docker.io
Description: Host-side orchestrator for AmneziaWG traffic shaping and lifecycle recovery
AWG2 Harness manages AmneziaWG traffic shaping, IFB ingress shaping,
nftables anti-torrent guard, reboot scheduling, swap setup and daemonized
drift recovery on dedicated Debian hosts.
EOF
cat >"$staging/DEBIAN/postinst" <<'EOF'
#!/bin/sh
set -e
if command -v systemctl >/dev/null 2>&1; then
systemctl daemon-reload || true
fi
exit 0
EOF
cat >"$staging/DEBIAN/postrm" <<'EOF'
#!/bin/sh
set -e
if command -v systemctl >/dev/null 2>&1; then
systemctl daemon-reload || true
fi
exit 0
EOF
chmod 0755 "$staging/DEBIAN/postinst" "$staging/DEBIAN/postrm"
mkdir -p "$DIST_DIR"
local pkg_name
pkg_name="awg2-harness_${version}_amd64.deb"
local pkg_path
pkg_path="$DIST_DIR/$pkg_name"
dpkg-deb --build "$staging" "$pkg_path"
(
cd "$DIST_DIR"
sha256sum "$pkg_name" > SHA256SUMS
)
echo "Package created: $pkg_path"
echo "Checksums: $DIST_DIR/SHA256SUMS"
}
enable_service() {
require_cmd systemctl
setup_root_runner
@@ -252,6 +383,10 @@ if [[ "$DO_BUILD" == true ]]; then
prepare_build_gate
fi
if [[ "$PACKAGE" == true ]]; then
package_deb
fi
if [[ "$INSTALL" == true ]]; then
install_artifacts
fi
+54 -5
View File
@@ -290,11 +290,12 @@ fn parse_tc_filter_hits(raw: &str) -> BTreeMap<String, u64> {
current_ip = extract_tc_match_ip(t);
} else if t.starts_with("Sent ") {
if let Some(ip) = current_ip.clone() {
let hits = t
.split_whitespace()
.nth(4)
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0);
let hits = parse_sent_packets(t).unwrap_or(0);
out.insert(ip, hits);
}
} else if t.contains(" success ") || t.starts_with("success ") {
if let Some(ip) = current_ip.clone() {
let hits = parse_success_packets(t).unwrap_or(0);
out.insert(ip, hits);
}
}
@@ -302,6 +303,20 @@ fn parse_tc_filter_hits(raw: &str) -> BTreeMap<String, u64> {
out
}
fn parse_sent_packets(line: &str) -> Option<u64> {
let toks: Vec<&str> = line.split_whitespace().collect();
if toks.len() >= 5 && toks[0] == "Sent" && toks[2] == "bytes" && toks[4] == "pkt" {
return toks[3].parse::<u64>().ok();
}
None
}
fn parse_success_packets(line: &str) -> Option<u64> {
let toks: Vec<&str> = line.split_whitespace().collect();
let pos = toks.iter().position(|v| *v == "success")?;
toks.get(pos + 1)?.parse::<u64>().ok()
}
fn extract_tc_match_ip(line: &str) -> Option<String> {
let toks: Vec<&str> = line.split_whitespace().collect();
let pos = toks.iter().position(|v| *v == "match")?;
@@ -316,3 +331,37 @@ fn extract_tc_match_ip(line: &str) -> Option<String> {
let b4 = u8::from_str_radix(&hex[6..8], 16).ok()?;
Some(format!("{b1}.{b2}.{b3}.{b4}"))
}
#[cfg(test)]
mod tests {
use super::{parse_sent_packets, parse_success_packets, parse_tc_filter_hits};
#[test]
fn parse_sent_pkt_counter() {
let line = "Sent 12345 bytes 67 pkt (dropped 0, overlimits 0 requeues 0)";
assert_eq!(parse_sent_packets(line), Some(67));
}
#[test]
fn parse_success_counter() {
let line = "match 0a080102/ffffffff at 16 success 42";
assert_eq!(parse_success_packets(line), Some(42));
}
#[test]
fn parse_tc_filter_hits_supports_sent_and_success() {
let raw = r#"
filter parent 1: protocol ip pref 1 u32 chain 0
filter parent 1: protocol ip pref 1 u32 chain 0 fh 800: ht divisor 1
filter parent 1: protocol ip pref 1 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:10 not_in_hw
match 0a080102/ffffffff at 16
Sent 100 bytes 7 pkt (dropped 0, overlimits 0 requeues 0)
filter parent 1: protocol ip pref 1 u32 chain 0 fh 800::801 order 2049 key ht 800 bkt 0 flowid 1:11 not_in_hw
match 0a080103/ffffffff at 16 success 9
"#;
let map = parse_tc_filter_hits(raw);
assert_eq!(map.get("10.8.1.2"), Some(&7));
assert_eq!(map.get("10.8.1.3"), Some(&9));
}
}