mirror of
https://github.com/andrey271192/amnezia_web-PRO.git
synced 2026-09-20 14:42:00 +00:00
Add a "Протоколы / инстансы" section that spins up AmneziaWG server containers straight from the panel, with port + variant selection — no need to run the Amnezia desktop app to set up the server. Core (scripts/awg-instance.sh): - create <awg2|awg|legacy> <port> [name]: pulls the public image (amneziavpn/amneziawg-go:2.0.0 / :0.2.18 / amneziavpn/amnezia-wg), generates server keys + psk, writes awg0.conf/wg0.conf with a free 10.8.<N>.0/24 subnet (scans running containers to avoid clashes), random AmneziaWG obfuscation (Jc/Jmin/Jmax/S1..S4/H1..H4 as single uint32 values — ranges break awg setconf), and a start.sh that brings the iface up via userspace amneziawg-go + NAT MASQUERADE. remove <name>, list. Backend (server.js): - Profiles are now dynamic: env AWG_PROFILES merged with managed instances persisted in /data/instances.json (getProfiles()), so a new instance is usable immediately without restarting the panel. - /api/instances (list with running/peers), /api/instances/create, /delete, /stop, /start. create runs the script then registers the profile; delete tears down container + data + profile. Infra: - Dockerfile: add bash iproute2 coreutils, COPY scripts. - install.sh: mkdir /opt/amnezia-instances and bind-mount it into the panel so docker-in-docker bind paths line up. UI (index.html/app.js/styles.css): - Cards per instance (icon, NEW badge, description, РАБОТАЕТ/ОСТАНОВЛЕН, port, connections) with Стоп/Старт, Подключения (switches the active instance), Удалить; plus a create form (variant + port). Verified end to end on a live VPS: create awg2/awg/legacy instances, interfaces come up, a client created on a new instance gets the right subnet (10.8.20.2) and Endpoint (host:51850).
168 lines
5.8 KiB
Bash
Executable File
168 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Deploy / remove an AmneziaWG server instance from public images — no Amnezia app.
|
|
# Variants: awg2 (AmneziaWG 2.0, awg-go 2.0.0), awg (classic, awg-go 0.2.18),
|
|
# legacy (kernel WireGuard, amnezia-wg).
|
|
# Usage:
|
|
# awg-instance.sh create <variant> <port> [name]
|
|
# awg-instance.sh remove <name>
|
|
# awg-instance.sh list
|
|
set -euo pipefail
|
|
|
|
INSTANCES_DIR="${INSTANCES_DIR:-/opt/amnezia-instances}"
|
|
IMG_AWG2="${IMG_AWG2:-amneziavpn/amneziawg-go:2.0.0}"
|
|
IMG_AWG="${IMG_AWG:-amneziavpn/amneziawg-go:0.2.18}"
|
|
IMG_LEGACY="${IMG_LEGACY:-amneziavpn/amnezia-wg:latest}"
|
|
|
|
err() { echo "ERROR: $*" >&2; exit 1; }
|
|
|
|
rand() { od -An -N4 -tu4 </dev/urandom | tr -d ' '; }
|
|
rand_range() { # min max
|
|
local min="$1"
|
|
local max="$2"
|
|
local span=$(( max - min + 1 ))
|
|
echo $(( min + ($(rand) % span) ))
|
|
}
|
|
rand_magic() { # single large uint32 (AmneziaWG H header), NOT a range
|
|
rand_range 1000000000 2147000000
|
|
}
|
|
|
|
image_for() {
|
|
case "$1" in
|
|
awg2) echo "$IMG_AWG2" ;;
|
|
awg) echo "$IMG_AWG" ;;
|
|
legacy) echo "$IMG_LEGACY" ;;
|
|
*) err "unknown variant: $1 (awg2|awg|legacy)" ;;
|
|
esac
|
|
}
|
|
|
|
# pick an unused 10.8.<N>.0/24 subnet
|
|
pick_subnet() {
|
|
local used n c
|
|
used="$(grep -rhoE 'Address = 10\.8\.[0-9]+\.' "$INSTANCES_DIR"/*/conf/*.conf 2>/dev/null | grep -oE '10\.8\.[0-9]+' | awk -F. '{print $3}' | sort -un || true)"
|
|
# also subnets used by any running container (native Amnezia, etc.)
|
|
for c in $(docker ps --format '{{.Names}}' 2>/dev/null); do
|
|
local a
|
|
a="$(docker exec "$c" sh -c 'cat /opt/amnezia/awg/*.conf 2>/dev/null' 2>/dev/null | grep -oE 'Address = 10\.8\.[0-9]+' | grep -oE '10\.8\.[0-9]+' | awk -F. '{print $3}' || true)"
|
|
[[ -n "$a" ]] && used="$used"$'\n'"$a"
|
|
done
|
|
used="$(printf '%s\n' "$used" | sort -un)"
|
|
# start at 20 to avoid colliding with Amnezia native default 10.8.1
|
|
for n in $(seq 20 250); do
|
|
if ! grep -qx "$n" <<<"$used"; then echo "$n"; return; fi
|
|
done
|
|
err "no free subnet"
|
|
}
|
|
|
|
cmd_create() {
|
|
local variant="$1" port="$2" name="${3:-}"
|
|
[[ "$port" =~ ^[0-9]+$ ]] && (( port>=1 && port<=65535 )) || err "bad port: $port"
|
|
local img; img="$(image_for "$variant")"
|
|
[[ -z "$name" ]] && name="amnezia-${variant}-${port}"
|
|
# sanitize name
|
|
name="$(echo "$name" | tr -cd 'a-zA-Z0-9_-')"
|
|
[[ -n "$name" ]] || err "bad name"
|
|
docker inspect "$name" >/dev/null 2>&1 && err "container $name already exists"
|
|
# port free?
|
|
if ss -lun 2>/dev/null | grep -qE "[:.]${port}\b"; then err "udp port $port busy"; fi
|
|
|
|
echo "→ pulling $img"
|
|
docker pull -q "$img" >/dev/null
|
|
|
|
local sub; sub="$(pick_subnet)"
|
|
local net="10.8.${sub}"
|
|
local dir="$INSTANCES_DIR/$name/conf"
|
|
mkdir -p "$dir"
|
|
|
|
local conf binary iface
|
|
if [[ "$variant" == "legacy" ]]; then binary="wg"; iface="wg0"; else binary="awg"; iface="awg0"; fi
|
|
conf="$dir/${iface}.conf"
|
|
|
|
# keys via the image
|
|
local priv pub psk
|
|
priv="$(docker run --rm "$img" "$binary" genkey | tr -d '\r\n')"
|
|
pub="$(printf '%s' "$priv" | docker run --rm -i "$img" "$binary" pubkey | tr -d '\r\n')"
|
|
psk="$(docker run --rm "$img" "$binary" genpsk | tr -d '\r\n')"
|
|
|
|
# interface block
|
|
{
|
|
echo "[Interface]"
|
|
echo "PrivateKey = $priv"
|
|
echo "Address = ${net}.0/24"
|
|
echo "ListenPort = $port"
|
|
if [[ "$variant" != "legacy" ]]; then
|
|
echo "Jc = $(rand_range 3 10)"
|
|
echo "Jmin = 10"
|
|
echo "Jmax = 50"
|
|
echo "S1 = $(rand_range 15 60)"
|
|
echo "S2 = $(rand_range 15 60)"
|
|
if [[ "$variant" == "awg2" ]]; then
|
|
echo "S3 = $(rand_range 5 40)"
|
|
echo "S4 = $(rand_range 1 40)"
|
|
fi
|
|
echo "H1 = $(rand_magic)"
|
|
echo "H2 = $(rand_magic)"
|
|
echo "H3 = $(rand_magic)"
|
|
echo "H4 = $(rand_magic)"
|
|
fi
|
|
} > "$conf"
|
|
|
|
printf '%s\n' "$pub" > "$dir/wireguard_server_public_key.key"
|
|
printf '%s\n' "$priv" > "$dir/wireguard_server_private_key.key"
|
|
printf '%s\n' "$psk" > "$dir/wireguard_psk.key"
|
|
printf '[]\n' > "$dir/clientsTable"
|
|
|
|
# start script run inside container
|
|
local usimpl=""
|
|
if [[ "$variant" != "legacy" ]]; then usimpl="export WG_QUICK_USERSPACE_IMPLEMENTATION=amneziawg-go"; fi
|
|
cat > "$dir/start.sh" <<EOF
|
|
#!/bin/sh
|
|
set +e
|
|
$usimpl
|
|
${binary}-quick down /opt/amnezia/awg/${iface}.conf 2>/dev/null
|
|
[ -f /opt/amnezia/awg/${iface}.conf ] && ${binary}-quick up /opt/amnezia/awg/${iface}.conf
|
|
DEV=\$(ip route 2>/dev/null | awk '/default/ {print \$5; exit}')
|
|
[ -z "\$DEV" ] && DEV=eth0
|
|
iptables -A INPUT -i ${iface} -j ACCEPT
|
|
iptables -A FORWARD -i ${iface} -j ACCEPT
|
|
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
iptables -t nat -A POSTROUTING -s ${net}.0/24 -o "\$DEV" -j MASQUERADE
|
|
exec tail -f /dev/null
|
|
EOF
|
|
chmod +x "$dir/start.sh"
|
|
|
|
echo "→ run container $name (port $port/udp, subnet ${net}.0/24)"
|
|
docker run -d --name "$name" --restart unless-stopped \
|
|
--cap-add NET_ADMIN --cap-add SYS_MODULE --privileged \
|
|
--sysctl net.ipv4.conf.all.src_valid_mark=1 \
|
|
-p "${port}:${port}/udp" \
|
|
-v /lib/modules:/lib/modules:ro \
|
|
-v "$dir:/opt/amnezia/awg" \
|
|
"$img" sh /opt/amnezia/awg/start.sh >/dev/null
|
|
|
|
sleep 2
|
|
if docker exec "$name" "$binary" show "$iface" >/dev/null 2>&1; then
|
|
echo "OK container=$name variant=$variant port=$port subnet=${net}.0/24 binary=$binary iface=$iface conf=$conf"
|
|
else
|
|
echo "WARN container started but '$binary show $iface' failed — check: docker logs $name"
|
|
echo "OK_PARTIAL container=$name variant=$variant port=$port"
|
|
fi
|
|
}
|
|
|
|
cmd_remove() {
|
|
local name="$1"
|
|
docker rm -f "$name" >/dev/null 2>&1 || true
|
|
rm -rf "${INSTANCES_DIR:?}/$name"
|
|
echo "removed $name"
|
|
}
|
|
|
|
cmd_list() {
|
|
docker ps --format '{{.Names}} {{.Ports}}' | grep -E '^amnezia-(awg2|awg|legacy)-|^amnezia-' || true
|
|
}
|
|
|
|
case "${1:-}" in
|
|
create) shift; cmd_create "$@" ;;
|
|
remove) shift; cmd_remove "$@" ;;
|
|
list) cmd_list ;;
|
|
*) err "usage: $0 {create <variant> <port> [name] | remove <name> | list}" ;;
|
|
esac
|