#!/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/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 </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 < /etc/ssh/sshd_config < "$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" <> "$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 <