mirror of
https://github.com/andrey271192/amnezia_web.git
synced 2026-09-20 14:41:59 +00:00
feat: open community panel (read-only clients only)
Ship full install stack with .amnezia-panel-edition=community; docs and footer point to Boosty/PRO. Managing peers, export, cascade and WARP require private PRO build. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
249
scripts/install.sh
Executable file
249
scripts/install.sh
Executable file
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env bash
|
||||
# Установка Amnezia Admin WebUI одной командой (см. README).
|
||||
set -euo pipefail
|
||||
|
||||
GITHUB_REPO="${GITHUB_REPO:-andrey271192/amnezia_web}"
|
||||
BRANCH="${BRANCH:-main}"
|
||||
INSTALL_DIR="${INSTALL_DIR:-/opt/amnezia-admin}"
|
||||
DATA_DIR="${DATA_DIR:-/opt/amnezia-admin-data}"
|
||||
CONTAINER_NAME="${CONTAINER_NAME:-amnezia-admin}"
|
||||
HOST_PORT="${HOST_PORT:-8080}"
|
||||
LANDING_CONTAINER="${LANDING_CONTAINER:-amnezia-web-landing}"
|
||||
LANDING_IMAGE="${LANDING_IMAGE:-amnezia-web-landing:latest}"
|
||||
LANDING_PORT="${LANDING_PORT:-80}"
|
||||
|
||||
need_root() {
|
||||
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||||
echo "Запустите от root: sudo bash или: curl ... | sudo bash"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
need_docker() {
|
||||
command -v docker >/dev/null 2>&1 || {
|
||||
echo "Ошибка: нужен Docker."
|
||||
exit 1
|
||||
}
|
||||
docker info >/dev/null 2>&1 || {
|
||||
echo "Ошибка: демон Docker не отвечает."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
need_root
|
||||
need_docker
|
||||
|
||||
REPO_SLUG="${GITHUB_REPO##*/}"
|
||||
TMP=""
|
||||
cleanup() {
|
||||
[[ -n "${TMP}" ]] && rm -rf "${TMP}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if [[ "${SKIP_DOWNLOAD:-}" != "1" ]]; then
|
||||
echo "→ Клонирование релиза ${GITHUB_REPO} (${BRANCH})..."
|
||||
TMP=$(mktemp -d)
|
||||
curl -fsSL \
|
||||
-H 'Cache-Control: no-cache' \
|
||||
-H 'Pragma: no-cache' \
|
||||
"https://github.com/${GITHUB_REPO}/archive/refs/heads/${BRANCH}.tar.gz" \
|
||||
| tar xz -C "${TMP}"
|
||||
rm -rf "${INSTALL_DIR}"
|
||||
mkdir -p "$(dirname "${INSTALL_DIR}")"
|
||||
mv "${TMP}/${REPO_SLUG}-${BRANCH}" "${INSTALL_DIR}"
|
||||
TMP=""
|
||||
fi
|
||||
|
||||
mkdir -p "${DATA_DIR}"
|
||||
|
||||
# При повторном запуске не менять внешний порт панели, если не указали HOST_PORT явно (по умолчанию 8080).
|
||||
PREV_HOST_PORT=""
|
||||
if docker inspect "${CONTAINER_NAME}" >/dev/null 2>&1; then
|
||||
PREV_HOST_PORT="$(docker port "${CONTAINER_NAME}" 3980/tcp 2>/dev/null | head -1 | awk -F: '{print $NF}')"
|
||||
if [[ -n "${PREV_HOST_PORT}" && "${HOST_PORT}" == "8080" ]]; then
|
||||
HOST_PORT="${PREV_HOST_PORT}"
|
||||
echo "→ Уже запущен ${CONTAINER_NAME}: сохраняю внешний порт ${HOST_PORT} (укажите HOST_PORT=… чтобы сменить)."
|
||||
fi
|
||||
fi
|
||||
|
||||
BOOT_PW=""
|
||||
PASS_FILE="/root/amnezia-admin.initial-password"
|
||||
if [[ -f "${DATA_DIR}/password.hash" ]]; then
|
||||
echo "→ В ${DATA_DIR} уже есть password.hash — контейнер поднимется с прежним паролем."
|
||||
elif [[ -n "${ADMIN_PASSWORD:-}" ]]; then
|
||||
BOOT_PW="${ADMIN_PASSWORD}"
|
||||
echo "→ Использую ADMIN_PASSWORD из окружения."
|
||||
elif [[ "${ALLOW_DEFAULT_PASSWORD:-}" == "1" ]] || [[ "${ALLOW_DEFAULT_PASSWORD:-}" == "true" ]]; then
|
||||
echo "→ ALLOW_DEFAULT_PASSWORD=1 — см. README, пароль по умолчанию для входа."
|
||||
else
|
||||
BOOT_PW="$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 22 || openssl rand -hex 16)"
|
||||
umask 077
|
||||
printf '%s\n' "${BOOT_PW}" >"${PASS_FILE}"
|
||||
echo "→ Первый пароль записан в ${PASS_FILE}"
|
||||
fi
|
||||
|
||||
# AWG_PROFILES: не терять при апдейте без переменной (пропадает список «Инстанс»).
|
||||
AWG_PROFILE_SNAPSHOT="/root/amnezia-admin.awg-profiles.json"
|
||||
if [[ -n "${AWG_PROFILES:-}" ]]; then
|
||||
umask 077
|
||||
printf '%s\n' "${AWG_PROFILES}" >"${AWG_PROFILE_SNAPSHOT}" 2>/dev/null || true
|
||||
elif docker inspect "${CONTAINER_NAME}" >/dev/null 2>&1; then
|
||||
PREV_AWG_PROFILES=""
|
||||
while IFS= read -r __env_line; do
|
||||
if [[ "${__env_line}" == AWG_PROFILES=* ]]; then
|
||||
PREV_AWG_PROFILES="${__env_line#AWG_PROFILES=}"
|
||||
break
|
||||
fi
|
||||
done < <(docker inspect "${CONTAINER_NAME}" --format '{{range .Config.Env}}{{println .}}{{end}}')
|
||||
if [[ -n "${PREV_AWG_PROFILES}" ]]; then
|
||||
AWG_PROFILES="${PREV_AWG_PROFILES}"
|
||||
echo "→ AWG_PROFILES восстановлен из предыдущего контейнера ${CONTAINER_NAME}."
|
||||
umask 077
|
||||
printf '%s\n' "${AWG_PROFILES}" >"${AWG_PROFILE_SNAPSHOT}" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
if [[ -z "${AWG_PROFILES:-}" ]] && [[ -f "${AWG_PROFILE_SNAPSHOT}" ]]; then
|
||||
AWG_PROFILES="$(tr -d '\r\n' <"${AWG_PROFILE_SNAPSHOT}" || true)"
|
||||
if [[ -n "${AWG_PROFILES}" ]]; then
|
||||
echo "→ AWG_PROFILES восстановлен из ${AWG_PROFILE_SNAPSHOT}."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${AWG_PROFILES:-}" ]]; then
|
||||
__awg_multi_count="$(docker ps --format '{{.Names}}' 2>/dev/null | grep -E '^amnezia-awg' | wc -l | tr -d '[:space:]')"
|
||||
if [[ "${__awg_multi_count:-0}" =~ ^[0-9]+$ ]] && [[ "${__awg_multi_count}" -gt 1 ]]; then
|
||||
echo "⚠ Запущено ${__awg_multi_count} контейнеров с именами amnezia-awg*, но AWG_PROFILES не задан."
|
||||
echo " Переключатель «Инстанс» в панели не появится: см. README, раздел «Несколько инстансов» и «Переменные окружения и sudo»."
|
||||
echo " Без sudo -E: запишите JSON одной строкой в ${AWG_PROFILE_SNAPSHOT} и снова запустите этот установщик."
|
||||
fi
|
||||
fi
|
||||
|
||||
PREV_CONTAINER_ENV=""
|
||||
if docker inspect "${CONTAINER_NAME}" >/dev/null 2>&1; then
|
||||
PREV_CONTAINER_ENV="$(docker inspect "${CONTAINER_NAME}" --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null || true)"
|
||||
fi
|
||||
for __ui_var in UI_HIDE_SECTIONS UI_HIDE_USERS UI_HIDE_WARP UI_HIDE_CASCADE WARP_SSH_INSTALL_DIR; do
|
||||
if [[ -z "${!__ui_var:-}" ]] && [[ -n "${PREV_CONTAINER_ENV}" ]]; then
|
||||
PREV_VAL=""
|
||||
while IFS= read -r __line; do
|
||||
if [[ "${__line}" == "${__ui_var}="* ]]; then
|
||||
PREV_VAL="${__line#*=}"
|
||||
break
|
||||
fi
|
||||
done <<<"${PREV_CONTAINER_ENV}"
|
||||
if [[ -n "${PREV_VAL}" ]]; then
|
||||
printf -v "${__ui_var}" '%s' "${PREV_VAL}"
|
||||
echo "→ ${__ui_var} восстановлен из предыдущего контейнера ${CONTAINER_NAME}."
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
DOCKER_BUILD_EXTRA=()
|
||||
if [[ "${NO_CACHE:-}" == "1" ]]; then
|
||||
DOCKER_BUILD_EXTRA+=(--no-cache)
|
||||
echo "→ NO_CACHE=1 — сборка без слоя кэша Docker."
|
||||
fi
|
||||
|
||||
echo "→ Сборка образа amnezia-admin:latest ..."
|
||||
docker build "${DOCKER_BUILD_EXTRA[@]}" -t amnezia-admin:latest "${INSTALL_DIR}"
|
||||
|
||||
docker rm -f "${CONTAINER_NAME}" 2>/dev/null || true
|
||||
|
||||
RUN_ENV=(
|
||||
-e AWG_CONTAINER="${AWG_CONTAINER:-amnezia-awg2}"
|
||||
)
|
||||
|
||||
if [[ -n "${AWG_PROFILES:-}" ]]; then
|
||||
RUN_ENV+=( -e "AWG_PROFILES=${AWG_PROFILES}" )
|
||||
fi
|
||||
|
||||
if [[ -n "${TIME_SYNC_SSH_HOST:-}" ]]; then
|
||||
RUN_ENV+=( -e "TIME_SYNC_SSH_HOST=${TIME_SYNC_SSH_HOST}" )
|
||||
fi
|
||||
|
||||
if [[ -n "${TIME_SYNC_DISABLED:-}" ]]; then
|
||||
RUN_ENV+=( -e "TIME_SYNC_DISABLED=${TIME_SYNC_DISABLED}" )
|
||||
fi
|
||||
|
||||
if [[ -n "${TZ:-}" ]]; then
|
||||
RUN_ENV+=( -e "TZ=${TZ}" )
|
||||
fi
|
||||
|
||||
for __warp_var in WARP_DIR WARP_CONF_PATH WARP_CLIENTS_LIST AMNEZIA_START_SCRIPT WARP_SSH_INSTALL_DIR; do
|
||||
if [[ -n "${!__warp_var:-}" ]]; then
|
||||
RUN_ENV+=( -e "${__warp_var}=${!__warp_var}" )
|
||||
fi
|
||||
done
|
||||
|
||||
for __export_var in CLIENT_CONFIG_ENDPOINT CLIENT_EXPORT_DNS1 CLIENT_EXPORT_DNS2 EXPORT_CONFIG_SECRET; do
|
||||
if [[ -n "${!__export_var:-}" ]]; then
|
||||
RUN_ENV+=( -e "${__export_var}=${!__export_var}" )
|
||||
fi
|
||||
done
|
||||
|
||||
for __ui_var in UI_HIDE_SECTIONS UI_HIDE_USERS UI_HIDE_WARP UI_HIDE_CASCADE WARP_SSH_INSTALL_DIR; do
|
||||
if [[ -n "${!__ui_var:-}" ]]; then
|
||||
RUN_ENV+=( -e "${__ui_var}=${!__ui_var}" )
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "${BOOT_PW}" ]]; then
|
||||
RUN_ENV+=( -e "ADMIN_PASSWORD=${BOOT_PW}" )
|
||||
elif [[ "${ALLOW_DEFAULT_PASSWORD:-}" == "1" ]] || [[ "${ALLOW_DEFAULT_PASSWORD:-}" == "true" ]]; then
|
||||
RUN_ENV+=( -e "ALLOW_DEFAULT_PASSWORD=1" )
|
||||
fi
|
||||
|
||||
if [[ -n "${AMNEZIA_EDITION:-}" ]]; then
|
||||
RUN_ENV+=( -e "AMNEZIA_EDITION=${AMNEZIA_EDITION}" )
|
||||
elif [[ -f "${INSTALL_DIR}/.amnezia-panel-edition" ]]; then
|
||||
__PE="$(tr -d '\r\n' <"${INSTALL_DIR}/.amnezia-panel-edition" | head -c 48)"
|
||||
if [[ -n "${__PE}" ]]; then
|
||||
RUN_ENV+=( -e "AMNEZIA_EDITION=${__PE}" )
|
||||
echo "→ AMNEZIA_EDITION из ${INSTALL_DIR}/.amnezia-panel-edition: ${__PE}"
|
||||
fi
|
||||
fi
|
||||
for __ce_var in COMMUNITY_UPGRADE_URL COMMUNITY_UPGRADE_PITCH; do
|
||||
if [[ -n "${!__ce_var:-}" ]]; then
|
||||
RUN_ENV+=( -e "${__ce_var}=${!__ce_var}" )
|
||||
fi
|
||||
done
|
||||
|
||||
IP="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
|
||||
|
||||
docker run -d --name "${CONTAINER_NAME}" --restart unless-stopped \
|
||||
-p "${HOST_PORT}:3980" \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v "${DATA_DIR}:/data" \
|
||||
"${RUN_ENV[@]}" \
|
||||
amnezia-admin:latest
|
||||
|
||||
if [[ "${SKIP_LANDING:-}" != "1" ]] && [[ -d "${INSTALL_DIR}/landing" ]]; then
|
||||
printf "window.__AMNEZIA_ADMIN_PORT__='%s';\n" "${HOST_PORT}" >"${INSTALL_DIR}/landing/admin-port.js"
|
||||
echo "→ Сборка образа ${LANDING_IMAGE} (страница на порту ${LANDING_PORT})..."
|
||||
docker build "${DOCKER_BUILD_EXTRA[@]}" -t "${LANDING_IMAGE}" "${INSTALL_DIR}/landing"
|
||||
docker rm -f "${LANDING_CONTAINER}" 2>/dev/null || true
|
||||
if docker run -d --name "${LANDING_CONTAINER}" --restart unless-stopped \
|
||||
-p "${LANDING_PORT}:80" \
|
||||
"${LANDING_IMAGE}"; then
|
||||
echo "→ Публичная страница (лендинг): http://${IP:-SERVER_IP}:${LANDING_PORT}/"
|
||||
else
|
||||
echo "⚠ Не удалось запустить лендинг (часто порт ${LANDING_PORT} занят). Поставьте LANDING_PORT=8081 или SKIP_LANDING=1."
|
||||
fi
|
||||
else
|
||||
echo "→ Лендинг пропущен (SKIP_LANDING=1 или нет каталога landing)."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Готово ==="
|
||||
echo "Админ-панель: http://${IP:-SERVER_IP}:${HOST_PORT}"
|
||||
if [[ "${SKIP_LANDING:-}" != "1" ]]; then
|
||||
echo "Лендинг для пользователей: http://${IP:-SERVER_IP}:${LANDING_PORT}/ (ссылки доната автора — только в админ-панели)"
|
||||
fi
|
||||
if [[ -f "${PASS_FILE}" ]]; then
|
||||
echo "Первый пароль: $(cat "${PASS_FILE}")"
|
||||
fi
|
||||
if [[ "${ALLOW_DEFAULT_PASSWORD:-}" == "1" ]] || [[ "${ALLOW_DEFAULT_PASSWORD:-}" == "true" ]]; then
|
||||
echo "Пароль по умолчанию (смените в панели): AmneziaAdmin!ChangeMe"
|
||||
fi
|
||||
echo ""
|
||||
echo "Удаление: curl -fsSL https://raw.githubusercontent.com/${GITHUB_REPO}/${BRANCH}/scripts/uninstall.sh | sudo bash"
|
||||
46
scripts/uninstall.sh
Executable file
46
scripts/uninstall.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
# Удаление контейнера и опционально данных (см. README).
|
||||
set -euo pipefail
|
||||
|
||||
CONTAINER_NAME="${CONTAINER_NAME:-amnezia-admin}"
|
||||
IMAGE_NAME="${IMAGE_NAME:-amnezia-admin:latest}"
|
||||
INSTALL_DIR="${INSTALL_DIR:-/opt/amnezia-admin}"
|
||||
DATA_DIR="${DATA_DIR:-/opt/amnezia-admin-data}"
|
||||
|
||||
need_root() {
|
||||
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||||
echo "Запустите от root: curl ... | sudo bash"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
need_root
|
||||
|
||||
echo "→ Останавливаю лендинг ${LANDING_CONTAINER:-amnezia-web-landing}..."
|
||||
docker rm -f "${LANDING_CONTAINER:-amnezia-web-landing}" 2>/dev/null || echo "(лендинг уже отсутствует)"
|
||||
|
||||
echo "→ Останавливаю контейнер ${CONTAINER_NAME}..."
|
||||
docker rm -f "${CONTAINER_NAME}" 2>/dev/null || echo "(контейнер уже отсутствует)"
|
||||
|
||||
if [[ "${REMOVE_LANDING_IMAGE:-}" == "1" ]]; then
|
||||
echo "→ Удаляю образ ${LANDING_IMAGE:-amnezia-web-landing:latest}..."
|
||||
docker rmi "${LANDING_IMAGE:-amnezia-web-landing:latest}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [[ "${REMOVE_IMAGE:-}" == "1" ]]; then
|
||||
echo "→ Удаляю образ ${IMAGE_NAME}..."
|
||||
docker rmi "${IMAGE_NAME}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [[ "${REMOVE_DATA:-}" == "1" ]]; then
|
||||
echo "→ Удаляю данные панели ${DATA_DIR}..."
|
||||
rm -rf "${DATA_DIR}"
|
||||
fi
|
||||
|
||||
if [[ "${REMOVE_SRC:-}" == "1" ]]; then
|
||||
echo "→ Удаляю каталог исходников ${INSTALL_DIR}..."
|
||||
rm -rf "${INSTALL_DIR}"
|
||||
fi
|
||||
|
||||
echo "Готово."
|
||||
echo "Подсказка: REMOVE_LANDING_IMAGE=1 — удалить образ лендинга; REMOVE_DATA=1 REMOVE_SRC=1 REMOVE_IMAGE=1 curl ... | sudo bash — полная очистка."
|
||||
280
scripts/warp-amnezia.sh
Executable file
280
scripts/warp-amnezia.sh
Executable file
@@ -0,0 +1,280 @@
|
||||
#!/usr/bin/env bash
|
||||
# Cloudflare WARP внутри контейнера AmneziaWG (wgcf → warp.conf → wg-quick).
|
||||
# Запускать на хосте VPS от root: установка и обслуживание туннеля WARP для панели Amnezia Admin.
|
||||
# После install управление «кто выходит через WARP» — в веб-панели (раздел WARP). Полное снятие: подкоманда uninstall.
|
||||
set -euo pipefail
|
||||
|
||||
WGCF_VERSION="${WGCF_VERSION:-2.2.30}"
|
||||
WGCF_BIN="${WGCF_BIN:-/root/wgcf}"
|
||||
WGCF_ACCOUNT="${WGCF_ACCOUNT:-/root/wgcf-account.toml}"
|
||||
WGCF_PROFILE="${WGCF_PROFILE:-/root/wgcf-profile.conf}"
|
||||
|
||||
usage() {
|
||||
echo "Использование: $0 {install|start|stop|status|rekey|uninstall} [имя_контейнера]"
|
||||
echo "Переменные: AWG_CONTAINER, WARP_DIR (по умолчанию /opt/warp), AMNEZIA_START_SCRIPT (для uninstall, по умолчанию /opt/amnezia/start.sh)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
need_root() {
|
||||
if [[ "${EUID:-0}" -ne 0 ]]; then
|
||||
echo "Запустите от root."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
pick_container() {
|
||||
local c="${2:-${AWG_CONTAINER:-}}"
|
||||
if [[ -n "$c" ]] && docker exec "$c" true 2>/dev/null; then
|
||||
CONTAINER="$c"
|
||||
return 0
|
||||
fi
|
||||
local -a found=()
|
||||
while IFS= read -r n; do found+=("$n"); done < <(docker ps --format '{{.Names}}' | grep -E '^amnezia-awg2$|^amnezia-awg$' || true)
|
||||
if [[ ${#found[@]} -eq 1 ]]; then
|
||||
CONTAINER="${found[0]}"
|
||||
return 0
|
||||
fi
|
||||
if [[ ${#found[@]} -gt 1 ]]; then
|
||||
echo "Несколько контейнеров: ${found[*]}. Укажите вторым аргументом или AWG_CONTAINER="
|
||||
exit 1
|
||||
fi
|
||||
echo "Не найден контейнер amnezia-awg / amnezia-awg2."
|
||||
exit 1
|
||||
}
|
||||
|
||||
load_paths() {
|
||||
AWG_WARP_DIR="${WARP_DIR:-/opt/warp}"
|
||||
AWG_WARP_CONF="${AWG_WARP_DIR}/warp.conf"
|
||||
AWG_VPN_CONF=""
|
||||
if docker exec "$CONTAINER" test -f /opt/amnezia/awg/awg0.conf 2>/dev/null; then
|
||||
AWG_VPN_CONF="/opt/amnezia/awg/awg0.conf"
|
||||
elif docker exec "$CONTAINER" test -f /opt/amnezia/awg/wg0.conf 2>/dev/null; then
|
||||
AWG_VPN_CONF="/opt/amnezia/awg/wg0.conf"
|
||||
else
|
||||
for f in /opt/amnezia/awg/wg0.conf /opt/amnezia/awg/awg0.conf /etc/wireguard/wg0.conf; do
|
||||
if docker exec "$CONTAINER" test -f "$f" 2>/dev/null; then
|
||||
AWG_VPN_CONF="$f"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
[[ -n "$AWG_VPN_CONF" ]] || {
|
||||
echo "Не найден конфиг WireGuard/AWG в контейнере."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
install_wgcf() {
|
||||
[[ -x "$WGCF_BIN" ]] && return 0
|
||||
local arch wa
|
||||
arch="$(uname -m)"
|
||||
case "$arch" in
|
||||
x86_64) wa="amd64" ;;
|
||||
aarch64 | arm64) wa="arm64" ;;
|
||||
armv7l) wa="armv7" ;;
|
||||
*) echo "Архитектура не поддерживается: $arch"; exit 1 ;;
|
||||
esac
|
||||
wget -q -O "$WGCF_BIN" "https://github.com/ViRb3/wgcf/releases/download/v${WGCF_VERSION}/wgcf_${WGCF_VERSION}_linux_${wa}"
|
||||
chmod +x "$WGCF_BIN"
|
||||
}
|
||||
|
||||
ensure_account() {
|
||||
if [[ ! -f "$WGCF_ACCOUNT" ]]; then
|
||||
echo "Регистрация WARP (wgcf register)…"
|
||||
(cd /root && yes | "$WGCF_BIN" register >/dev/null 2>&1 || true)
|
||||
fi
|
||||
[[ -f "$WGCF_ACCOUNT" ]] || {
|
||||
echo "Не создан $WGCF_ACCOUNT"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
generate_profile() {
|
||||
(cd /root && yes | "$WGCF_BIN" generate >/dev/null 2>&1 || true)
|
||||
[[ -f "$WGCF_PROFILE" ]] || {
|
||||
echo "Не создан $WGCF_PROFILE"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
resolve_endpoint() {
|
||||
local ep
|
||||
ep="$(getent ahostsv4 engage.cloudflareclient.com 2>/dev/null | awk 'NR==1{print $1}')"
|
||||
[[ -n "$ep" ]] || {
|
||||
echo "Не удалось резолвить engage.cloudflareclient.com"
|
||||
exit 1
|
||||
}
|
||||
echo "$ep"
|
||||
}
|
||||
|
||||
build_warp_conf() {
|
||||
local endpoint_ip="$1"
|
||||
local pk pub addr
|
||||
pk="$(awk -F' = ' '/^PrivateKey = /{print $2}' "$WGCF_PROFILE")"
|
||||
pub="$(awk -F' = ' '/^PublicKey = /{print $2}' "$WGCF_PROFILE")"
|
||||
addr="$(awk -F' = ' '/^Address = /{print $2}' "$WGCF_PROFILE" | cut -d',' -f1)"
|
||||
docker exec "$CONTAINER" sh -c "mkdir -p '$AWG_WARP_DIR'"
|
||||
docker cp "$WGCF_PROFILE" "${CONTAINER}:${AWG_WARP_DIR}/wgcf-profile.conf" 2>/dev/null || true
|
||||
docker exec "$CONTAINER" sh -c "cat > '$AWG_WARP_CONF' <<WARPEOF
|
||||
[Interface]
|
||||
PrivateKey = ${pk}
|
||||
Address = ${addr}
|
||||
MTU = 1280
|
||||
Table = off
|
||||
|
||||
[Peer]
|
||||
PublicKey = ${pub}
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
Endpoint = ${endpoint_ip}:2408
|
||||
PersistentKeepalive = 25
|
||||
WARPEOF
|
||||
chmod 600 '$AWG_WARP_CONF'"
|
||||
}
|
||||
|
||||
warp_up() {
|
||||
docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' >/dev/null 2>&1 || true"
|
||||
docker exec "$CONTAINER" sh -c "wg-quick up '$AWG_WARP_CONF'"
|
||||
docker exec "$CONTAINER" ip addr show warp >/dev/null 2>&1 || {
|
||||
echo "Интерфейс warp не поднялся."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
warp_down() {
|
||||
docker exec "$CONTAINER" sh -c "wg-quick down '$AWG_WARP_CONF' 2>/dev/null || true"
|
||||
}
|
||||
|
||||
is_installed() {
|
||||
docker exec "$CONTAINER" test -f "$AWG_WARP_CONF" 2>/dev/null
|
||||
}
|
||||
|
||||
is_running() {
|
||||
docker exec "$CONTAINER" ip addr show warp >/dev/null 2>&1
|
||||
}
|
||||
|
||||
cmd_uninstall() {
|
||||
echo "→ Останавливаю WARP и убираю автозапуск в контейнере ${CONTAINER}…"
|
||||
warp_down || true
|
||||
local START_SCRIPT="${AMNEZIA_START_SCRIPT:-/opt/amnezia/start.sh}"
|
||||
docker exec \
|
||||
-e START_SCRIPT="$START_SCRIPT" \
|
||||
-e WARP_CONF="$AWG_WARP_CONF" \
|
||||
-e WARP_DIR="$AWG_WARP_DIR" \
|
||||
"$CONTAINER" sh -c '
|
||||
set +e
|
||||
ip rule | awk "/lookup 100/ {print \$1}" | sed "s/://g" | sort -rn | while read -r pr; do ip rule del priority "$pr" 2>/dev/null || true; done
|
||||
iptables -t nat -S POSTROUTING 2>/dev/null | grep -- "-o warp -j MASQUERADE" | while read -r line; do
|
||||
rule=$(echo "$line" | sed "s/^-A /-D /")
|
||||
iptables -t nat $rule 2>/dev/null || true
|
||||
done
|
||||
ip route flush table 100 2>/dev/null || true
|
||||
if [ -f "$START_SCRIPT" ] && grep -qF "# --- WARP-MANAGER BEGIN ---" "$START_SCRIPT" 2>/dev/null; then
|
||||
sed -i "/# --- WARP-MANAGER BEGIN ---/,/# --- WARP-MANAGER END ---/d" "$START_SCRIPT"
|
||||
fi
|
||||
rm -f "$WARP_CONF" "${WARP_DIR}/clients.list" "${WARP_DIR}/wgcf-profile.conf" 2>/dev/null || true
|
||||
'
|
||||
echo "→ Перезапускаю контейнер ${CONTAINER}…"
|
||||
docker restart "$CONTAINER" >/dev/null
|
||||
echo "Готово: WARP отключён, файлы в контейнере и блок в start.sh убраны."
|
||||
echo "Учёт wgcf на хосте при желании удалите вручную: $WGCF_ACCOUNT $WGCF_PROFILE (и $WGCF_BIN, если не нужен)."
|
||||
}
|
||||
|
||||
cmd_install() {
|
||||
echo "Бэкап конфигов в контейнере…"
|
||||
docker exec "$CONTAINER" sh -c "
|
||||
ts=\$(date +%Y%m%d-%H%M%S)
|
||||
cp '$AWG_VPN_CONF' '${AWG_VPN_CONF}.bak-warp-'\$ts 2>/dev/null || true
|
||||
cp /opt/amnezia/start.sh /opt/amnezia/start.sh.bak-warp-\$ts 2>/dev/null || true
|
||||
true
|
||||
"
|
||||
install_wgcf
|
||||
ensure_account
|
||||
generate_profile
|
||||
local ep
|
||||
ep="$(resolve_endpoint)"
|
||||
echo "Endpoint: $ep"
|
||||
build_warp_conf "$ep"
|
||||
warp_up
|
||||
echo "Готово: WARP установлен. Управление клиентами — в веб-панели (раздел WARP)."
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
if is_installed; then
|
||||
echo "warp.conf: есть ($AWG_WARP_CONF)"
|
||||
else
|
||||
echo "warp.conf: нет — выполните: $0 install"
|
||||
exit 1
|
||||
fi
|
||||
if is_running; then
|
||||
echo "Интерфейс warp: поднят"
|
||||
docker exec "$CONTAINER" wg show warp 2>/dev/null || true
|
||||
echo -n "Внешний IP через WARP: "
|
||||
docker exec "$CONTAINER" sh -c "curl -fsS --interface warp --connect-timeout 4 https://ifconfig.me 2>/dev/null || echo '?'"
|
||||
echo
|
||||
else
|
||||
echo "Интерфейс warp: опущен ($0 start)"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_rekey() {
|
||||
is_installed || {
|
||||
echo "Сначала install."
|
||||
exit 1
|
||||
}
|
||||
warp_down || true
|
||||
rm -f "$WGCF_ACCOUNT"
|
||||
ensure_account
|
||||
generate_profile
|
||||
local ep
|
||||
ep="$(resolve_endpoint)"
|
||||
build_warp_conf "$ep"
|
||||
warp_up
|
||||
echo "Ключ WARP перевыпущен. Заново отметьте клиентов в веб-панели и примените маршрутизацию."
|
||||
}
|
||||
|
||||
[[ "${1:-}" ]] || usage
|
||||
need_root
|
||||
command -v docker >/dev/null || {
|
||||
echo "Нужен docker в PATH."
|
||||
exit 1
|
||||
}
|
||||
|
||||
CMD="$1"
|
||||
pick_container "$@"
|
||||
load_paths
|
||||
|
||||
case "$CMD" in
|
||||
install)
|
||||
if is_installed && is_running; then
|
||||
echo "Уже установлен и работает."
|
||||
exit 0
|
||||
fi
|
||||
if is_installed && ! is_running; then
|
||||
echo "Конфиг есть — поднимаю интерфейс…"
|
||||
warp_up
|
||||
exit 0
|
||||
fi
|
||||
cmd_install
|
||||
;;
|
||||
start)
|
||||
is_installed || {
|
||||
echo "Нет warp.conf — сначала install."
|
||||
exit 1
|
||||
}
|
||||
is_running && {
|
||||
echo "Уже работает."
|
||||
exit 0
|
||||
}
|
||||
warp_up
|
||||
echo "WARP поднят."
|
||||
;;
|
||||
stop)
|
||||
is_installed || exit 0
|
||||
warp_down
|
||||
echo "WARP остановлен."
|
||||
;;
|
||||
uninstall) cmd_uninstall ;;
|
||||
status) cmd_status ;;
|
||||
rekey) cmd_rekey ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user