Panel redesign, per-server I1 masking, node minimum version 0.5.0, installer output
ci / check (push) Successful in 23m54s

- UI: settings tab with sections (server, API keys, Prometheus, audit, migrate,
  about); server settings moved there; master keys table rework (menu, chips,
  version, compact phone cards); three-state node version chip
- I1: random per-server template (DNS / QUIC Initial / STUN) kept in users.db,
  GET/POST /api/masking, change from the master keys banner
- Hub rejects nodes older than 0.5.0 (close 4026), /ui/nodes reports outdated
- install.sh: spinner for long commands (full output in the log), step progress
  bar, cards; installs older than 0.5.0 require a full reinstall with data reset
- Bump to Beta 0.5.0
This commit is contained in:
2026-10-29 22:59:09 +03:00
parent c7d1333125
commit 5b29993d5d
57 changed files with 3154 additions and 1106 deletions
+176 -65
View File
@@ -15,37 +15,90 @@ else
GRN=""; YLW=""; RED=""; ACC=""; BLD=""; DIM=""; NC=""
fi
# Терминал ли на выводе — запоминаем до перенаправления в лог (после него stdout — уже труба).
# Ширина — чтобы строка спиннера не переносилась.
TTY_OUT=""
[[ -t 1 ]] && TTY_OUT="y"
COLS=$(stty size 2>/dev/null </dev/tty | cut -d' ' -f2 || true)
[[ "$COLS" =~ ^[0-9]+$ && "$COLS" -ge 40 ]] || COLS=80
ok() { echo -e " ${GRN}✓${NC} $*"; }
warn() { echo -e " ${YLW}⚠${NC} $*"; }
fail() { trap - ERR; echo -e "\n ${RED}✗${NC} $*" >&2; exit 1; }
info() { echo -e " ${DIM}→ $*${NC}"; }
skip() { echo -e " ${DIM}– $*${NC}"; }
rule() { echo -e " ${DIM}────────────────────────────────────────────────────────${NC}"; }
# Заголовок шага: step "1/6 AmneziaWG" → «▍ AmneziaWG 1 из 6»; без номера — просто заголовок.
# Заголовок шага — полоса прогресса фиксированной ширины, затем название (так названия стоят
# в один столбец): step "4/6 Запуск AWG" → «━━━━━━━━──── Запуск AWG 4/6». Шаг без номера
# (проверки, вопросы) — с пустой полосой той же ширины.
STEP_W=12
step() {
local n=0 t=1 title="$*" full="" left="" bar i f
if [[ "$1" =~ ^([0-9]+)/([0-9]+)\ +(.*)$ ]]; then
echo -e "\n${ACC}▍${NC}${BLD} ${BASH_REMATCH[3]}${NC} ${DIM}${BASH_REMATCH[1]} из ${BASH_REMATCH[2]}${NC}"
n="${BASH_REMATCH[1]}"; t="${BASH_REMATCH[2]}"; title="${BASH_REMATCH[3]}"
fi
# Две половины собираем отдельно: срез ${bar:0:f} в C-локали режет байты, а «━» — три байта.
f=$(( n * STEP_W / t ))
for (( i = 0; i < STEP_W; i++ )); do
if (( i < f )); then full+="━"; else left+="─"; fi
done
bar="${ACC}${full}${NC}${DIM}${left}${NC}"
if (( n > 0 )); then
echo -e "\n ${bar} ${BLD}${title}${NC} ${DIM}${n}/${t}${NC}"
else
echo -e "\n${ACC}▍${NC}${BLD} $*${NC}"
echo -e "\n ${bar} ${BLD}${title}${NC}"
fi
}
# Карточка — строки с цветной полосой слева (правой рамки нет: ширину кириллицы в C-локали
# bash не посчитает, и рамка бы поехала). card_line ЦВЕТ "текст".
card_line() { echo -e " ${1}┃${NC} ${2:-}"; }
# Приглашение к вводу — для read -p.
Q=" ${ACC}›${NC} "
# Вывод команд — полностью, на экран и в лог, приглушённым блоком «│» под шагом.
# run cmd … — выполнить, показать всё, что она пишет (stdout+stderr); код возврата — её.
# Вывод команд. В лог — всегда полностью, приглушённым блоком «│». На экране (терминал) — одна
# строка: спиннер, последняя строка вывода и время; упала команда — её последние 30 строк.
# Без терминала или с VERBOSE=1 — весь вывод и на экран, как в логе.
# run cmd … — выполнить; код возврата — её.
# … | logblock — то же оформление для готового текста (хвост make.log, dmesg).
# Переменные перед вызовом функции bash передаёт и в неё, и в её команды:
# DEBIAN_FRONTEND=noninteractive run apt-get install -y …
# Дескрипторы (открываются ниже, вместе с логом): 3 — только в лог, 4 — только на экран.
# Командам 3 закрываем: демон, унаследовавший его, держал бы лог открытым после выхода.
logblock() {
local line
while IFS= read -r line || [[ -n "$line" ]]; do
printf ' %s│ %s%s\n' "$DIM" "$line" "$NC"
done
}
SPIN=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)
run() {
"$@" 2>&1 | logblock
return "${PIPESTATUS[0]}"
if [[ -z "$TTY_OUT" || -n "${VERBOSE:-}" ]] || ! { true >&3; } 2>/dev/null; then
"$@" 2>&1 3>&- | logblock
return "${PIPESTATUS[0]}"
fi
local out pid rc=0 i=0 t0=$SECONDS last el w name="$1"
[[ "$name" == */* ]] && name="${name##*/}"
[[ -n "${2:-}" && "$2" != -* ]] && name+=" $2"
w=$(( COLS - ${#name} - 20 )); (( w < 10 )) && w=10
out=$(mktemp)
printf ' %s$ %s%s\n' "$DIM" "$*" "$NC" >&3
"$@" > "$out" 2>&1 3>&- &
pid=$!
while kill -0 "$pid" 2>/dev/null; do
el=$(( SECONDS - t0 ))
last=$(tail -n 1 "$out" 2>/dev/null | tr -d '\r\033\t' | cut -c1-"$w" || true)
printf '\r\033[K %s%s%s %s %s%s %d:%02d%s' "$ACC" "${SPIN[i++ % 10]}" "$NC" "$name" "$DIM" "$last" $(( el / 60 )) $(( el % 60 )) "$NC" >&4
sleep 0.12
done
wait "$pid" || rc=$?
printf '\r\033[K' >&4
logblock < "$out" >&3
(( rc != 0 )) && printf ' ✗ код %d\n' "$rc" >&3
if (( rc != 0 )); then
printf ' %s✗%s %s — код %d, последние строки (весь вывод — в логе):\n' "$RED" "$NC" "$name" "$rc" >&4
tail -n 30 "$out" | logblock >&4
fi
rm -f "$out"
return "$rc"
}
# Случайная строка из [a-zA-Z0-9]. Вход конечный: при `tr < /dev/urandom | head` tr получает
@@ -92,15 +145,22 @@ LOGFILE="/var/log/awg-install-$(date +%Y%m%d-%H%M%S).log"
# LC_ALL=C: sed режет байты, а не символы — на битом UTF-8 из чужого вывода (make.log, dmesg) он
# не спотыкается. tee -p: если запись лога вдруг умрёт, tee не падает от SIGPIPE и продолжает
# писать на экран — иначе вслед за ним молча умер бы и сам установщик.
exec > >(tee -p >(LC_ALL=C sed -u -e $'s/\033\\[[0-9;]*[A-Za-z]//g' -e $'s/.*\r//' >> "$LOGFILE")) 2>&1
# Три потока: 4 — сам терминал (спиннер run и хвост упавшей команды — только на экран);
# 3 — только лог (полный вывод команд под спиннером); stdout/stderr — и туда, и туда (tee).
exec 4>&1
exec 3> >(LC_ALL=C sed -u -e $'s/\033\\[[0-9;]*[A-Za-z]//g' -e $'s/.*\r//' >> "$LOGFILE")
LOG_SED_PID="${!:-}"
exec > >(tee -p /dev/fd/3) 2>&1
LOG_PID="${!:-}"
# Дождаться, пока tee допишет хвост, — иначе последние строки вылезут уже после приглашения шелла.
trap 'exec >&- 2>&-; wait "$LOG_PID" 2>/dev/null || true' EXIT
# Дождаться, пока tee и sed допишут хвост, — иначе последние строки вылезут уже после приглашения
# шелла. sed ждём не дольше 2 с: если какой-то процесс всё же унаследовал fd 3, выход не повиснет.
trap 'exec >&- 2>&-; wait "$LOG_PID" 2>/dev/null || true; exec 3>&-
for _ in {1..20}; do kill -0 "$LOG_SED_PID" 2>/dev/null || break; sleep 0.1; done' EXIT
# ⚠️ Дублирует .env из репозитория: установщик собирает URL релиза и печатает
# баннер ДО того, как архив с .env скачан, поэтому взять их оттуда не может.
# При бампе версии правь оба места.
VERSION="0.4.7"
VERSION="0.5.0"
CHANNEL="${CHANNEL:-Beta}"
# Откуда брать архив и как называется продукт. По умолчанию — релизы на GitHub; источник
@@ -158,25 +218,57 @@ MTU="1376"
BIN="$PROJECT/awg-control"
echo
echo -e " ${ACC}◆${NC} ${BLD}${BRAND}${NC} ${DIM}${CHANNEL} · ${VERSION}${NC}"
echo -e " ${DIM}Панель управления VPN на AmneziaWG — установка${NC}"
echo -e " ${DIM}Лог: $LOGFILE${NC}"
rule
card_line "$ACC"
card_line "$ACC" "${ACC}◆${NC} ${BLD}${BRAND}${NC} ${DIM}${CHANNEL} · ${VERSION}${NC}"
card_line "$ACC" "${DIM}Панель управления VPN на AmneziaWG — установка${NC}"
card_line "$ACC"
card_line "$ACC" "${DIM}лог $LOGFILE${NC}"
card_line "$ACC"
# Поверх существующей установки: обновить (настройки, пользователи и роль остаются)
# или переустановить с нуля. Спрашиваем первым — от ответа зависит, спрашивать ли роль.
#
# Установку старше MIN_UPGRADE_VERSION не обновить и не перенести: с ней несовместимы и данные,
# и выданные ключи. Для неё — только полная переустановка со сбросом всех данных (WIPE=y):
# пользователи, мастер-ключи, ключи API, ключи сервера и ноды — всё заново. Старые базы уходят
# в бэкап (*.bak-…), как при обычной переустановке, но перенести их нельзя. Версия — из .env
# прежней установки (он есть с 0.2.0); нет его — версия неизвестна и тоже считается устаревшей.
MIN_UPGRADE_VERSION="0.5.0"
version_lt() { [[ "$1" != "$2" && "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -1)" == "$1" ]]; }
INSTALL_MODE="fresh"
WIPE="n"
PREV_VERSION=""
if [[ -d "$PROJECT" ]]; then
PREV_VERSION=$(sed -n 's/^VERSION=//p' "$PROJECT/.env" 2>/dev/null | head -1 | tr -d "\"' " || true)
echo
warn "Уже установлено в $PROJECT"
echo -e " ${ACC}${BLD}1${NC} Обновить ${DIM}до ${VERSION}; настройки, пользователи и роль сохранятся${NC}"
echo -e " ${ACC}${BLD}2${NC} Переустановить ${DIM}с нуля; данные можно будет сохранить${NC}"
ask INST_CHOICE "Выбери [1/2, Enter — 1]: "
case "${INST_CHOICE:-1}" in
1) INSTALL_MODE="update" ;;
2) INSTALL_MODE="fresh" ;;
*) fail "Неверный выбор: введи 1 или 2" ;;
esac
if [[ -z "$PREV_VERSION" ]] || version_lt "${PREV_VERSION%%-*}" "$MIN_UPGRADE_VERSION"; then
card_line "$RED"
card_line "$RED" "${RED}${BLD}✗ Установлена устаревшая версия ${PREV_VERSION:-(неизвестна)}${NC}"
card_line "$RED"
card_line "$RED" "Обновить её до ${VERSION} нельзя: версии старше ${MIN_UPGRADE_VERSION} нужна полная переустановка."
card_line "$RED" "Будут удалены все ключи и данные:"
card_line "$RED" " ${DIM}·${NC} пользователи и их vpn:// ключи"
card_line "$RED" " ${DIM}·${NC} мастер-ключи и ссылки sen://, ключи API"
card_line "$RED" " ${DIM}·${NC} ключи сервера и подключение к панели (ноду придётся добавить заново)"
card_line "$RED"
card_line "$RED" "Выданные ключи перестанут работать — клиентам нужно будет выдать новые."
card_line "$RED" "${DIM}Старые базы останутся в бэкапе (*.bak-…), но перенести их в ${VERSION} нельзя.${NC}"
card_line "$RED"
echo
ask WIPE_YN "Переустановить с удалением всех ключей? [y/N]: "
[[ "${WIPE_YN:-n}" =~ ^[Yy]$ ]] || { echo " Отменено — ничего не изменено."; exit 0; }
WIPE="y"
else
warn "Уже установлено в $PROJECT ${DIM}(версия ${PREV_VERSION})${NC}"
echo -e " ${ACC}${BLD}1${NC} Обновить ${DIM}до ${VERSION}; настройки, пользователи и роль сохранятся${NC}"
echo -e " ${ACC}${BLD}2${NC} Переустановить ${DIM}с нуля; данные можно будет сохранить${NC}"
ask INST_CHOICE "Выбери [1/2, Enter — 1]: "
case "${INST_CHOICE:-1}" in
1) INSTALL_MODE="update" ;;
2) INSTALL_MODE="fresh" ;;
*) fail "Неверный выбор: введи 1 или 2" ;;
esac
fi
echo
fi
@@ -371,7 +463,9 @@ DB_EXISTS="n"
# - S4 добавляется к каждому пакету данных — держим маленьким, как прежние 14
# - H1–H4: непересекающиеся диапазоны в [10^8, 2^31−1] — далеко от 1–4 (типы сообщений
# WireGuard) и не выше прежних значений (некоторые клиенты разбирают их как int32)
# - I1–I5 НЕ трогать: в vpn:// ключ уходят пустыми плейсхолдерами
# - I1 здесь не задаётся и в awg1.conf не пишется: он нужен только клиентам, его выбирает
# awg-ctrl (свой у каждого сервера, хранится в users.db — KEEP_DATA его сохраняет;
# internal/ctrl/i1.go). I2–I5 в vpn:// ключ уходят пустыми плейсхолдерами
# Прежние постоянные значения: ими собраны ключи установок до рандомизации — нужны, если
# при KEEP_DATA=y awg1.conf потерялся.
LEGACY_AWG_PORT="47619"
@@ -632,9 +726,10 @@ if [[ "$INSTALL_MODE" == "update" ]]; then
install_command
start_and_status
rule
echo -e " ${GRN}${BLD}✓ Обновлено до ${VERSION}${NC} ${DIM}${ROLE_LABEL}${NC}"
rule
echo
card_line "$GRN"
card_line "$GRN" "${GRN}${BLD}✓ Обновлено до ${VERSION}${NC} ${DIM}${BRAND} · ${ROLE_LABEL}${NC}"
card_line "$GRN"
echo
exit 0
fi
@@ -683,7 +778,9 @@ fi
# vpn:// ключи пользователей привязаны к ключу сервера. ui.db следует за ними.
# Панель без VPN (core): её данные — ui.db (серверы, ключи API, мастер-ключи).
KEEP_DATA="n"
if [[ "$ROLE" == "core" ]]; then
if [[ "$WIPE" == "y" ]]; then
: # устаревшая установка: сохранять нечего, сброс уже подтверждён
elif [[ "$ROLE" == "core" ]]; then
if [[ -f "$UI_DB_FILE" ]]; then
echo
warn "Найдены данные панели: $UI_DB_FILE"
@@ -793,7 +890,7 @@ if [[ "$ROLE" == "node" ]]; then
JOIN="${JOIN:-}"
# Переустановка ноды: ключ ноды на месте, панель её уже знает — можно не выпускать
# новую строку, а оставить прежнее подключение.
if [[ -z "$JOIN" && "$PREV_ROLE" == "node" && -n "$(prev_val CORE_HOST)" && -f "$NODE_KEY_FILE" ]]; then
if [[ -z "$JOIN" && "$WIPE" != "y" && "$PREV_ROLE" == "node" && -n "$(prev_val CORE_HOST)" && -f "$NODE_KEY_FILE" ]]; then
ask KJ "Оставить подключение к панели $(prev_val CORE_HOST):$(prev_val CORE_PORT) (нода #$(prev_val NODE_ID))? [Y/n]: "
if [[ "${KJ:-y}" =~ ^[Yy]$ ]]; then
CORE_HOST="$(prev_val CORE_HOST)"; CORE_PORT="$(prev_val CORE_PORT)"
@@ -836,36 +933,39 @@ fi
# Сводка. Метки выровнены пробелами прямо в строках: printf в bash считает ширину в байтах,
# и кириллица поехала бы.
echo
echo -e " ${BLD}Будет установлено${NC}"
rule
echo -e " ${DIM}Роль ${NC} ${BLD}$ROLE_LABEL${NC}"
echo -e " ${DIM}Адрес ${NC} $SERVER_IP ${DIM}· интерфейс $NET_IFACE${NC}"
card_line "$ACC" "${BLD}Будет установлено${NC}"
card_line "$ACC"
card_line "$ACC" "${DIM}Роль ${NC} ${BLD}$ROLE_LABEL${NC}"
card_line "$ACC" "${DIM}Адрес ${NC} $SERVER_IP ${DIM}· интерфейс $NET_IFACE${NC}"
if [[ "$ROLE" != "core" ]]; then
echo -e " ${DIM}Имя сервера ${NC} $SERVER_NAME"
echo -e " ${DIM}VPN ${NC} ${AWG_PORT}/udp"
card_line "$ACC" "${DIM}Имя сервера ${NC} $SERVER_NAME"
card_line "$ACC" "${DIM}VPN ${NC} ${AWG_PORT}/udp"
fi
if [[ "$ROLE" != "node" ]]; then
echo -e " ${DIM}Панель ${NC} ${UI_PORT}/tcp ${DIM}· логин $UI_USER${NC}"
echo -e " ${DIM}Подписка sen://${NC} ${SUB_PORT}/tcp ${DIM}· $([[ "$SUB_TLS" == "on" ]] && echo "HTTPS" || echo "HTTP, ответы подписаны")${NC}"
card_line "$ACC" "${DIM}Панель ${NC} ${UI_PORT}/tcp ${DIM}· логин $UI_USER${NC}"
card_line "$ACC" "${DIM}Подписка sen://${NC} ${SUB_PORT}/tcp ${DIM}· $([[ "$SUB_TLS" == "on" ]] && echo "HTTPS" || echo "HTTP, ответы подписаны")${NC}"
if [[ -n "$NODE_PORT" ]]; then
echo -e " ${DIM}Приём нод ${NC} ${NODE_PORT}/tcp"
card_line "$ACC" "${DIM}Приём нод ${NC} ${NODE_PORT}/tcp"
else
echo -e " ${DIM}Приём нод ${NC} ${DIM}выключен${NC}"
card_line "$ACC" "${DIM}Приём нод ${NC} ${DIM}выключен${NC}"
fi
else
echo -e " ${DIM}Панель ${NC} $CORE_HOST:$CORE_PORT ${DIM}· нода #$NODE_ID${NC}"
card_line "$ACC" "${DIM}Панель ${NC} $CORE_HOST:$CORE_PORT ${DIM}· нода #$NODE_ID${NC}"
fi
if [[ "$KEEP_DATA" == "y" ]]; then
if [[ "$ROLE" == "core" ]]; then
echo -e " ${DIM}Данные ${NC} ${GRN}сохраняются: серверы и ключи панели${NC}"
card_line "$ACC" "${DIM}Данные ${NC} ${GRN}сохраняются: серверы и ключи панели${NC}"
else
echo -e " ${DIM}Данные ${NC} ${GRN}сохраняются: пользователи и ключ сервера${NC}"
card_line "$ACC" "${DIM}Данные ${NC} ${GRN}сохраняются: пользователи и ключ сервера${NC}"
fi
elif [[ "$WIPE" == "y" ]]; then
card_line "$ACC" "${DIM}Данные ${NC} ${RED}все ключи и данные удаляются${NC} ${DIM}(устаревшая версия ${PREV_VERSION:-?}; базы — в бэкап)${NC}"
elif [[ "$ROLE" != "core" && -f "$DB_FILE" ]] || [[ "$ROLE" == "core" && -f "$UI_DB_FILE" ]]; then
echo -e " ${DIM}Данные ${NC} ${YLW}с нуля, старая база уйдёт в бэкап${NC}"
card_line "$ACC" "${DIM}Данные ${NC} ${YLW}с нуля, старая база уйдёт в бэкап${NC}"
fi
echo -e " ${DIM}Каталог ${NC} ${DIM}$PROJECT${NC}"
rule
card_line "$ACC" "${DIM}Каталог ${NC} ${DIM}$PROJECT${NC}"
card_line "$ACC"
echo
ask YN "Продолжить? [Y/n]: "
[[ "${YN:-y}" =~ ^[Nn]$ ]] && { echo " Отменено."; exit 0; }
@@ -1110,6 +1210,13 @@ step "3/6 Ключи и конфиг AWG"
mkdir -p "$AWG_DIR" "$AMNEZIA_DIR"
# Сброс устаревшей установки: ключ ноды — тоже в бэкап, агент создаст новый (ноду добавляют в
# панель заново). Остальное (базы, ключи сервера и подписи) сбрасывается как при KEEP_DATA=n ниже.
if [[ "$WIPE" == "y" && -f "$NODE_KEY_FILE" ]]; then
mv "$NODE_KEY_FILE" "${NODE_KEY_FILE}.bak-$(date +%Y%m%d-%H%M%S)"
warn "Старый ключ ноды убран в бэкап — ноду нужно заново добавить в панели"
fi
# ui.db (серверы, API-ключи, мастер-ключи awg-ui) — в бэкап, чтобы awg-ui создал чистую БД.
if [[ "$KEEP_DATA" != "y" && -f "$UI_DB_FILE" ]]; then
UI_DB_BAK="${UI_DB_FILE}.bak-$(date +%Y%m%d-%H%M%S)"
@@ -1175,9 +1282,10 @@ if [[ "$ROLE" != "node" ]]; then
if [[ "$KEEP_DATA" == "y" && -f "$SUB_TLS_CRT" && -f "$SUB_TLS_KEY" ]]; then
ok "Сертификат подписки sen:// сохранён"
else
# CN случайный (12 hex, как имя хоста контейнера): постоянный CN — готовый отпечаток для DPI.
( umask 077
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
-keyout "$SUB_TLS_KEY" -out "$SUB_TLS_CRT" -days 3650 -subj "/CN=sen" 2>/dev/null )
-keyout "$SUB_TLS_KEY" -out "$SUB_TLS_CRT" -days 3650 -subj "/CN=$(openssl rand -hex 6)" 2>/dev/null )
chmod 600 "$SUB_TLS_KEY" "$SUB_TLS_CRT"
ok "Самоподписанный сертификат подписки sen:// создан (10 лет)"
fi
@@ -1515,29 +1623,32 @@ if [[ "$ROLE" == "node" ]]; then
fi
fi
rule
echo -e " ${GRN}${BLD}✓ Готово${NC} ${DIM}${BRAND} ${VERSION} · ${ROLE_LABEL}${NC}"
rule
echo
card_line "$GRN"
card_line "$GRN" "${GRN}${BLD}✓ Готово${NC} ${DIM}${BRAND} ${VERSION} · ${ROLE_LABEL}${NC}"
card_line "$GRN"
if [[ "$ROLE" != "node" ]]; then
echo -e " ${DIM}Панель ${NC} ${BLD}http://${SERVER_IP}:${UI_PORT}${NC}"
echo -e " ${DIM}Логин ${NC} ${UI_USER}"
echo -e " ${DIM}Пароль ${NC} ${YLW}${UI_PASS}${NC} ${DIM}сменить: awg-ctrl credentials${NC}"
echo
echo -e " ${DIM}sen:// ${NC} $([[ "$SUB_TLS" == "on" ]] && echo https || echo http)://${SERVER_IP}:${SUB_PORT} ${DIM}подписка SenAWG${NC}"
[[ -n "$NODE_PORT" ]] && echo -e " ${DIM}Ноды ${NC} wss://${SERVER_IP}:${NODE_PORT} ${DIM}«Добавить сервер» в панели${NC}"
card_line "$GRN" "${DIM}Панель ${NC} ${BLD}http://${SERVER_IP}:${UI_PORT}${NC}"
card_line "$GRN" "${DIM}Логин ${NC} ${UI_USER}"
card_line "$GRN" "${DIM}Пароль ${NC} ${YLW}${UI_PASS}${NC} ${DIM}сменить: awg-ctrl credentials${NC}"
card_line "$GRN"
card_line "$GRN" "${DIM}sen:// ${NC} $([[ "$SUB_TLS" == "on" ]] && echo https || echo http)://${SERVER_IP}:${SUB_PORT} ${DIM}подписка SenAWG${NC}"
[[ -n "$NODE_PORT" ]] && card_line "$GRN" "${DIM}Ноды ${NC} wss://${SERVER_IP}:${NODE_PORT} ${DIM}«Добавить сервер» в панели${NC}"
fi
if [[ "$ROLE" != "core" ]]; then
echo -e " ${DIM}VPN ${NC} ${SERVER_IP}:${AWG_PORT}/udp ${DIM}· awg-ctrl на 127.0.0.1:${AWGCTRL_PORT}${NC}"
card_line "$GRN" "${DIM}VPN ${NC} ${SERVER_IP}:${AWG_PORT}/udp ${DIM}· awg-ctrl на 127.0.0.1:${AWGCTRL_PORT}${NC}"
fi
if [[ "$ROLE" == "node" ]]; then
echo -e " ${DIM}Панель ${NC} wss://${CORE_HOST}:${CORE_PORT} ${DIM}исходящее подключение${NC}"
echo
echo -e " Сервер «${SERVER_NAME}» появится в списке серверов панели — управляй им оттуда."
card_line "$GRN" "${DIM}Панель ${NC} wss://${CORE_HOST}:${CORE_PORT} ${DIM}исходящее подключение${NC}"
card_line "$GRN"
card_line "$GRN" "Сервер «${SERVER_NAME}» появится в списке серверов панели — управляй им оттуда."
fi
rule
echo -e " ${YLW}⚠${NC} Если у хостера свой фаервол (панель провайдера, security group) — открой там:"
echo -e " ${BLD}${OPEN_PORTS[*]}${NC} ${DIM}иначе клиенты не подключатся, а на сервере всё будет выглядеть исправным${NC}"
rule
card_line "$GRN"
echo
card_line "$YLW" "${YLW}⚠${NC} Если у хостера свой фаервол (панель провайдера, security group) — открой там:"
card_line "$YLW" " ${BLD}${OPEN_PORTS[*]}${NC}"
card_line "$YLW" " ${DIM}иначе клиенты не подключатся, а на сервере всё будет выглядеть исправным${NC}"
echo
echo -e " ${DIM}awg-ctrl ${NC} меню: запуск, статус, логин и пароль, join/unjoin"
echo -e " ${DIM}systemctl restart awg-control${NC} перезапуск ${DIM}(автозапуск на загрузке включён)${NC}"
echo