Files

373 lines
12 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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