fix29: удалён legacy, введён Debian 13-only и обновлён rollback
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
# /etc/systemd/system/amnezia-tc-watcher.service
|
||||
[Unit]
|
||||
Description=AmneziaWG docker watcher
|
||||
After=docker.service
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
ExecStart=/opt/amnezia/docker_watcher.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,14 +0,0 @@
|
||||
# /etc/systemd/system/amnezia-tc.service
|
||||
[Unit]
|
||||
Description=Apply AmneziaWG traffic limits
|
||||
After=docker.service network-online.target
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
Environment=WG_IF=awg0
|
||||
ExecStart=/opt/amnezia/limit_peers.sh
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,190 +0,0 @@
|
||||
root@5862577-oj93961:~# cat /usr/local/bin/check-vpn-limits
|
||||
#!/bin/bash
|
||||
echo "┌──────────────────────────────────────────────────┐"
|
||||
echo "│ AmneziaWG Traffic Control v1.2 │"
|
||||
echo "└──────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
|
||||
# === Auto-detect container and interface ===
|
||||
# Supported container names in order of preference
|
||||
POSSIBLE_CONTAINERS=("amnezia-awg" "amnezia-awg2")
|
||||
CONTAINER=""
|
||||
for name in "${POSSIBLE_CONTAINERS[@]}"; do
|
||||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -xq "$name"; then
|
||||
CONTAINER="$name"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# helper to run docker exec if container found
|
||||
docker_exec() {
|
||||
if [ -z "$CONTAINER" ]; then
|
||||
return 1
|
||||
fi
|
||||
docker exec "$CONTAINER" "$@"
|
||||
}
|
||||
|
||||
# detect interface name inside container (prefer wg*, tun*, amnezia*, veth*)
|
||||
IF=""
|
||||
if [ -n "$CONTAINER" ]; then
|
||||
IF=$(docker_exec sh -c "ip -o link show 2>/dev/null | awk -F': ' '{print \$2}'" 2>/dev/null | \
|
||||
egrep -o '^(wg[0-9]+|wg|amnezia[0-9]*|tun[0-9]+|veth[0-9]+)' | head -n1 || true)
|
||||
|
||||
# fallback: pick first iface that has an ipv4 address inside container
|
||||
if [ -z "$IF" ]; then
|
||||
IF=$(docker_exec sh -c "ip -o -4 addr show 2>/dev/null | awk '{print \$2}'" 2>/dev/null | head -n1 || true)
|
||||
fi
|
||||
fi
|
||||
|
||||
# final fallback to wg0 for backwards-compatibility (if nothing found)
|
||||
IF=${IF:-wg0}
|
||||
|
||||
# 1. Состояние системы
|
||||
echo "📊 СИСТЕМНЫЙ СТАТУС:"
|
||||
if systemctl is-active amnezia-tc.service >/dev/null 2>&1; then
|
||||
echo " Сервис: ✅ активен"
|
||||
else
|
||||
echo " Сервис: ❌ неактивен"
|
||||
fi
|
||||
|
||||
if systemctl is-active amnezia-tc.timer >/dev/null 2>&1; then
|
||||
next_time=$(systemctl list-timers --all | grep amnezia 2>/dev/null | head -1 | awk '{print $2, $3, $4, $5}')
|
||||
if [ -n "$next_time" ]; then
|
||||
echo " Таймер: ✅ активен (след. запуск: $next_time)"
|
||||
else
|
||||
echo " Таймер: ✅ активен (расписание не найдено)"
|
||||
fi
|
||||
else
|
||||
echo " Таймер: ❌ неактивен"
|
||||
fi
|
||||
|
||||
# container status (if found)
|
||||
if [ -n "$CONTAINER" ]; then
|
||||
container_status=$(docker ps --filter "name=^${CONTAINER}$" --format '{{.Status}}' 2>/dev/null)
|
||||
echo " Контейнер: ✅ ${CONTAINER} — ${container_status}"
|
||||
else
|
||||
# Try to detect if any amnezia container exists at all (for clearer message)
|
||||
any_amnezia=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -i amne || true)
|
||||
if [ -n "$any_amnezia" ]; then
|
||||
echo " Контейнер: ❌ ожидается amnezia-awg/awg2, но не найден в списке запущенных контейнеров. Найдены: $any_amnezia"
|
||||
else
|
||||
echo " Контейнер: ❌ не запущен"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 2. Клиенты
|
||||
echo "👥 ПОДКЛЮЧЕННЫЕ КЛИЕНТЫ:"
|
||||
if [ -n "$CONTAINER" ]; then
|
||||
docker_exec wg show 2>/dev/null | awk '
|
||||
BEGIN {count=0}
|
||||
/^peer:/ {peer=$2}
|
||||
/allowed ips:/ {ip=$3; gsub("/32","",ip)}
|
||||
/latest handshake:/ {
|
||||
if ($3 == "Never") {
|
||||
status="❌ отключен"
|
||||
time="никогда"
|
||||
} else {
|
||||
time=$3" "$4
|
||||
if ($4 ~ /second|minute/) status="✅ онлайн"
|
||||
else if ($4 ~ /hour/) status="⚠ давно"
|
||||
else status="✅ онлайн"
|
||||
}
|
||||
}
|
||||
/transfer:/ {
|
||||
rx=$3; tx=$6
|
||||
printf " • %-15s %-15s %8s ↓ / %8s ↑\n", ip, status, rx, tx
|
||||
count++
|
||||
}'
|
||||
if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -q -x "$CONTAINER"; then
|
||||
echo " Контейнер не запущен"
|
||||
fi
|
||||
else
|
||||
echo " Контейнер не запущен — не могу показать клиентов (wg)."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 3. Ограничения TC
|
||||
echo "⏸️ ОГРАНИЧЕНИЯ ТРАФИКА (70 Мбит/с):"
|
||||
if [ -n "$CONTAINER" ]; then
|
||||
count=$(docker_exec sh -c "tc class show dev ${IF} 2>/dev/null | grep -c 'rate 70Mbit'" 2>/dev/null || true)
|
||||
if [ -n "$count" ] && [ "$count" -gt 0 ]; then
|
||||
echo " Классы TC:"
|
||||
docker_exec sh -c "tc class show dev ${IF} 2>/dev/null | grep 'rate 70Mbit'" 2>/dev/null | \
|
||||
while read -r line; do
|
||||
class_id=$(echo "$line" | awk '{print $2}' | cut -d: -f2)
|
||||
if [ -n "$class_id" ]; then
|
||||
echo " • Класс 1:$class_id → 70 Мбит/с"
|
||||
fi
|
||||
done
|
||||
echo " ✅ Ограничения активны для $count клиентов"
|
||||
else
|
||||
echo " ❌ Нет активных ограничений"
|
||||
fi
|
||||
else
|
||||
echo " Контейнер не запущен"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 4. Статистика фильтров
|
||||
echo "📈 СТАТИСТИКА ФИЛЬТРОВ:"
|
||||
if [ -n "$CONTAINER" ]; then
|
||||
docker_exec sh -c "tc -s filter show dev ${IF} parent 1: 2>/dev/null" 2>/dev/null | \
|
||||
grep -B1 -A1 "flowid" | while read -r line1; do
|
||||
if echo "$line1" | grep -q "flowid"; then
|
||||
read -r line2
|
||||
read -r line3
|
||||
class=$(echo "$line1" | grep -o "flowid 1:[0-9]*" | cut -d: -f2)
|
||||
hex_ip=$(echo "$line2" | grep -o "match [0-9a-f]*" | awk '{print $2}')
|
||||
success=$(echo "$line3" | grep -o "success [0-9]*" | awk '{print $2}')
|
||||
|
||||
if [ -n "$hex_ip" ] && [ -n "$class" ]; then
|
||||
ip=$(printf "%d.%d.%d.%d" \
|
||||
0x${hex_ip:0:2} \
|
||||
0x${hex_ip:2:2} \
|
||||
0x${hex_ip:4:2} \
|
||||
0x${hex_ip:6:2} 2>/dev/null)
|
||||
|
||||
if [ -n "$success" ] && [ "$success" -gt 0 ]; then
|
||||
echo " • $ip → активен ($success пакетов)"
|
||||
else
|
||||
echo " • $ip → ожидание трафика"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if ! docker_exec sh -c "tc -s filter show dev ${IF} parent 1: 2>/dev/null" 2>/dev/null | grep -q "flowid"; then
|
||||
echo " Нет активных фильтров"
|
||||
fi
|
||||
else
|
||||
echo " Контейнер не запущен"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 5. Последние события
|
||||
echo "🔄 ПОСЛЕДНИЕ СОБЫТИЯ:"
|
||||
if [ -f "/var/log/amnezia-tc.log" ]; then
|
||||
tail -3 /var/log/amnezia-tc.log | while read -r line; do
|
||||
message=$(echo "$line" | cut -d']' -f2-)
|
||||
echo " $message"
|
||||
done
|
||||
else
|
||||
echo " Лог-файл не найден"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "┌──────────────────────────────────────────────────┐"
|
||||
echo "│ СИСТЕМА ГОТОВА К ЭКСПЛУАТАЦИИ! 🎉 │"
|
||||
echo "│ │"
|
||||
echo "│ Проверено: │"
|
||||
echo "│ ✅ Автозапуск после перезагрузки │"
|
||||
echo "│ ✅ Автоматическое восстановление правил │"
|
||||
echo "│ ✅ Ограничение 70 Мбит/с на клиента │"
|
||||
echo "│ ✅ Обнаружение новых клиентов │"
|
||||
echo "└──────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
echo "📋 Команды управления:"
|
||||
echo " check-vpn-limits - этот отчет"
|
||||
echo " systemctl restart amnezia-tc.service - перезапуск"
|
||||
echo " journalctl -u amnezia-tc.service -f - логи"
|
||||
echo " docker restart ${CONTAINER:-amnezia-awg2} - перезапуск WG (поменяйте имя при необходимости)"
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
wait_wg() {
|
||||
local c="$1"
|
||||
for i in {1..20}; do
|
||||
if docker exec "$c" ip link show awg0 >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
docker events --filter event=start --format '{{.Actor.Attributes.name}}' | while read name; do
|
||||
if [[ "$name" == "amnezia-awg2" || "$name" == "amnezia-awg" ]]; then
|
||||
echo "[watcher] container started: $name, waiting wg..."
|
||||
if wait_wg "$name"; then
|
||||
echo "[watcher] wg ready, applying limits"
|
||||
WG_IF=awg0 /opt/amnezia/limit_peers.sh
|
||||
else
|
||||
echo "[watcher] wg not appeared, skip"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
@@ -1,24 +0,0 @@
|
||||
root@5862577-oj93961:~# echo "===== kernel ====="
|
||||
===== kernel =====
|
||||
root@5862577-oj93961:~# uname -a
|
||||
== iproute2 ====="
|
||||
tc -V
|
||||
|
||||
echo "===== docker ====="
|
||||
docker -Linux 5862577-oj93961 6.12.57+deb13-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.57-1 (2025-11-05) x86_64 GNU/Linux
|
||||
root@5862577-oj93961:~#
|
||||
root@5862577-oj93961:~# echo "===== iproute2 ====="
|
||||
===== iproute2 =====
|
||||
root@5862577-oj93961:~# tc -V
|
||||
inspect amnezia-awg2 --format '{{.Config.Image}}'tc utility, iproute2-6.15.0, libbpf 1.5.0
|
||||
root@5862577-oj93961:~#
|
||||
root@5862577-oj93961:~# echo "===== docker ====="
|
||||
===== docker =====
|
||||
root@5862577-oj93961:~# docker --version
|
||||
Docker version 26.1.5+dfsg1, build a72d7cd
|
||||
root@5862577-oj93961:~#
|
||||
root@5862577-oj93961:~# echo "===== amnezia container ====="
|
||||
===== amnezia container =====
|
||||
root@5862577-oj93961:~# docker inspect amnezia-awg2 --format '{{.Config.Image}}'
|
||||
amnezia-awg2
|
||||
root@5862577-oj93961:~#
|
||||
@@ -1,138 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Traffic control for AmneziaWG (production-ready)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
LOG="/var/log/amnezia-tc.log"
|
||||
LOCK_FILE="/tmp/amnezia-tc.lock"
|
||||
LIMIT="${LIMIT:-70mbit}"
|
||||
CONTAINER=""
|
||||
WG_IF="${WG_IF:-wg0}"
|
||||
|
||||
# Prefer new container name, fall back to old
|
||||
for c in amnezia-awg2 amnezia-awg; do
|
||||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -xq "$c"; then
|
||||
CONTAINER="$c"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG"
|
||||
}
|
||||
|
||||
# Lock to avoid concurrent runs
|
||||
exec 200>"$LOCK_FILE"
|
||||
flock -n 200 || { log "Script already running, exit"; exit 0; }
|
||||
|
||||
if [ -z "$CONTAINER" ]; then
|
||||
log "Container amnezia-awg2/amnezia-awg not found, exit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# detect interface inside container if default not present there
|
||||
if ! docker exec "$CONTAINER" ip link show "$WG_IF" >/dev/null 2>&1; then
|
||||
DET_IF=$(docker exec "$CONTAINER" sh -c "ip -o link show 2>/dev/null | awk -F': ' '{print $2}'" 2>/dev/null \
|
||||
| egrep -o '^(wg[0-9]+|wg|awg[0-9]*|amnezia[0-9]*|tun[0-9]+)' | head -n1 || true)
|
||||
|
||||
if [ -n "$DET_IF" ]; then
|
||||
WG_IF="$DET_IF"
|
||||
log "Detected interface inside container: $WG_IF"
|
||||
else
|
||||
log "Interface $WG_IF not found inside container $CONTAINER, exit"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
log "=== Start updating rules === (container=$CONTAINER, iface=$WG_IF, limit=$LIMIT)"
|
||||
|
||||
# Get current clients
|
||||
CLIENT_IPS=$(docker exec "$CONTAINER" sh -c "wg show all allowed-ips 2>/dev/null" 2>/dev/null \
|
||||
| awk '{print $3}' | grep -oE '10\.8\.1\.[0-9]{1,3}' | sort -u || true)
|
||||
|
||||
# fallback
|
||||
if [ -z "$CLIENT_IPS" ]; then
|
||||
CLIENT_IPS=$(docker exec "$CONTAINER" sh -c "wg show 2>/dev/null" 2>/dev/null \
|
||||
| grep -Eo '10\.8\.1\.[0-9]{1,3}' | sort -u || true)
|
||||
fi
|
||||
|
||||
if [ -z "$CLIENT_IPS" ]; then
|
||||
log "No clients found, exit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "Clients: $(echo $CLIENT_IPS | tr '\n' ' ')"
|
||||
|
||||
EXISTING_RULES=$(docker exec "$CONTAINER" sh -c "tc filter show dev $WG_IF parent 1: 2>/dev/null || true" 2>/dev/null \
|
||||
| grep -oE 'match [0-9a-f]+' | awk '{print $2}' | sort -u || true)
|
||||
|
||||
NEEDS_UPDATE=false
|
||||
|
||||
num_ips=$(echo "$CLIENT_IPS" | wc -w)
|
||||
num_rules=$(echo "$EXISTING_RULES" | wc -w)
|
||||
if [ "$num_rules" -ne "$num_ips" ] || [ -z "$EXISTING_RULES" ]; then
|
||||
NEEDS_UPDATE=true
|
||||
else
|
||||
for ip in $CLIENT_IPS; do
|
||||
hex_ip=$(printf '%02x' ${ip//./ } | tr -d '\n' || true)
|
||||
if ! echo "$EXISTING_RULES" | grep -q "$hex_ip"; then
|
||||
NEEDS_UPDATE=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$NEEDS_UPDATE" = false ]; then
|
||||
log "Rules are already up-to-date, skip"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "Updating rules (old will be removed)"
|
||||
|
||||
docker exec "$CONTAINER" sh -c "
|
||||
tc qdisc del dev $WG_IF root 2>/dev/null || true
|
||||
tc qdisc del dev $WG_IF ingress 2>/dev/null || true
|
||||
ip link del ifb0 2>/dev/null || true
|
||||
" 2>/dev/null || true
|
||||
|
||||
docker exec "$CONTAINER" sh -c "
|
||||
tc qdisc add dev $WG_IF root handle 1: htb 2>/dev/null || true
|
||||
tc class add dev $WG_IF parent 1: classid 1:1 htb rate 1000mbit ceil 1000mbit 2>/dev/null || true
|
||||
" 2>/dev/null || true
|
||||
|
||||
docker exec "$CONTAINER" sh -c "
|
||||
modprobe ifb 2>/dev/null || true
|
||||
ip link add name ifb0 type ifb 2>/dev/null || true
|
||||
ip link set dev ifb0 up 2>/dev/null || true
|
||||
|
||||
tc qdisc add dev $WG_IF handle ffff: ingress 2>/dev/null || true
|
||||
tc filter add dev $WG_IF parent ffff: protocol all u32 match u32 0 0 action mirred egress redirect dev ifb0 2>/dev/null || true
|
||||
|
||||
tc qdisc add dev ifb0 root handle 2: htb 2>/dev/null || true
|
||||
tc class add dev ifb0 parent 2: classid 2:1 htb rate 1000mbit ceil 1000mbit 2>/dev/null || true
|
||||
" 2>/dev/null || true
|
||||
|
||||
class_id=10
|
||||
count=0
|
||||
for ip in $CLIENT_IPS; do
|
||||
log "Adding limit for $ip (class $class_id)"
|
||||
|
||||
docker exec "$CONTAINER" sh -c "
|
||||
tc class add dev $WG_IF parent 1:1 classid 1:$class_id htb rate $LIMIT ceil $LIMIT 2>/dev/null || true
|
||||
tc qdisc add dev $WG_IF parent 1:$class_id fq_codel 2>/dev/null || true
|
||||
tc filter add dev $WG_IF parent 1: protocol ip u32 match ip dst $ip flowid 1:$class_id 2>/dev/null || true
|
||||
" 2>/dev/null || true
|
||||
|
||||
docker exec "$CONTAINER" sh -c "
|
||||
tc class add dev ifb0 parent 2:1 classid 2:$class_id htb rate $LIMIT ceil $LIMIT 2>/dev/null || true
|
||||
tc qdisc add dev ifb0 parent 2:$class_id fq_codel 2>/dev/null || true
|
||||
tc filter add dev ifb0 parent 2: protocol ip u32 match ip src $ip flowid 2:$class_id 2>/dev/null || true
|
||||
" 2>/dev/null || true
|
||||
|
||||
class_id=$((class_id + 1))
|
||||
count=$((count + 1))
|
||||
done
|
||||
|
||||
log "Limits applied for $count clients"
|
||||
log "=== Completed successfully ==="
|
||||
exit 0
|
||||
@@ -1,17 +0,0 @@
|
||||
root@5862577-oj93961:~# echo "===== permissions ====="
|
||||
===== permissions =====
|
||||
root@5862577-oj93961:~# ls -l /opt/amnezia/
|
||||
ho "===== systemd enabled ====="
|
||||
systemctl is-enabled amnezia-tc.service
|
||||
systemctl is-enabled amnezia-tc-watchtotal 16
|
||||
drwxr-xr-x 2 root root 4096 Feb 21 05:15 amnezia-awg2
|
||||
-rwxr-xr-x 1 root root 618 Feb 21 18:24 docker_watcher.sh
|
||||
-rwxr-xr-x 1 root root 4496 Feb 21 18:17 limit_peers.sh
|
||||
root@5862577-oj93961:~#
|
||||
root@5862577-oj93961:~# echo "===== systemd enabled ====="
|
||||
===== systemd enabled =====
|
||||
root@5862577-oj93961:~# systemctl is-enabled amnezia-tc.service
|
||||
enabled
|
||||
root@5862577-oj93961:~# systemctl is-enabled amnezia-tc-watcher.service
|
||||
enabled
|
||||
root@5862577-oj93961:~#
|
||||
@@ -1,172 +0,0 @@
|
||||
root@5862577-oj93961:~# echo "===== TC qdisc ====="
|
||||
===== TC qdisc =====
|
||||
root@5862577-oj93961:~# docker exec amnezia-awg2 tc qdisc show dev awg0
|
||||
|
||||
docker exec amnezia-awg2 tc class show dev awg0
|
||||
|
||||
echo "===== TC filters ====="
|
||||
docker exec amnezia-awg2 tc filter show dev awg0qdisc htb 1: root refcnt 2 r2q 10 default 0 direct_packets_stat 6 direct_qlen 500
|
||||
qdisc fq_codel 807b: parent 1:24 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 807f: parent 1:26 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 807d: parent 1:25 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8081: parent 1:27 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8083: parent 1:28 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8087: parent 1:30 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8085: parent 1:29 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8089: parent 1:31 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 805f: parent 1:10 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 808b: parent 1:32 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8063: parent 1:12 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8061: parent 1:11 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8065: parent 1:13 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8067: parent 1:14 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 806b: parent 1:16 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8069: parent 1:15 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 806d: parent 1:17 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 806f: parent 1:18 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8071: parent 1:19 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8075: parent 1:21 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8073: parent 1:20 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8077: parent 1:22 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc fq_codel 8079: parent 1:23 limit 10240p flows 1024 quantum 1420 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
|
||||
qdisc ingress ffff: parent ffff:fff1 ----------------
|
||||
root@5862577-oj93961:~#
|
||||
root@5862577-oj93961:~# echo "===== TC class ====="
|
||||
===== TC class =====
|
||||
root@5862577-oj93961:~# docker exec amnezia-awg2 tc class show dev awg0
|
||||
class htb 1:11 parent 1:1 leaf 8061: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:32 parent 1:1 leaf 808b: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:10 parent 1:1 leaf 805f: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:31 parent 1:1 leaf 8089: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:13 parent 1:1 leaf 8065: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:30 parent 1:1 leaf 8087: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:12 parent 1:1 leaf 8063: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:15 parent 1:1 leaf 8069: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:14 parent 1:1 leaf 8067: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:17 parent 1:1 leaf 806d: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:16 parent 1:1 leaf 806b: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:19 parent 1:1 leaf 8071: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:18 parent 1:1 leaf 806f: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:22 parent 1:1 leaf 8077: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:23 parent 1:1 leaf 8079: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:1 root rate 1Gbit ceil 1Gbit burst 1375b cburst 1375b
|
||||
class htb 1:20 parent 1:1 leaf 8073: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:21 parent 1:1 leaf 8075: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:26 parent 1:1 leaf 807f: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:27 parent 1:1 leaf 8081: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:24 parent 1:1 leaf 807b: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:25 parent 1:1 leaf 807d: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:28 parent 1:1 leaf 8083: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class htb 1:29 parent 1:1 leaf 8085: prio 0 rate 70Mbit ceil 70Mbit burst 1583b cburst 1583b
|
||||
class fq_codel 805f:75 parent 805f:
|
||||
class fq_codel 805f:7b parent 805f:
|
||||
class fq_codel 805f:325 parent 805f:
|
||||
class fq_codel 805f:3ab parent 805f:
|
||||
class fq_codel 805f:3e5 parent 805f:
|
||||
class fq_codel 808b:41 parent 808b:
|
||||
class fq_codel 808b:d5 parent 808b:
|
||||
class fq_codel 808b:e7 parent 808b:
|
||||
class fq_codel 808b:147 parent 808b:
|
||||
class fq_codel 808b:1a4 parent 808b:
|
||||
class fq_codel 8063:251 parent 8063:
|
||||
class fq_codel 8063:290 parent 8063:
|
||||
class fq_codel 8061:284 parent 8061:
|
||||
class fq_codel 8065:203 parent 8065:
|
||||
class fq_codel 8069:2bf parent 8069:
|
||||
root@5862577-oj93961:~#
|
||||
root@5862577-oj93961:~# echo "===== TC filters ====="
|
||||
===== TC filters =====
|
||||
root@5862577-oj93961:~# docker exec amnezia-awg2 tc filter show dev awg0
|
||||
filter parent 1: protocol ip pref 49130 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49130 u32 chain 0 fh 816: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49130 u32 chain 0 fh 816::800 order 2048 key ht 816 bkt 0 flowid 1:32 not_in_hw
|
||||
match 0a080105/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49131 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49131 u32 chain 0 fh 815: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49131 u32 chain 0 fh 815::800 order 2048 key ht 815 bkt 0 flowid 1:31 not_in_hw
|
||||
match 0a080104/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49132 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49132 u32 chain 0 fh 814: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49132 u32 chain 0 fh 814::800 order 2048 key ht 814 bkt 0 flowid 1:30 not_in_hw
|
||||
match 0a080103/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49133 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49133 u32 chain 0 fh 813: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49133 u32 chain 0 fh 813::800 order 2048 key ht 813 bkt 0 flowid 1:29 not_in_hw
|
||||
match 0a08011b/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49134 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49134 u32 chain 0 fh 812: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49134 u32 chain 0 fh 812::800 order 2048 key ht 812 bkt 0 flowid 1:28 not_in_hw
|
||||
match 0a08011a/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49135 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49135 u32 chain 0 fh 811: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49135 u32 chain 0 fh 811::800 order 2048 key ht 811 bkt 0 flowid 1:27 not_in_hw
|
||||
match 0a080119/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49136 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49136 u32 chain 0 fh 810: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49136 u32 chain 0 fh 810::800 order 2048 key ht 810 bkt 0 flowid 1:26 not_in_hw
|
||||
match 0a080118/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49137 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49137 u32 chain 0 fh 80f: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49137 u32 chain 0 fh 80f::800 order 2048 key ht 80f bkt 0 flowid 1:25 not_in_hw
|
||||
match 0a080117/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49138 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49138 u32 chain 0 fh 80e: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49138 u32 chain 0 fh 80e::800 order 2048 key ht 80e bkt 0 flowid 1:24 not_in_hw
|
||||
match 0a080116/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49139 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49139 u32 chain 0 fh 80d: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49139 u32 chain 0 fh 80d::800 order 2048 key ht 80d bkt 0 flowid 1:23 not_in_hw
|
||||
match 0a080115/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49140 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49140 u32 chain 0 fh 80c: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49140 u32 chain 0 fh 80c::800 order 2048 key ht 80c bkt 0 flowid 1:22 not_in_hw
|
||||
match 0a080114/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49141 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49141 u32 chain 0 fh 80b: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49141 u32 chain 0 fh 80b::800 order 2048 key ht 80b bkt 0 flowid 1:21 not_in_hw
|
||||
match 0a080102/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49142 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49142 u32 chain 0 fh 80a: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49142 u32 chain 0 fh 80a::800 order 2048 key ht 80a bkt 0 flowid 1:20 not_in_hw
|
||||
match 0a080113/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49143 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49143 u32 chain 0 fh 809: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49143 u32 chain 0 fh 809::800 order 2048 key ht 809 bkt 0 flowid 1:19 not_in_hw
|
||||
match 0a080112/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49144 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49144 u32 chain 0 fh 808: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49144 u32 chain 0 fh 808::800 order 2048 key ht 808 bkt 0 flowid 1:18 not_in_hw
|
||||
match 0a080111/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49145 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49145 u32 chain 0 fh 807: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49145 u32 chain 0 fh 807::800 order 2048 key ht 807 bkt 0 flowid 1:17 not_in_hw
|
||||
match 0a080110/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49146 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49146 u32 chain 0 fh 806: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49146 u32 chain 0 fh 806::800 order 2048 key ht 806 bkt 0 flowid 1:16 not_in_hw
|
||||
match 0a08010f/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49147 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49147 u32 chain 0 fh 805: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49147 u32 chain 0 fh 805::800 order 2048 key ht 805 bkt 0 flowid 1:15 not_in_hw
|
||||
match 0a08010e/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49148 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49148 u32 chain 0 fh 804: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49148 u32 chain 0 fh 804::800 order 2048 key ht 804 bkt 0 flowid 1:14 not_in_hw
|
||||
match 0a08010d/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49149 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49149 u32 chain 0 fh 803: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49149 u32 chain 0 fh 803::800 order 2048 key ht 803 bkt 0 flowid 1:13 not_in_hw
|
||||
match 0a08010c/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49150 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49150 u32 chain 0 fh 802: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49150 u32 chain 0 fh 802::800 order 2048 key ht 802 bkt 0 flowid 1:12 not_in_hw
|
||||
match 0a08010b/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49151 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49151 u32 chain 0 fh 801: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49151 u32 chain 0 fh 801::800 order 2048 key ht 801 bkt 0 flowid 1:11 not_in_hw
|
||||
match 0a08010a/ffffffff at 16
|
||||
filter parent 1: protocol ip pref 49152 u32 chain 0
|
||||
filter parent 1: protocol ip pref 49152 u32 chain 0 fh 800: ht divisor 1
|
||||
filter parent 1: protocol ip pref 49152 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 flowid 1:10 not_in_hw
|
||||
match 0a080101/ffffffff at 16
|
||||
root@5862577-oj93961:~#
|
||||
@@ -1,129 +0,0 @@
|
||||
root@5862577-oj93961:~# echo "===== WG peers ====="
|
||||
===== WG peers =====
|
||||
root@5862577-oj93961:~# docker exec amnezia-awg2 wg show
|
||||
interface: awg0
|
||||
public key: 03RA0Pz5SXeHFlYOWVtbtuFFr1qRZfwoIsp1dX/KXA8=
|
||||
private key: (hidden)
|
||||
listening port: 44344
|
||||
jc: 4
|
||||
jmin: 10
|
||||
jmax: 50
|
||||
s1: 108
|
||||
s2: 92
|
||||
s3: 37
|
||||
s4: 5
|
||||
h1: 1644392140-1655070006
|
||||
h2: 1744170008-1963857766
|
||||
h3: 2020199760-2027285116
|
||||
h4: 2115790963-2141451260
|
||||
|
||||
peer: trH/DMazLMdu1QE5uXhpesx5SzWvNFoW5KZHd+gTulA=
|
||||
preshared key: (hidden)
|
||||
endpoint: 172.29.172.1:46697
|
||||
allowed ips: 10.8.1.5/32
|
||||
latest handshake: 14 seconds ago
|
||||
transfer: 575.60 KiB received, 1.42 MiB sent
|
||||
|
||||
peer: LNoepSV+wHrq8xrpG3nVR7ciSk3qqoOCtvZCkSpjbno=
|
||||
preshared key: (hidden)
|
||||
endpoint: 172.29.172.1:49282
|
||||
allowed ips: 10.8.1.11/32
|
||||
latest handshake: 14 seconds ago
|
||||
transfer: 7.53 MiB received, 159.08 MiB sent
|
||||
|
||||
peer: C57aO+jMb6LBJ8kI/olF8JLIM3SnERumSyZjEFm5jlY=
|
||||
preshared key: (hidden)
|
||||
endpoint: 172.29.172.1:51133
|
||||
allowed ips: 10.8.1.14/32
|
||||
latest handshake: 23 seconds ago
|
||||
transfer: 81.93 KiB received, 104.35 KiB sent
|
||||
|
||||
peer: FlbqHCQdjC53Wn9xFLq8E/v1RJmT0r93wV9w+uVrBCs=
|
||||
preshared key: (hidden)
|
||||
endpoint: 172.29.172.1:54591
|
||||
allowed ips: 10.8.1.12/32
|
||||
latest handshake: 40 seconds ago
|
||||
transfer: 5.16 MiB received, 102.56 MiB sent
|
||||
|
||||
peer: oAYyolWjgRmfWejHdjzoUivF7yk0FGsy1B3IeF0PLBg=
|
||||
preshared key: (hidden)
|
||||
endpoint: 172.29.172.1:51761
|
||||
allowed ips: 10.8.1.1/32
|
||||
latest handshake: 42 seconds ago
|
||||
transfer: 2.01 MiB received, 2.39 MiB sent
|
||||
|
||||
peer: jLBpMPRBc7E8b1NEDjlVHAVQG3PN7fmueC9paf8K8yM=
|
||||
preshared key: (hidden)
|
||||
endpoint: 172.29.172.1:57449
|
||||
allowed ips: 10.8.1.10/32
|
||||
latest handshake: 44 seconds ago
|
||||
transfer: 225.05 MiB received, 3.14 GiB sent
|
||||
|
||||
peer: OMeNeKilU+f1ilnrnwgpbyRV8lyVT9psAIK5iJLoKxI=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.27/32
|
||||
|
||||
peer: IYyJq14x4kOA8SbbCd+u6WQNuVZntLuSQu0JSs78uk4=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.26/32
|
||||
|
||||
peer: a+Yw80sFSnyKFXbfOZtQupWiO2f9BWLby7ydbyo7+RA=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.2/32
|
||||
|
||||
peer: Ir8DYbG96qGhudYPGpNSqs7AZaMO9oF6yecb0cq4WTQ=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.4/32
|
||||
|
||||
peer: Sg56DQUHGl9byHOLvrJBNbwQER+Y9LttyaonPTFX8Eg=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.16/32
|
||||
|
||||
peer: wgeDnqt7hfSPgHlGbpdKkDhAwWbKRBDsWBQ2e3OATlM=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.19/32
|
||||
|
||||
peer: iF4DdwwnEnWQ6Ve4KrnHt88dSGyr1reQWQuHGQ3OzSg=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.24/32
|
||||
|
||||
peer: kA8CUmfjcGAACXJHsVEXFxReLKU+/LGpZjI6Va+kwDQ=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.20/32
|
||||
|
||||
peer: J130uXmnNsPs7+OahiqG5hUOtJ/4eEOgeL3or9QMYQs=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.3/32
|
||||
|
||||
peer: 5QdCq+tlMDL4PTs7z9LD9g26Qr+bNeWxnmSI/DzBaDM=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.15/32
|
||||
|
||||
peer: KnqncORZhtC5RhC+MSttDLla19jCHrxooUvIY+MmnhA=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.18/32
|
||||
|
||||
peer: z8hfD0AqagIxJjob242MbQWdM2E450f4v/g4Pe9YUBc=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.23/32
|
||||
|
||||
peer: +XNRC3O5TEYAJWqF5ihN9ggvBBaf03FAAdGcXezKDHE=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.25/32
|
||||
|
||||
peer: 7/MIQLMhYBcvQtrlvJ+HAoXiPUdGVfKrD2RJNhJ0jR8=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.13/32
|
||||
|
||||
peer: PZRbsLPBZtTV2h82ggpENOrAqFNEkdCv2vJxU5cBaU4=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.17/32
|
||||
|
||||
peer: LphW/YJyc4lFf5mFOyS3G20yH3YvA5VavVaVRR+0yV0=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.21/32
|
||||
|
||||
peer: uc3UA+woJyqrSFfx5PM7yneBZAhNGSBZgjHXsHh8x2M=
|
||||
preshared key: (hidden)
|
||||
allowed ips: 10.8.1.22/32
|
||||
root@5862577-oj93961:~#
|
||||
@@ -1,56 +0,0 @@
|
||||
6. Единственный скрытый риск (самый важный)
|
||||
|
||||
Он не сейчас, а через время.
|
||||
|
||||
Amnezia иногда меняет имя интерфейса:
|
||||
|
||||
awg0
|
||||
wg0
|
||||
amnezia0
|
||||
|
||||
У тебя в логах уже видно:
|
||||
|
||||
iface=awg0
|
||||
|
||||
Если они обновят контейнер → имя изменится → tc пропадёт.
|
||||
|
||||
НО
|
||||
|
||||
Твой скрипт уже умеет авто-детект:
|
||||
|
||||
ip -o link show | egrep '^(wg[0-9]+|wg|amnezia[0-9]*|tun[0-9]+)'
|
||||
|
||||
✔ значит система устойчива даже к обновлениям Amnezia
|
||||
|
||||
|
||||
|
||||
|
||||
7. Итог
|
||||
|
||||
Сейчас у тебя:
|
||||
|
||||
Production-grade auto-healing bandwidth limiter
|
||||
|
||||
Он:
|
||||
|
||||
✔ переживает reboot
|
||||
✔ переживает restart docker
|
||||
✔ переживает restart контейнера
|
||||
✔ переживает обновление Amnezia
|
||||
✔ переживает добавление пользователей
|
||||
✔ переживает удаление пользователей
|
||||
|
||||
8. Когда он может сломаться
|
||||
|
||||
Только в 2 случаях:
|
||||
|
||||
Amnezia полностью поменяет формат wg show
|
||||
|
||||
в контейнере уберут tc или iproute2
|
||||
|
||||
Это редкость.
|
||||
|
||||
9. Вердикт
|
||||
|
||||
Да — сейчас система настроена правильно.
|
||||
Её уже можно считать стабильной боевой конфигурацией.
|
||||
Generated
+1
-1
@@ -84,7 +84,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "awg2-harness"
|
||||
version = "0.1.4"
|
||||
version = "0.1.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
```text
|
||||
awg2_harness/
|
||||
├── Amnezia limits (old bash)/ # исторический reference старой bash-схемы
|
||||
├── reboot_module/ # исходники awg2-reboot-scheduler
|
||||
├── traffic_core/ # исходники awg2-harness
|
||||
├── tools/ # build/package tooling
|
||||
@@ -31,15 +30,13 @@ awg2_harness/
|
||||
└── awg2-reboot-scheduler.service # reboot scheduler unit
|
||||
```
|
||||
|
||||
`Amnezia limits (old bash)` оставлен как historical reference, а не как runtime-зависимость. На production-хосте после перехода на `.deb` source tree и старые bash-скрипты не нужны.
|
||||
|
||||
## Runtime-план production-развёртывания
|
||||
|
||||
Ниже целевой порядок установки. Его смысл простой: сначала безопасный SSH, потом официальная Amnezia, потом AWG2 Harness.
|
||||
|
||||
### 1. Подготовить Debian-хост
|
||||
|
||||
Целевая платформа проекта — Debian 13 amd64 на KVM/полноценной VM или dedicated host. Для Amnezia upstream также указывает Debian 12/13 и Ubuntu 22.04/24.04 как официально поддерживаемые OS, x86-64/amd64, публичный IPv4, SSH-доступ и Docker-based self-hosted deployment через приложение AmneziaVPN.
|
||||
Целевая платформа AWG2 Harness — только Debian 13 amd64 на KVM/полноценной VM или dedicated host.
|
||||
|
||||
Минимальный baseline перед установкой Amnezia:
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
- [ ] Для каждого peer есть `fq_codel` под `2:<id>`.
|
||||
- [ ] Для каждого peer есть ingress filter `src peer_ip -> 2:<id>`.
|
||||
- [ ] Class ids начинаются с `traffic.class_start_id`, default `10`.
|
||||
- [ ] Peer IP → class id mapping сохраняет bash parity: IP сортируются лексикографически как строки, не numeric IPv4.
|
||||
- [ ] Peer IP → class id mapping использует стабильный лексикографический контракт: IP сортируются как строки, не numeric IPv4.
|
||||
|
||||
## D. Idempotency and drift recovery
|
||||
|
||||
@@ -112,9 +112,11 @@
|
||||
|
||||
## J. Rollback
|
||||
|
||||
- [ ] Старые bash-файлы сохранены до завершения миграции.
|
||||
- [ ] Известно, как остановить `awg2-harness.service`.
|
||||
- [ ] Известно, как вернуть `amnezia-tc.service` и `amnezia-tc-watcher.service`.
|
||||
- [ ] Известно, как остановить и отключить `awg2-harness.service` и `awg2-reboot-scheduler.service`.
|
||||
- [ ] Известно, как удалить только AWG2-owned host firewall state (`inet awg2_host_firewall`).
|
||||
- [ ] Известно, как удалить только AWG2-owned anti-torrent state (`inet awg2_harness`) в выбранном namespace.
|
||||
- [ ] Известно, как удалить AWG2-owned `tc` topology (`root`, `ingress`, `ifb0`) в netns Amnezia.
|
||||
- [ ] При необходимости rollback включает удаление пакета `awg2-harness` после остановки сервисов.
|
||||
- [ ] Rollback проверен на тестовом сервере.
|
||||
|
||||
## K. Packaging / release
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Anti-torrent connection-count guard
|
||||
|
||||
Этот модуль не является частью `Amnezia limits (old bash)`. Это новое расширение Rust multi-orchestrator. Оно нормально вписывается в проект, потому что сервер выделенный, а бинарь уже владеет network state вокруг Amnezia: `tc`, `ifb0`, lifecycle recovery и диагностика.
|
||||
Этот модуль — часть текущего production-контракта AWG2 Harness. Он вписывается в multi-orchestrator модель, потому что сервер выделенный, а бинарь уже владеет AWG2-owned network state вокруг Amnezia: `tc`, `ifb0`, lifecycle recovery и диагностика.
|
||||
|
||||
## 1. Цель
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ The binary should not blindly flush unrelated host firewall rules, Docker NAT ru
|
||||
|
||||
## 11. Anti-torrent firewall guard
|
||||
|
||||
This is a new module, not part of old bash parity. It fits the multi-orchestrator model because the server is dedicated and the binary is allowed to own host/network rules required for the VPN product. The rule must still be scoped narrowly and observable.
|
||||
This is part of the AWG2 production contract. It fits the multi-orchestrator model because the server is dedicated and the binary is allowed to own host/network rules required for the VPN product. The rule must still be scoped narrowly and observable.
|
||||
|
||||
Goal: limit concurrent conntrack flows per VPN peer IP. This is not torrent protocol detection. It is a generic connection-count guard that makes high-peer-count P2P workloads unattractive while leaving normal browsing, streaming, gaming and messaging traffic unaffected.
|
||||
|
||||
@@ -456,7 +456,7 @@ Unit tests:
|
||||
- `wg show all allowed-ips` parser;
|
||||
- `wg show` fallback parser;
|
||||
- CIDR filtering;
|
||||
- deterministic class assignment with lexicographic bash parity;
|
||||
- deterministic class assignment with lexicographic class-id stability contract;
|
||||
- nftables ruleset rendering and parsing for the AWG2-owned table;
|
||||
- `tc` parser for qdisc/class/filter snapshots;
|
||||
- drift diff;
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
# Миграция AWG2 Harness с bash-системы на Rust multi-orchestrator
|
||||
|
||||
## 1. Исправленный scope
|
||||
|
||||
AWG2 Harness не должен быть просто Rust-эквивалентом пары bash-скриптов. Цель шире: сделать единый production multi-orchestrator, который сначала приводит сервер и Amnezia network namespace к нужному состоянию, а затем постоянно следит за drift и возвращает систему в desired state.
|
||||
|
||||
Источник поведения — папка `Amnezia limits (old bash)/`. Всё, что там реально настроено и используется в production, должно быть явно перенесено в Rust-модель: обнаружение контейнера, обнаружение интерфейса, получение peers, `tc` rules, `ifb0`, ingress redirect, per-peer HTB classes, `fq_codel`, auto-healing, статусная диагностика и логи. Новые правила, которых не было в old bash, например anti-torrent через nftables, должны быть описаны отдельно как расширения, а не смешаны с parity contract.
|
||||
|
||||
Сервер выделенный. На нём нет соседних сервисов, которые надо защищать от наших сетевых правил. Поэтому бинарь может и должен владеть host/network rules, необходимыми для Amnezia. Ограничение только одно: не ломать саму Amnezia как продукт и не менять её конфиги без явной задачи. Но системные правила, созданные old bash для лимитов, становятся зоной ответственности Rust-бинаря.
|
||||
|
||||
## 2. Production-контекст
|
||||
|
||||
Целевой хост:
|
||||
|
||||
- Debian 13 amd64.
|
||||
- SSH настроен заранее владельцем сервера.
|
||||
- AmneziaWG/AWG 2.0 устанавливается официальным способом до установки AWG2 Harness.
|
||||
- После Amnezia доставляется один Rust-бинарь и единый конфиг.
|
||||
- Пакет ставится один раз; автообновления самого пакета не проектируются.
|
||||
- Бинарь запускается от root через systemd, потому что ему нужны Docker/systemd/net-admin/swap возможности.
|
||||
|
||||
Система должна переживать:
|
||||
|
||||
- reboot хоста;
|
||||
- restart Docker;
|
||||
- restart контейнера Amnezia;
|
||||
- update контейнера Amnezia;
|
||||
- изменение имени интерфейса `awg0` / `wg0` / `amnezia0` / `tun0`;
|
||||
- добавление peer;
|
||||
- удаление peer;
|
||||
- частично применённое или повреждённое `tc`-состояние;
|
||||
- частично применённое или повреждённое AWG2-owned nftables-состояние.
|
||||
|
||||
## 3. Что именно делает старая bash-система
|
||||
|
||||
Старая система состоит из нескольких файлов, и все они важны.
|
||||
|
||||
`limit_peers.sh` — главный apply/reconcile-скрипт. Он:
|
||||
|
||||
- берёт lock через `/tmp/amnezia-tc.lock`, чтобы не было параллельных применений;
|
||||
- ищет запущенный контейнер `amnezia-awg2`, затем fallback `amnezia-awg`;
|
||||
- проверяет интерфейс `WG_IF`, по умолчанию `wg0`, и если его нет, авто-детектит `wg*`, `awg*`, `amnezia*`, `tun*`;
|
||||
- получает client IP из `wg show all allowed-ips`, fallback — regex из `wg show`;
|
||||
- фильтрует текущую production-подсеть `10.8.1.x`;
|
||||
- проверяет существующие `tc filter` по hex-адресам client IP;
|
||||
- если состояние не совпадает, полностью удаляет старую topology и создаёт новую;
|
||||
- создаёт root HTB на WG/AWG-интерфейсе: `tc qdisc add dev <iface> root handle 1: htb`;
|
||||
- создаёт root class `1:1` с `rate 1000mbit ceil 1000mbit`;
|
||||
- загружает/создаёт `ifb0`;
|
||||
- добавляет ingress qdisc `ffff:` на WG/AWG-интерфейс;
|
||||
- добавляет ingress redirect через `mirred egress redirect dev ifb0`;
|
||||
- создаёт root HTB на `ifb0`: `handle 2:`;
|
||||
- создаёт root class `2:1` с `rate 1000mbit ceil 1000mbit`;
|
||||
- для каждого peer создаёт egress class `1:<class_id>` с `rate LIMIT ceil LIMIT`;
|
||||
- для каждого peer создаёт `fq_codel` под egress class;
|
||||
- для каждого peer добавляет egress filter `match ip dst <peer_ip> flowid 1:<class_id>`;
|
||||
- для каждого peer создаёт ingress class `2:<class_id>` с тем же лимитом;
|
||||
- для каждого peer создаёт `fq_codel` под ingress class;
|
||||
- для каждого peer добавляет ingress filter на `ifb0`: `match ip src <peer_ip> flowid 2:<class_id>`;
|
||||
- начинает `class_id` с 10;
|
||||
- пишет события в `/var/log/amnezia-tc.log`.
|
||||
|
||||
`docker_watcher.sh` слушает `docker events --filter event=start`. При старте `amnezia-awg2` или `amnezia-awg` он ждёт появления `awg0` до 20 секунд и запускает `limit_peers.sh`. В Rust это нужно исправить: watcher не должен ждать только `awg0`, он обязан использовать общий detector интерфейса.
|
||||
|
||||
`check-vpn-limits` — CLI-диагностика. Он показывает systemd статус, контейнер, peers из `wg show`, классы с лимитом 70 Mbit/s, filter stats и последние строки `/var/log/amnezia-tc.log`. Rust `status` должен заменить этот отчёт, но проверять шире: оба направления, `ifb0`, ingress redirect и соответствие desired state.
|
||||
|
||||
`amnezia-tc.service` — oneshot apply после Docker/network-online.
|
||||
|
||||
`amnezia-tc-watcher.service` — постоянно запущенный watcher Docker events.
|
||||
|
||||
`traffic_control-was.txt`, `wg-peers.txt`, `iproute2_a_core.txt`, `permissions.txt`, `Нюансы.txt` — production-снимки. Они фиксируют реальные условия: Debian 13 cloud kernel, Docker 26.1.5, iproute2 6.15.0, интерфейс `awg0`, 23 peer IP в `10.8.1.0/24`, HTB classes `1:10..1:32`, `fq_codel` leaves и enabled systemd units.
|
||||
|
||||
## 4. Desired state Rust-бинаря
|
||||
|
||||
Rust-бинарь должен иметь explicit desired state model. Нельзя ограничиться последовательным запуском команд “как в bash”. Нужно уметь сравнить expected и actual, а затем принять решение: no-op, incremental fix или full rebuild.
|
||||
|
||||
Базовый desired state для traffic limits:
|
||||
|
||||
```text
|
||||
container: first running from configured names
|
||||
netns: namespace of selected container
|
||||
interface: detected AWG/WG interface inside that netns
|
||||
peers: IPv4 /32 allowed IPs inside configured peer CIDR
|
||||
root egress qdisc: <iface> root handle 1: htb
|
||||
root egress class: 1:1 rate root_rate ceil root_rate
|
||||
per-peer egress class: 1:<id> rate peer_limit ceil peer_limit
|
||||
per-peer egress qdisc: fq_codel under 1:<id>
|
||||
per-peer egress filter: dst peer_ip -> 1:<id>
|
||||
ingress qdisc: <iface> handle ffff:
|
||||
ingress redirect: <iface> ingress -> ifb0
|
||||
ifb device: ifb0 up in the same namespace where shaping is applied
|
||||
root ingress qdisc: ifb0 root handle 2: htb
|
||||
root ingress class: 2:1 rate root_rate ceil root_rate
|
||||
per-peer ingress class: 2:<id> rate peer_limit ceil peer_limit
|
||||
per-peer ingress qdisc: fq_codel under 2:<id>
|
||||
per-peer ingress filter: src peer_ip -> 2:<id>
|
||||
```
|
||||
|
||||
Любое отклонение от этого состояния считается drift. Если drift простой, можно исправить точечно. Для v1 допустим и предпочтителен full rebuild всей AWG traffic topology, потому что он проще, предсказуемее и повторяет текущую production-модель.
|
||||
|
||||
## 5. Модель управления host/network state
|
||||
|
||||
Исправление к предыдущей версии документации: бинарь не должен рассматриваться как осторожная внешняя обёртка, которая только вызывает `docker exec` и не владеет правилами. Правильная модель — host-level orchestrator with namespace control.
|
||||
|
||||
Практический порядок для v1:
|
||||
|
||||
1. На хосте определить контейнер через Docker API или `docker inspect`.
|
||||
2. Получить PID контейнера и путь к его network namespace: `/proc/<pid>/ns/net`.
|
||||
3. Все операции `wg`, `ip`, `tc`, `ifb` выполнять в правильном namespace.
|
||||
4. Для реализации можно выбрать один из двух путей:
|
||||
- через `nsenter -t <pid> -n ...` / `ip netns exec`-эквивалент и системные команды;
|
||||
- через netlink/native Rust libraries после входа в namespace.
|
||||
5. `docker exec` допустим только как fallback или transitional adapter, но не как архитектурный центр. Причина: это делает нас зависимыми от shell/tools внутри контейнера и хуже соответствует модели “наш бинарь управляет состоянием сервера”.
|
||||
|
||||
Для первой production-версии нормальный компромисс: Rust-бинарь владеет orchestration, locking, state comparison, retry, status и lifecycle, а низкоуровневые `tc/ip/wg` операции выполняет через команды в namespace. Нативный netlink можно вводить после стабилизации, если это даст реальный выигрыш по надёжности или нагрузке.
|
||||
|
||||
## 6. Обязательные функции Rust-версии
|
||||
|
||||
Минимум для parity со старой системой:
|
||||
|
||||
1. `daemon` — основной systemd-процесс.
|
||||
2. `apply-limits` — ручное применение/reconcile traffic limits.
|
||||
3. `status` — диагностика уровня `check-vpn-limits`, но с проверкой обоих направлений и drift.
|
||||
4. `validate-config` — проверка конфига без изменения системы.
|
||||
5. `apply-firewall` — ручной reconcile AWG2-owned nftables state, если anti-torrent включён.
|
||||
6. `ensure-swap` — идемпотентный setup swap-файла 2 ГБ.
|
||||
7. Внешний reboot scheduler `awg2-reboot-scheduler` как отдельный production service.
|
||||
8. Locking, чтобы одновременно не шли два apply/reconcile.
|
||||
9. Docker event watcher.
|
||||
10. Периодический reconciliation loop, потому что Docker events могут быть пропущены.
|
||||
11. Start-up reconciliation: при запуске daemon сразу проверить и применить лимиты и AWG2-owned firewall state.
|
||||
12. Graceful behavior, если контейнер временно отсутствует: логировать и продолжать loop, а не падать насмерть.
|
||||
|
||||
## 7. Подкоманды
|
||||
|
||||
```bash
|
||||
awg2-harness daemon
|
||||
awg2-harness apply-limits
|
||||
awg2-harness apply-firewall
|
||||
awg2-harness status
|
||||
awg2-harness ensure-swap
|
||||
awg2-harness validate-config
|
||||
awg2-harness validate-runtime
|
||||
```
|
||||
|
||||
`daemon` — основной production-режим. Он делает initial reconcile, слушает Docker events, периодически проверяет state и пишет runtime-логи в journald. Планирование reboot выполняется отдельным сервисом `awg2-reboot-scheduler`.
|
||||
|
||||
`apply-limits` — немедленный reconcile traffic limits. Команда должна быть безопасной для повторного запуска.
|
||||
|
||||
`apply-firewall` — немедленный reconcile nftables anti-torrent rules. Команда должна менять только AWG2-owned table/chain.
|
||||
|
||||
`status` — отчёт о текущем состоянии: контейнер, PID/netns, интерфейс, peers, egress topology, ingress/ifb topology, drift summary, последние ошибки.
|
||||
|
||||
`ensure-swap` — setup-задача, не постоянный loop.
|
||||
|
||||
`validate-config` — синтаксическая и семантическая проверка.
|
||||
|
||||
## 8. Что нужно исправить относительно bash
|
||||
|
||||
Bash работает, но в Rust нужно убрать известные слабые места.
|
||||
|
||||
Первое: единый detector. Сейчас `limit_peers.sh` умеет авто-детектить интерфейс, а `docker_watcher.sh` ждёт только `awg0`. В Rust detector должен быть один и использоваться везде.
|
||||
|
||||
Второе: проверка полного state. Старый skip decision в основном смотрит фильтры на WG-интерфейсе. Rust должен проверять также `ifb0`, ingress redirect, classes на `ifb0`, qdisc leaves и соответствие class_id ↔ peer_ip.
|
||||
|
||||
Третье: парсинг peers. Текущий regex жёстко ищет `10.8.1.x`. В Rust подсеть должна быть конфигурируемой, а парсинг `wg show all allowed-ips` — структурным.
|
||||
|
||||
Четвёртое: наблюдаемость. Status должен отличать “container отсутствует”, “interface отсутствует”, “wg не читается”, “tc отсутствует”, “rules partial”, “rules match desired”.
|
||||
|
||||
Пятое: namespace ownership. Не надо полагаться на shell внутри контейнера как на единственный способ управления. Бинарь должен уметь работать от host authority и заходить в namespace контейнера.
|
||||
|
||||
## 9. Единый конфиг
|
||||
|
||||
Конфиг должен жить в `/etc/awg2-harness/config.toml` и содержать только параметры, которые реально меняются оператором.
|
||||
|
||||
```toml
|
||||
[amnezia]
|
||||
container_names = ["amnezia-awg2", "amnezia-awg"]
|
||||
interface_candidates = ["awg0", "wg0", "amnezia0", "tun0"]
|
||||
interface_patterns = ["awg*", "wg*", "amnezia*", "tun*"]
|
||||
peer_ipv4_cidr = "10.8.1.0/24"
|
||||
|
||||
[traffic]
|
||||
enabled = true
|
||||
peer_limit = "50mbit"
|
||||
root_rate = "500mbit"
|
||||
reconcile_interval_secs = 15
|
||||
container_ready_timeout_secs = 120
|
||||
class_start_id = 10
|
||||
ifb_name = "ifb0"
|
||||
|
||||
[host_firewall]
|
||||
enabled = true
|
||||
ssh_port = 2323
|
||||
amnezia_port = 443
|
||||
amnezia_protocols = ["udp"]
|
||||
allow_icmp = true
|
||||
default_input_policy = "drop"
|
||||
|
||||
[anti_torrent]
|
||||
enabled = true
|
||||
max_connections_per_peer = 150
|
||||
peer_ipv4_cidr = "10.8.1.0/24"
|
||||
apply_in_amnezia_netns = true
|
||||
|
||||
[reboot]
|
||||
enabled = true
|
||||
window_start_utc = "01:00"
|
||||
window_end_utc = "02:00"
|
||||
|
||||
[swap]
|
||||
enabled = true
|
||||
path = "/swapfile"
|
||||
size_mb = 2048
|
||||
priority = 1
|
||||
swappiness = 1
|
||||
```
|
||||
|
||||
`ifb_name` можно оставить в конфиге, потому что это имя системного интерфейса, которым владеет orchestrator. Handles `1:`, `2:`, `ffff:` лучше не выносить в конфиг: это implementation contract AWG traffic module, а не эксплуатационная настройка.
|
||||
|
||||
## 10. Anti-torrent extension
|
||||
|
||||
Anti-torrent не входит в старую bash-систему, но хорошо ложится в целевую модель multi-orchestrator. Бинарь уже владеет нужным network namespace и должен уметь применять, проверять и восстанавливать nftables state так же, как он восстанавливает `tc` topology.
|
||||
|
||||
Правильная v1-модель — не protocol detection и не strike-система, а простой per-peer connection-count guard через nftables `ct count`. Правило должно быть scoped к VPN peer CIDR и keyed by source peer IP через `meter`, иначе можно случайно получить общий лимит на всю подсеть.
|
||||
|
||||
Правило должно жить в отдельной AWG2-owned таблице, например `table inet awg2_harness`, и применяться в Amnezia network namespace. Нельзя делать `flush ruleset`; удалять можно только owned table/chain.
|
||||
|
||||
См. `docs/ANTI_TORRENT.md`.
|
||||
|
||||
## 11. Systemd-модель
|
||||
|
||||
Вместо двух старых units нужен один daemon-unit:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=AWG2 Harness multi-orchestrator
|
||||
After=network-online.target docker.service
|
||||
Wants=network-online.target
|
||||
Requires=docker.service
|
||||
StartLimitIntervalSec=60
|
||||
StartLimitBurst=5
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStartPre=/usr/sbin/awg2-harness --config /etc/awg2-harness/config.toml ensure-swap
|
||||
ExecStart=/usr/sbin/awg2-harness --config /etc/awg2-harness/config.toml daemon
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Сервис должен запускаться от root. На выделенном сервере не нужно искусственно ограничивать его так, чтобы он не мог управлять сетью. Hardening можно добавлять после реальной проверки, но нельзя включать `PrivateNetwork`, `PrivateDevices` или слишком жёсткий `CapabilityBoundingSet`, если они ломают `tc`, `ifb`, Docker или reboot.
|
||||
|
||||
## 12. Reboot и swap
|
||||
|
||||
Логика reboot вынесена в отдельный production-бинарь `reboot_module` (`awg2-reboot-scheduler`). Окно перезагрузки задаётся в конфиге, включая корректные окна через полночь. Механика `systemctl --no-block reboot` сохраняется.
|
||||
|
||||
`ensure-swap` работает как exclusive reconciler: при `swap.enabled=true` пакет полностью владеет swap-state хоста. После reconcile активен ровно один swap (`swap.path`), все прочие активные swap entries выключены (`swapoff`), а все старые swap-строки в `/etc/fstab` заменены на одну managed-запись AWG2. Для block-device swap выполняются только `swapoff` и удаление записи из `fstab`; device не удаляется.
|
||||
|
||||
## 13. Acceptance критерий миграции
|
||||
|
||||
Миграция считается корректной только если Rust-бинарь на реальном Debian 13 хосте:
|
||||
|
||||
- создаёт ту же traffic-control topology, что старая bash-система;
|
||||
- ограничивает каждого peer в обе стороны на `traffic.peer_limit`;
|
||||
- после повторного запуска не создаёт дубли и не ломает state;
|
||||
- после restart контейнера сам восстанавливает rules;
|
||||
- после restart Docker сам восстанавливает rules;
|
||||
- после reboot хоста сам восстанавливает rules;
|
||||
- после добавления peer создаёт новый class/filter;
|
||||
- после удаления peer убирает устаревший class/filter через rebuild;
|
||||
- `status` показывает не только “есть 70Mbit classes”, а полную картину desired vs actual;
|
||||
- swap и reboot работают как части одного пакета;
|
||||
- anti-torrent, если включён, применяет per-peer flow limit и не затрагивает unrelated nftables rules.
|
||||
@@ -1,230 +0,0 @@
|
||||
# Поведение `Amnezia limits (old bash)`
|
||||
|
||||
Этот документ фиксирует фактический behavior старой bash-системы. Он нужен как migration contract: Rust-версия должна воспроизвести не только видимый лимит 70 Mbit/s, но и всю сетевую topology, lifecycle-логику и диагностику.
|
||||
|
||||
## 1. Файлы старой системы
|
||||
|
||||
```text
|
||||
Amnezia limits (old bash)/
|
||||
├── limit_peers.sh.txt # основной apply/reconcile traffic-control rules
|
||||
├── docker_watcher.sh.txt # watcher Docker container start events
|
||||
├── check-vpn-limits.txt # status/diagnostic CLI
|
||||
├── amnezia-tc.service.txt # systemd oneshot apply
|
||||
├── amnezia-tc-watcher.service.txt # systemd watcher
|
||||
├── traffic_control-was.txt # production snapshot of tc qdisc/classes/filters
|
||||
├── wg-peers.txt # production snapshot of wg peers
|
||||
├── iproute2_a_core.txt # kernel/iproute2/docker/container facts
|
||||
├── permissions.txt # installed paths and enabled units
|
||||
└── Нюансы.txt # operational notes and known risks
|
||||
```
|
||||
|
||||
## 2. Runtime environment from snapshots
|
||||
|
||||
Зафиксированное окружение:
|
||||
|
||||
- Debian 13 cloud amd64 kernel line: `6.12.57+deb13-cloud-amd64`.
|
||||
- `tc` from `iproute2-6.15.0`.
|
||||
- Docker `26.1.5+dfsg1`.
|
||||
- Amnezia container name in snapshot: `amnezia-awg2`.
|
||||
- Interface in snapshot: `awg0`.
|
||||
- Systemd units `amnezia-tc.service` and `amnezia-tc-watcher.service` enabled.
|
||||
- Scripts installed in `/opt/amnezia/` and executable by root.
|
||||
|
||||
## 3. Container discovery
|
||||
|
||||
`limit_peers.sh` searches running containers in this order:
|
||||
|
||||
```text
|
||||
amnezia-awg2
|
||||
amnezia-awg
|
||||
```
|
||||
|
||||
`check-vpn-limits` uses the opposite order in its array:
|
||||
|
||||
```text
|
||||
amnezia-awg
|
||||
amnezia-awg2
|
||||
```
|
||||
|
||||
Rust should normalize this into one configured ordered list. Recommended default: `amnezia-awg2`, then `amnezia-awg`, because `amnezia-awg2` is the current AWG 2.0 production name in the snapshot.
|
||||
|
||||
## 4. Interface discovery
|
||||
|
||||
`limit_peers.sh` starts from `WG_IF`, default `wg0`. If this interface is absent inside the container, it auto-detects:
|
||||
|
||||
```text
|
||||
wg[0-9]+ | wg | awg[0-9]* | amnezia[0-9]* | tun[0-9]+
|
||||
```
|
||||
|
||||
`check-vpn-limits` detects:
|
||||
|
||||
```text
|
||||
wg[0-9]+ | wg | amnezia[0-9]* | tun[0-9]+ | veth[0-9]+
|
||||
```
|
||||
|
||||
Operational note says the important risk is future Amnezia interface rename between `awg0`, `wg0`, `amnezia0`. Rust must use a single detector shared by apply, daemon, watcher and status.
|
||||
|
||||
## 5. Peer discovery
|
||||
|
||||
Primary source:
|
||||
|
||||
```bash
|
||||
wg show all allowed-ips
|
||||
```
|
||||
|
||||
The script extracts the third field and keeps IPv4 addresses matching:
|
||||
|
||||
```text
|
||||
10.8.1.[0-9]
|
||||
```
|
||||
|
||||
Fallback source:
|
||||
|
||||
```bash
|
||||
wg show
|
||||
```
|
||||
|
||||
Then regex extracts the same `10.8.1.x` addresses.
|
||||
|
||||
The production peer snapshot contains 23 peers in `10.8.1.0/24`, with a mix of online peers and peers without endpoint/handshake. Rust must shape all peers with allowed IPs, not only currently online peers.
|
||||
|
||||
## 6. Traffic-control topology
|
||||
|
||||
The old script owns both egress and ingress shaping.
|
||||
|
||||
Egress on the AWG/WG interface:
|
||||
|
||||
```text
|
||||
<iface> root qdisc: handle 1: htb
|
||||
<iface> root class: 1:1 rate 1000mbit ceil 1000mbit
|
||||
per peer class: 1:<class_id> rate LIMIT ceil LIMIT
|
||||
per peer qdisc: fq_codel under 1:<class_id>
|
||||
per peer filter: match ip dst <peer_ip> flowid 1:<class_id>
|
||||
```
|
||||
|
||||
Ingress is implemented through IFB:
|
||||
|
||||
```text
|
||||
modprobe ifb
|
||||
ip link add name ifb0 type ifb
|
||||
ip link set dev ifb0 up
|
||||
<iface> ingress qdisc: handle ffff:
|
||||
<iface> ingress filter: action mirred egress redirect dev ifb0
|
||||
ifb0 root qdisc: handle 2: htb
|
||||
ifb0 root class: 2:1 rate 1000mbit ceil 1000mbit
|
||||
per peer class: 2:<class_id> rate LIMIT ceil LIMIT
|
||||
per peer qdisc: fq_codel under 2:<class_id>
|
||||
per peer filter: match ip src <peer_ip> flowid 2:<class_id>
|
||||
```
|
||||
|
||||
Default limit:
|
||||
|
||||
```text
|
||||
LIMIT=70mbit
|
||||
```
|
||||
|
||||
Default root rate:
|
||||
|
||||
```text
|
||||
1000mbit
|
||||
```
|
||||
|
||||
Default first class id:
|
||||
|
||||
```text
|
||||
10
|
||||
```
|
||||
|
||||
The snapshot `traffic_control-was.txt` shows classes `1:10` through `1:32`, which corresponds to 23 peers. Class assignment follows bash `sort -u`, so it is lexicographic string order, not numeric IPv4 order. For example, `10.8.1.10` sorts before `10.8.1.2`. Rust v1 must preserve this if strict parity with production class IDs is required.
|
||||
|
||||
## 7. Rebuild behavior
|
||||
|
||||
Before applying new rules, the script deletes old topology:
|
||||
|
||||
```bash
|
||||
tc qdisc del dev <iface> root || true
|
||||
tc qdisc del dev <iface> ingress || true
|
||||
ip link del ifb0 || true
|
||||
```
|
||||
|
||||
Then it recreates everything. This is important: the production model is full rebuild on detected mismatch. Rust v1 should keep this behavior unless a well-tested incremental diff is implemented later.
|
||||
|
||||
## 8. Drift detection in bash
|
||||
|
||||
The script reads existing filters:
|
||||
|
||||
```bash
|
||||
tc filter show dev <iface> parent 1:
|
||||
```
|
||||
|
||||
It extracts hex IP matches and compares count/content with expected peer IPs. If count differs or any peer IP is missing, it rebuilds rules.
|
||||
|
||||
Weakness: this only strongly verifies egress filters on `<iface>`. It does not fully verify `ifb0`, ingress redirect, qdisc leaves or per-peer ingress filters. Rust must improve this and treat any partial state as drift.
|
||||
|
||||
## 9. Docker watcher behavior
|
||||
|
||||
`docker_watcher.sh` listens to:
|
||||
|
||||
```bash
|
||||
docker events --filter event=start --format '{{.Actor.Attributes.name}}'
|
||||
```
|
||||
|
||||
If the started container is `amnezia-awg2` or `amnezia-awg`, it waits up to 20 seconds for:
|
||||
|
||||
```bash
|
||||
ip link show awg0
|
||||
```
|
||||
|
||||
Then it runs:
|
||||
|
||||
```bash
|
||||
WG_IF=awg0 /opt/amnezia/limit_peers.sh
|
||||
```
|
||||
|
||||
Weakness: hardcoded `awg0`. Rust watcher must reuse the same interface detector as `apply-limits`.
|
||||
|
||||
## 10. Status behavior
|
||||
|
||||
`check-vpn-limits` reports:
|
||||
|
||||
- status of `amnezia-tc.service`;
|
||||
- status of `amnezia-tc.timer`, although timer file is not present in the archive;
|
||||
- running container and Docker status string;
|
||||
- connected clients from `wg show`;
|
||||
- TC classes with `rate 70Mbit` on the AWG/WG interface;
|
||||
- filter statistics from `tc -s filter show dev <iface> parent 1:`;
|
||||
- last 3 events from `/var/log/amnezia-tc.log`.
|
||||
|
||||
Rust `status` must be at least as useful, but should not hardcode 70 Mbit/s; it should use config and display desired vs actual.
|
||||
|
||||
## 11. Operational guarantees stated in old notes
|
||||
|
||||
The old notes claim the system currently survives:
|
||||
|
||||
- host reboot;
|
||||
- Docker restart;
|
||||
- container restart;
|
||||
- Amnezia update;
|
||||
- user addition;
|
||||
- user removal.
|
||||
|
||||
They also identify two breakage scenarios:
|
||||
|
||||
- Amnezia fully changes `wg show` output format;
|
||||
- container loses `tc` or `iproute2` tools.
|
||||
|
||||
In the Rust architecture, the second risk should be reduced by host namespace control: `tc/ip` should preferably come from the host while operating in the container netns, rather than requiring them inside the container.
|
||||
|
||||
## 12. Migration contract
|
||||
|
||||
Rust migration is not accepted until all of the following are true:
|
||||
|
||||
- same peers are discovered as in old bash;
|
||||
- same default limit is applied: 70 Mbit/s per peer;
|
||||
- both egress and ingress shaping exist;
|
||||
- `ifb0` exists and is up when ingress shaping is enabled;
|
||||
- class ids are deterministic, start from 10 and use lexicographic bash `sort -u` peer ordering;
|
||||
- full rebuild removes stale peer rules after peer deletion;
|
||||
- repeated apply is safe;
|
||||
- daemon recovers after Docker/container lifecycle events;
|
||||
- status explains actual drift rather than printing only a success banner.
|
||||
@@ -8,8 +8,6 @@
|
||||
4. [`ACCEPTANCE_CHECKLIST.md`](ACCEPTANCE_CHECKLIST.md) — checklist перед production-ready статусом.
|
||||
5. [`ANTI_TORRENT.md`](ANTI_TORRENT.md) — nftables connection-count guard.
|
||||
6. [`IMPLEMENTATION_SPEC.md`](IMPLEMENTATION_SPEC.md) — архитектура и state reconciliation.
|
||||
7. [`OLD_BASH_BEHAVIOR.md`](OLD_BASH_BEHAVIOR.md) — что делала старая bash-система.
|
||||
8. [`MIGRATION.md`](MIGRATION.md) — контекст миграции с bash на Rust multi-orchestrator.
|
||||
|
||||
Отдельно:
|
||||
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
Изучил архив. Мой вывод: старый `Amnezia limits (old bash)/` можно убирать из проекта, но вместе с ним нужно зачистить документацию и закрыть один процессный пробел: rollback больше не должен ссылаться на `amnezia-tc.service`.
|
||||
|
||||
По текущему дереву Rust-код уже не зависит от старой bash-папки. `traffic_core` сам делает discovery контейнера/interface/peers, строит egress + IFB ingress `tc` topology, проверяет drift, восстанавливает state, ведёт status. Отдельно есть nftables host firewall, anti-torrent guard, swap reconciler и reboot scheduler. Старая папка сейчас является только историческим reference.
|
||||
|
||||
Главные места, которые конфликтуют с твоей новой моделью:
|
||||
|
||||
`tools/build.sh` сейчас принимает `debian|ubuntu`. Это надо заменить на строгий `Debian 13 amd64`. Иначе у тебя документация будет говорить “только Debian 13”, а build-helper фактически разрешит Ubuntu.
|
||||
|
||||
`tools/README.md` пишет “Debian/Ubuntu с apt”. Нужно заменить на “Debian 13 amd64”.
|
||||
|
||||
Корневой `README.md` содержит `Amnezia limits (old bash)` в дереве репозитория и отдельный абзац про historical reference. Это надо удалить. Там же строка про upstream Amnezia с Debian 12/13 и Ubuntu 22.04/24.04 лучше убрать полностью или переписать так, чтобы было ясно: AWG2 Harness поддерживает только Debian 13 amd64, независимо от того, что поддерживает Amnezia upstream.
|
||||
|
||||
`docs/OLD_BASH_BEHAVIOR.md` и `docs/MIGRATION.md` логично удалить. Сейчас они попадут в `.deb`, потому что `tools/build.sh` пакует все `docs/*.md`.
|
||||
|
||||
`docs/README.md` надо очистить от ссылок на `OLD_BASH_BEHAVIOR.md` и `MIGRATION.md`.
|
||||
|
||||
`docs/ACCEPTANCE_CHECKLIST.md` содержит rollback через старые bash-файлы и `amnezia-tc.service` / `amnezia-tc-watcher.service`. Это больше нельзя оставлять. Нужен Rust-native rollback: остановить сервисы, отключить host firewall / anti-torrent, удалить AWG2-owned `tc` topology и при необходимости удалить пакет.
|
||||
|
||||
В `docs/IMPLEMENTATION_SPEC.md`, `docs/CONFIGURATION.md`, `docs/ANTI_TORRENT.md` нужно не удалять смысл, а переименовать происхождение. Например “bash parity” заменить на “class-id stability contract”. Лексикографическую сортировку peer IP я бы оставил: код уже сортирует IP как строки, и это полезно как стабильный ABI для class id. Просто больше не надо объяснять это через старый bash.
|
||||
|
||||
Минимальный список удаления:
|
||||
|
||||
```text
|
||||
Amnezia limits (old bash)/
|
||||
docs/OLD_BASH_BEHAVIOR.md
|
||||
docs/MIGRATION.md
|
||||
```
|
||||
|
||||
Что я бы добавил до удаления старого rollback:
|
||||
|
||||
```text
|
||||
awg2-harness cleanup-limits
|
||||
```
|
||||
|
||||
или хотя бы документированную процедуру:
|
||||
|
||||
```bash
|
||||
systemctl disable --now awg2-harness.service
|
||||
systemctl disable --now awg2-reboot-scheduler.service
|
||||
|
||||
C="$(docker ps --format '{{.Names}}' | grep -E '^(amnezia-awg2|amnezia-awg)$' | head -n1)"
|
||||
PID="$(docker inspect -f '{{.State.Pid}}' "$C")"
|
||||
|
||||
nsenter -t "$PID" -n -- tc qdisc del dev awg0 root 2>/dev/null || true
|
||||
nsenter -t "$PID" -n -- tc qdisc del dev awg0 ingress 2>/dev/null || true
|
||||
nsenter -t "$PID" -n -- ip link del ifb0 2>/dev/null || true
|
||||
```
|
||||
|
||||
Для Debian 13-only модели я бы сделал три уровня защиты.
|
||||
|
||||
Первый — `tools/build.sh`: запретить всё кроме Debian 13 amd64.
|
||||
|
||||
```bash
|
||||
ensure_supported_host() {
|
||||
if [[ ! -f /etc/os-release ]]; then
|
||||
echo "Unsupported OS: missing /etc/os-release. Only Debian 13 amd64 is supported."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/os-release
|
||||
|
||||
if [[ "${ID:-}" != "debian" || "${VERSION_ID:-}" != "13" ]]; then
|
||||
echo "Unsupported OS: ${PRETTY_NAME:-unknown}. Only Debian 13 amd64 is supported."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local arch
|
||||
arch="$(dpkg --print-architecture 2>/dev/null || uname -m)"
|
||||
if [[ "$arch" != "amd64" && "$arch" != "x86_64" ]]; then
|
||||
echo "Unsupported arch: $arch. Only amd64 is supported."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
Второй — `.deb preinst`, чтобы пакет не ставился на Ubuntu/Debian 12/Debian 14 случайно.
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ ! -r /etc/os-release ]; then
|
||||
echo "awg2-harness supports only Debian 13 amd64" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
. /etc/os-release
|
||||
|
||||
if [ "${ID:-}" != "debian" ] || [ "${VERSION_ID:-}" != "13" ]; then
|
||||
echo "awg2-harness supports only Debian 13 amd64; got ${PRETTY_NAME:-unknown}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v dpkg >/dev/null 2>&1; then
|
||||
arch="$(dpkg --print-architecture)"
|
||||
if [ "$arch" != "amd64" ]; then
|
||||
echo "awg2-harness supports only amd64; got $arch" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
```
|
||||
|
||||
Третий — `validate-runtime` в Rust: проверять `/etc/os-release` и arch, чтобы ручной запуск бинаря вне `.deb` тоже явно падал.
|
||||
|
||||
Ещё один важный несостык: документация использует версию `0.1.6`, а `traffic_core/Cargo.toml` сейчас `0.1.4`. Если релиз реально `0.1.6`, лучше поднять crate version, а не жить на постоянном `--version` override.
|
||||
Версия реально 0.1.6
|
||||
+1
-1
@@ -13,7 +13,7 @@ reboot_module -> awg2-reboot-scheduler
|
||||
|
||||
## Поддерживаемая build-среда
|
||||
|
||||
- Debian/Ubuntu с `apt`.
|
||||
- Debian 13 с `apt`.
|
||||
- amd64/x86_64.
|
||||
- Rust toolchain из `rust-toolchain.toml` (`1.95.0` в текущем дереве).
|
||||
- `Cargo.lock` обязателен: build gate работает в `--locked` режиме.
|
||||
|
||||
+42
-11
@@ -125,19 +125,23 @@ setup_root_runner() {
|
||||
}
|
||||
|
||||
ensure_supported_host() {
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/os-release
|
||||
case "${ID:-}" in
|
||||
debian|ubuntu) ;;
|
||||
*) echo "Unsupported OS: ${ID:-unknown}. Only Debian/Ubuntu are supported."; exit 1 ;;
|
||||
esac
|
||||
if [[ ! -f /etc/os-release ]]; then
|
||||
echo "Unsupported OS: missing /etc/os-release. Only Debian 13 amd64 is supported."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/os-release
|
||||
|
||||
if [[ "${ID:-}" != "debian" || "${VERSION_ID:-}" != "13" ]]; then
|
||||
echo "Unsupported OS: ${PRETTY_NAME:-unknown}. Only Debian 13 amd64 is supported."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local arch
|
||||
arch="$(uname -m)"
|
||||
if [[ "$arch" != "x86_64" && "$arch" != "amd64" ]]; then
|
||||
echo "Unsupported arch: $arch. Only amd64/x86_64 is supported."
|
||||
arch="$(dpkg --print-architecture 2>/dev/null || uname -m)"
|
||||
if [[ "$arch" != "amd64" && "$arch" != "x86_64" ]]; then
|
||||
echo "Unsupported arch: $arch. Only amd64 is supported."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@@ -366,6 +370,33 @@ if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl daemon-reload || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
cat >"$staging/DEBIAN/preinst" <<'EOF'
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ ! -r /etc/os-release ]; then
|
||||
echo "awg2-harness supports only Debian 13 amd64" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
. /etc/os-release
|
||||
|
||||
if [ "${ID:-}" != "debian" ] || [ "${VERSION_ID:-}" != "13" ]; then
|
||||
echo "awg2-harness supports only Debian 13 amd64; got ${PRETTY_NAME:-unknown}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v dpkg >/dev/null 2>&1; then
|
||||
arch="$(dpkg --print-architecture)"
|
||||
if [ "$arch" != "amd64" ]; then
|
||||
echo "awg2-harness supports only amd64; got $arch" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
@@ -380,7 +411,7 @@ fi
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod 0755 "$staging/DEBIAN/postinst" "$staging/DEBIAN/postrm"
|
||||
chmod 0755 "$staging/DEBIAN/preinst" "$staging/DEBIAN/postinst" "$staging/DEBIAN/postrm"
|
||||
|
||||
mkdir -p "$DIST_DIR"
|
||||
local pkg_name
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "awg2-harness"
|
||||
version = "0.1.4"
|
||||
version = "0.1.6"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -213,6 +213,7 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn validate_runtime(&self) -> anyhow::Result<()> {
|
||||
self.check_supported_host()?;
|
||||
self.check_host_tools()
|
||||
}
|
||||
|
||||
@@ -256,6 +257,52 @@ impl Config {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_supported_host(&self) -> anyhow::Result<()> {
|
||||
let os_release = std::fs::read_to_string("/etc/os-release").map_err(|_| {
|
||||
anyhow::anyhow!(
|
||||
"unsupported host: missing /etc/os-release; only Debian 13 amd64 is supported"
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut id = String::new();
|
||||
let mut version_id = String::new();
|
||||
for line in os_release.lines() {
|
||||
if let Some((k, v)) = line.split_once('=') {
|
||||
let val = v.trim_matches('"');
|
||||
match k {
|
||||
"ID" => id = val.to_string(),
|
||||
"VERSION_ID" => version_id = val.to_string(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if id != "debian" || version_id != "13" {
|
||||
anyhow::bail!(
|
||||
"unsupported host: only Debian 13 amd64 is supported (got ID={id}, VERSION_ID={version_id})"
|
||||
);
|
||||
}
|
||||
|
||||
let arch = std::process::Command::new("dpkg")
|
||||
.args(["--print-architecture"])
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| {
|
||||
if o.status.success() {
|
||||
Some(String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| std::env::consts::ARCH.to_string());
|
||||
|
||||
if arch != "amd64" && arch != "x86_64" {
|
||||
anyhow::bail!("unsupported host arch: {arch}; only Debian 13 amd64 is supported");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hhmm(v: &str) -> anyhow::Result<()> {
|
||||
|
||||
Reference in New Issue
Block a user