feat: Add minimal server setup script for 3x-ui without TLS

This commit is contained in:
2026-04-21 10:46:01 +04:00
parent 822f3b8f8d
commit f6bb9c878d
+372
View File
@@ -0,0 +1,372 @@
#!/bin/bash
#
# Minimal Server Setup — 3x-ui без TLS-обвязки
# OS: Ubuntu 22.04 LTS
# Запуск: sudo bash minimal.sh
#
# Перед запуском положите рядом:
# - ../data/authorized_keys (обязательно)
# - ../data/x-ui.db (опционально, иначе чистый старт)
#
set -euo pipefail
# ============================================================
# Хардкод — порты по ТЗ
# ============================================================
SSH_PORT=10001
PANEL_PORT=10002
VPN_PORT=10003
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DATA_DIR="$SCRIPT_DIR/../data"
# ============================================================
# Проверки окружения
# ============================================================
if [ "$(id -u)" -ne 0 ]; then
echo "ERROR: Запустите с sudo: sudo bash $0"
exit 1
fi
if [ ! -d "$DATA_DIR" ]; then
echo "ERROR: Директория $DATA_DIR не найдена."
exit 1
fi
if [ ! -f "$DATA_DIR/authorized_keys" ]; then
echo "ERROR: $DATA_DIR/authorized_keys отсутствует — без него потеряете доступ после рестарта SSH."
exit 1
fi
# ============================================================
# Interactive: имя пользователя и таймзон
# ============================================================
ask() {
local var="$1" prompt="$2" default="$3" value
if [ -n "${!var:-}" ]; then return; fi
read -r -p "$prompt [$default]: " value </dev/tty || value=""
printf -v "$var" '%s' "${value:-$default}"
}
ask NEW_USER "Имя пользователя" "eberkheev"
ask TIMEZONE "Часовой пояс" "Europe/Moscow"
# ============================================================
# Auto-detect: текущая сеть (для пиннинга через netplan)
# ============================================================
IFACE=$(ip -o -4 route show to default | awk '{print $5; exit}')
IP_CIDR=$(ip -o -4 addr show dev "$IFACE" scope global 2>/dev/null | awk '{print $4; exit}')
GATEWAY=$(ip -o -4 route show to default | awk '{print $3; exit}')
DNS=$(resolvectl dns "$IFACE" 2>/dev/null | awk -F': ' 'NF>1 {print $2}' | tr ' ' '\n' | grep -E '^[0-9a-fA-F:.]+$' | paste -sd, -)
[ -z "$DNS" ] && DNS=$(awk '/^nameserver/ {print $2}' /etc/resolv.conf | paste -sd, -)
[ -z "$DNS" ] && DNS="8.8.8.8,1.1.1.1"
if [ -z "$IFACE" ] || [ -z "$IP_CIDR" ] || [ -z "$GATEWAY" ]; then
echo "ERROR: Не удалось определить сеть (IFACE='$IFACE' IP='$IP_CIDR' GW='$GATEWAY')."
echo "Сеть не работает или роутинг нетиповой — netplan-пиннинг опасен."
exit 1
fi
# ============================================================
# Сводка + подтверждение
# ============================================================
cat <<SUMMARY
============================================
Итоговая конфигурация
============================================
Пользователь: $NEW_USER
Часовой пояс: $TIMEZONE
SSH порт: $SSH_PORT
Панель 3x-ui: $PANEL_PORT
VPN порт: $VPN_PORT
Сеть (auto-detect, будет зафиксировано в netplan):
интерфейс: $IFACE
IP: $IP_CIDR
шлюз: $GATEWAY
DNS: $DNS
x-ui.db: $([ -f "$DATA_DIR/x-ui.db" ] && echo "будет скопирован" || echo "нет — чистый старт")
============================================
SUMMARY
read -r -p "Продолжить? [y/N]: " confirm </dev/tty || confirm=""
case "$confirm" in
y|Y|yes|YES) ;;
*) echo "Отменено."; exit 0 ;;
esac
# ============================================================
# [1/10] Обновление системы
# ============================================================
echo "=== [1/10] Обновление системы ==="
apt update && apt upgrade -y
# ============================================================
# [2/10] Установка пакетов
# ============================================================
echo "=== [2/10] Установка пакетов ==="
apt install -y \
curl \
git \
docker.io \
docker-compose \
ufw
systemctl enable docker
systemctl start docker
# ============================================================
# [3/10] Часовой пояс
# ============================================================
echo "=== [3/10] Часовой пояс ==="
timedatectl set-timezone "$TIMEZONE"
# ============================================================
# [4/10] Пользователь и SSH-ключи
# ============================================================
echo "=== [4/10] Пользователь $NEW_USER ==="
if ! id "$NEW_USER" &>/dev/null; then
adduser --disabled-password --gecos "" "$NEW_USER"
usermod -aG sudo "$NEW_USER"
echo "$NEW_USER ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/$NEW_USER"
chmod 440 "/etc/sudoers.d/$NEW_USER"
echo "Пользователь $NEW_USER создан"
else
echo "Пользователь $NEW_USER уже существует"
fi
usermod -aG docker "$NEW_USER"
USER_HOME="/home/$NEW_USER"
mkdir -p "$USER_HOME/.ssh"
cp "$DATA_DIR/authorized_keys" "$USER_HOME/.ssh/authorized_keys"
chmod 700 "$USER_HOME/.ssh"
chmod 600 "$USER_HOME/.ssh/authorized_keys"
chown -R "$NEW_USER:$NEW_USER" "$USER_HOME/.ssh"
# ============================================================
# [5/10] Netplan — пиннинг текущих сетевых настроек
# ============================================================
echo "=== [5/10] Netplan: пиннинг $IP_CIDR на $IFACE ==="
# Чистим старые конфиги, чтобы не было двух источников правды
rm -f /etc/netplan/*.yaml /etc/netplan/*.yml
cat > /etc/netplan/01-netcfg.yaml <<NETEOF
network:
version: 2
ethernets:
$IFACE:
addresses:
- $IP_CIDR
routes:
- to: default
via: $GATEWAY
nameservers:
addresses:
- ${DNS//,/$'\n - '}
NETEOF
chmod 600 /etc/netplan/01-netcfg.yaml
netplan apply
echo "Сеть зафиксирована"
# ============================================================
# [6/10] SSH
# ============================================================
echo "=== [6/10] SSH на порт $SSH_PORT ==="
cat > /etc/ssh/sshd_config <<SSHEOF
Include /etc/ssh/sshd_config.d/*.conf
Port $SSH_PORT
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
KbdInteractiveAuthentication no
UsePAM yes
X11Forwarding yes
PrintMotd no
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
SSHEOF
systemctl restart ssh
# ============================================================
# [7/10] 3x-ui
# ============================================================
echo "=== [7/10] 3x-ui ==="
XUI_DIR="$USER_HOME/3x-ui"
if [ ! -d "$XUI_DIR" ]; then
sudo -u "$NEW_USER" git clone https://github.com/MHSanaei/3x-ui.git "$XUI_DIR"
else
echo "3x-ui уже склонирован"
fi
mkdir -p "$XUI_DIR/db"
if [ -f "$DATA_DIR/x-ui.db" ]; then
cp "$DATA_DIR/x-ui.db" "$XUI_DIR/db/x-ui.db"
cp "$DATA_DIR/x-ui.db" "$USER_HOME/x-ui-backup.db"
echo "База x-ui.db скопирована"
else
echo "WARNING: x-ui.db не найден — панель поднимется с дефолтными настройками"
fi
cat > "$XUI_DIR/docker-compose.yml" <<'DCEOF'
services:
3xui:
image: ghcr.io/mhsanaei/3x-ui:latest
container_name: 3xui_app
volumes:
- ./db/:/etc/x-ui/
environment:
XRAY_VMESS_AEAD_FORCED: "false"
XUI_ENABLE_FAIL2BAN: "true"
tty: true
network_mode: host
restart: unless-stopped
DCEOF
chown -R "$NEW_USER:$NEW_USER" "$XUI_DIR"
# ============================================================
# [8/10] UFW
# ============================================================
echo "=== [8/10] UFW ==="
ufw default deny incoming
ufw default allow outgoing
ufw allow ${SSH_PORT}/tcp
ufw allow ${PANEL_PORT}/tcp
ufw allow ${VPN_PORT}/tcp
ufw --force enable
# ============================================================
# [9/10] Запуск 3x-ui
# ============================================================
echo "=== [9/10] Запуск 3x-ui ==="
docker pull ghcr.io/mhsanaei/3x-ui:latest
docker rm -f 3xui_app 2>/dev/null || true
docker run -d \
--name 3xui_app \
--restart unless-stopped \
--network host \
--env XRAY_VMESS_AEAD_FORCED=false \
--env XUI_ENABLE_FAIL2BAN=true \
-v "$XUI_DIR/db/:/etc/x-ui/" \
-t \
ghcr.io/mhsanaei/3x-ui:latest
# ============================================================
# [10/10] update.sh
# ============================================================
echo "=== [10/10] Генерация $USER_HOME/update.sh ==="
cat > "$USER_HOME/update.sh" <<UPDATEHEAD
#!/bin/bash
set -e
BACKUP_PATH="/home/$NEW_USER/x-ui-backup.db"
XUI_PATH="/home/$NEW_USER/3x-ui"
DOCKER_IMAGE="ghcr.io/mhsanaei/3x-ui:latest"
UPDATEHEAD
cat >> "$USER_HOME/update.sh" <<'UPDATEBODY'
BUILD_MODE="pull"
if [ "$1" = "--build" ]; then
BUILD_MODE="build"
fi
echo "=== Mode: $BUILD_MODE ==="
echo "=== System update ==="
sudo apt update && sudo apt upgrade -y
echo "=== Finding x-ui container ==="
CONTAINER=$(sudo docker ps --format "{{.Names}}" | grep -i "xui\|x-ui\|3xui" | head -n 1)
if [ -z "$CONTAINER" ]; then
echo "WARNING: x-ui container not found, skipping backup..."
if [ ! -f "$BACKUP_PATH" ]; then
echo "ERROR: No container and no backup found, aborting!"
exit 1
fi
echo "Existing backup found at $BACKUP_PATH, continuing..."
else
echo "Found container: $CONTAINER"
echo "=== Backup x-ui database ==="
sudo docker cp $CONTAINER:/etc/x-ui/x-ui.db $BACKUP_PATH
echo "Backup saved to $BACKUP_PATH"
fi
echo "=== Pulling latest from main ==="
cd $XUI_PATH
sudo git checkout main
sudo git pull origin main
echo "=== Stopping old container ==="
sudo docker-compose down
if [ "$BUILD_MODE" = "build" ]; then
echo "--- Building from source ---"
sudo docker-compose build --no-cache
sudo docker-compose up -d
else
echo "--- Pulling prebuilt image: $DOCKER_IMAGE ---"
sudo docker pull $DOCKER_IMAGE
sudo docker rm -f 3xui_app 2>/dev/null || true
sudo docker run -d \
--name 3xui_app \
--restart unless-stopped \
--network host \
--env XRAY_VMESS_AEAD_FORCED=false \
--env XUI_ENABLE_FAIL2BAN=true \
-v $XUI_PATH/db/:/etc/x-ui/ \
-t \
$DOCKER_IMAGE
fi
echo "=== Restoring database from backup ==="
NEW_CONTAINER=$(sudo docker ps --format "{{.Names}}" | grep -i "xui\|x-ui\|3xui" | head -n 1)
if [ -z "$NEW_CONTAINER" ]; then
echo "ERROR: Container not found after restart!"
exit 1
fi
sudo docker cp $BACKUP_PATH $NEW_CONTAINER:/etc/x-ui/x-ui.db
echo "Database restored, restarting container..."
sudo docker restart $NEW_CONTAINER
echo "=== Status ==="
sudo docker ps | grep -i "xui\|x-ui\|3xui"
sudo docker logs $NEW_CONTAINER --tail=30
echo "=== Done! ==="
UPDATEBODY
chmod +x "$USER_HOME/update.sh"
chown "$NEW_USER:$NEW_USER" "$USER_HOME/update.sh"
# ============================================================
# Финал
# ============================================================
SERVER_IP="${IP_CIDR%/*}"
cat <<DONE
============================================
Готово.
============================================
SSH: ssh -p $SSH_PORT $NEW_USER@$SERVER_IP
Панель: http://$SERVER_IP:$PANEL_PORT (TLS в x-ui.db отключите через /panel/settings)
VPN: $VPN_PORT/tcp
UFW: $SSH_PORT, $PANEL_PORT, $VPN_PORT
Сеть: $IFACE = $IP_CIDR (gw $GATEWAY, dns $DNS) — зафиксировано в /etc/netplan/01-netcfg.yaml
Обновление 3x-ui: bash ~/update.sh
Статус: docker ps
DONE