feat: Add minimal server setup script for 3x-ui without TLS
This commit is contained in:
Executable
+372
@@ -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
|
||||||
Reference in New Issue
Block a user