From f6bb9c878d9d7a827cd21da3840e330ce4982ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AD=D0=BB=D1=8C=D0=BD=D0=B0=D1=80?= Date: Tue, 21 Apr 2026 10:46:01 +0400 Subject: [PATCH] feat: Add minimal server setup script for 3x-ui without TLS --- server-setup/manual-setup/minimal.sh | 372 +++++++++++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100755 server-setup/manual-setup/minimal.sh diff --git a/server-setup/manual-setup/minimal.sh b/server-setup/manual-setup/minimal.sh new file mode 100755 index 0000000..19fe7f7 --- /dev/null +++ b/server-setup/manual-setup/minimal.sh @@ -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/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 <