Link setup choices across server scripts
This commit is contained in:
parent
feaa85a39b
commit
ab7d3f6e03
6 changed files with 188 additions and 40 deletions
20
README.md
20
README.md
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
| Файл | Назначение | Можно запускать отдельно |
|
| Файл | Назначение | Можно запускать отдельно |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `ochenstarik-server-1.sh` | Создаёт swap-файл 2 ГБ и задаёт `vm.swappiness=20` | Да |
|
| `ochenstarik-server-1.sh` | Предлагает выбрать часовой пояс, создаёт swap-файл 2 ГБ и задаёт `vm.swappiness=20` | Да |
|
||||||
| `ochenstarik-server-2.sh` | Обновляет систему, ставит серверные и офисные пакеты, настраивает часовой пояс и UFW | Да |
|
| `ochenstarik-server-2.sh` | Предлагает выбрать будущий SSH-порт, обновляет систему, ставит пакеты и настраивает UFW | Да |
|
||||||
| `ochenstarik-server-user-3.sh` | Создаёт администратора, позволяет выбрать SSH-порт, включает fail2ban или отдельно добавляет правила UFW | После установки необходимых пакетов; проще всего сначала запустить скрипт 2 |
|
| `ochenstarik-server-user-3.sh` | Создаёт администратора, переносит SSH на порт из этапа 2, проверяет права и включает fail2ban | После установки необходимых пакетов; проще всего сначала запустить скрипт 2 |
|
||||||
| `ochenstarik-server-tg-4.sh` | Отправляет уведомления в Telegram при успешном входе по SSH | После установки OpenSSH и `curl`; обычно после скрипта 2 или 3 |
|
| `ochenstarik-server-tg-4.sh` | Отправляет уведомления в Telegram при успешном входе по SSH | После установки OpenSSH и `curl`; обычно после скрипта 2 или 3 |
|
||||||
| `ochenstarik-server-vpn-5.sh` | Устанавливает Xray и направляет системный трафик через VLESS + REALITY | Да; зависимости устанавливаются автоматически |
|
| `ochenstarik-server-vpn-5.sh` | Устанавливает Xray и направляет системный трафик через VLESS + REALITY | Да; зависимости устанавливаются автоматически |
|
||||||
|
|
||||||
|
|
@ -37,11 +37,11 @@ chmod 700 ./*.sh
|
||||||
bash -n ./ИМЯ-СКРИПТА.sh
|
bash -n ./ИМЯ-СКРИПТА.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## 1. Настройка swap
|
## 1. Часовой пояс и настройка swap
|
||||||
|
|
||||||
Файл: `ochenstarik-server-1.sh`.
|
Файл: `ochenstarik-server-1.sh`.
|
||||||
|
|
||||||
Создаёт `/swapfile` размером 2 ГБ, добавляет его в `/etc/fstab` и устанавливает `vm.swappiness=20`. Повторный запуск предусмотрен.
|
Сначала предлагает ввести часовой пояс в формате IANA, например `Asia/Novosibirsk`, `Europe/Moscow` или `UTC`, и проверяет его через `timedatectl`. Затем создаёт `/swapfile` размером 2 ГБ, добавляет его в `/etc/fstab` и устанавливает `vm.swappiness=20`. Повторный запуск предусмотрен.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-1.sh
|
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-1.sh
|
||||||
|
|
@ -61,12 +61,12 @@ sysctl vm.swappiness
|
||||||
|
|
||||||
Файл: `ochenstarik-server-2.sh`.
|
Файл: `ochenstarik-server-2.sh`.
|
||||||
|
|
||||||
Скрипт обновляет систему, устанавливает OpenSSH, UFW, fail2ban, инструменты обработки документов, изображений, аудио и видео, а также задаёт часовой пояс `Asia/Novosibirsk`.
|
Скрипт запрашивает SSH-порт, который будет применён на этапе 3, сохраняет его в `/etc/ochenstarik-server/ssh-port.conf`, обновляет систему и устанавливает OpenSSH, UFW, fail2ban, инструменты обработки документов, изображений, аудио и видео.
|
||||||
|
|
||||||
Открываются TCP-порты:
|
Открываются TCP-порты:
|
||||||
|
|
||||||
- `22` — временный SSH-порт;
|
- `22` — временный SSH-порт;
|
||||||
- `20202` — новый SSH-порт;
|
- выбранный SSH-порт — будущий порт SSH;
|
||||||
- `80`, `443` — HTTP и HTTPS;
|
- `80`, `443` — HTTP и HTTPS;
|
||||||
- `2096`, `40000`, `63636` — порты приложений.
|
- `2096`, `40000`, `63636` — порты приложений.
|
||||||
|
|
||||||
|
|
@ -93,10 +93,12 @@ timedatectl
|
||||||
1. новая установка или повторная настройка администратора и SSH;
|
1. новая установка или повторная настройка администратора и SSH;
|
||||||
2. только добавление открытых портов в UFW без изменения пользователя и SSH.
|
2. только добавление открытых портов в UFW без изменения пользователя и SSH.
|
||||||
|
|
||||||
В режиме новой установки скрипт интерактивно запрашивает имя администратора, SSH-порт, SSH-ключ и пароль. Затем он:
|
В режиме новой установки скрипт интерактивно запрашивает имя администратора, SSH-ключ и пароль. SSH-порт автоматически читается из защищённого файла, созданного этапом 2. Если этап 2 не выполнялся, скрипт предложит выбрать порт вручную. Затем он:
|
||||||
|
|
||||||
- создаёт или обновляет пользователя;
|
- создаёт или обновляет пользователя;
|
||||||
- добавляет пользователя в группу `sudo`;
|
- добавляет пользователя в группу `sudo`;
|
||||||
|
- назначает пользователю его домашнюю папку и всё содержимое `.ssh`;
|
||||||
|
- устанавливает права `700` для `.ssh` и `600` для `authorized_keys` и остальных файлов ключей;
|
||||||
- запрещает вход `root` по SSH;
|
- запрещает вход `root` по SSH;
|
||||||
- переносит SSH на выбранный порт и добавляет для него правило UFW;
|
- переносит SSH на выбранный порт и добавляет для него правило UFW;
|
||||||
- настраивает fail2ban;
|
- настраивает fail2ban;
|
||||||
|
|
@ -162,7 +164,7 @@ sudo tail -f /var/log/ochenstarik-ssh-login-telegram.log
|
||||||
|
|
||||||
Файл: `ochenstarik-server-vpn-5.sh`.
|
Файл: `ochenstarik-server-vpn-5.sh`.
|
||||||
|
|
||||||
Скрипт устанавливает Xray и необходимые пакеты, принимает прямую ссылку `vless://` либо HTTPS-ссылку подписки 3x-ui и настраивает маршрутизацию через `nftables`. Поддерживается VLESS с транспортом TCP/RAW и REALITY.
|
Скрипт устанавливает Xray и необходимые пакеты, принимает прямую ссылку `vless://` либо HTTPS-ссылку подписки 3x-ui и настраивает маршрутизацию через `nftables`. Если подписка содержит VLESS-ссылки на нескольких портах, скрипт выводит доступные порты и просит выбрать нужный. Поддерживается VLESS с транспортом TCP/RAW и REALITY.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-vpn-5.sh
|
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-vpn-5.sh
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
Актуальная инструкция находится в README.md:
|
Актуальная инструкция находится в README.md:
|
||||||
https://github.com/ochenstarik-ui/lightweight-server#readme
|
https://github.com/ochenstarik-ui/lightweight-server#readme
|
||||||
|
|
||||||
Можно установить только нужный модуль. Пример для базовой настройки:
|
Можно установить только нужный модуль. Первый скрипт предлагает выбрать часовой пояс, второй — будущий SSH-порт, а пятый — порт VLESS из подписки. Пример для базовой настройки:
|
||||||
|
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y curl
|
sudo apt-get install -y curl
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,34 @@ readonly SWAPPINESS="20"
|
||||||
readonly SYSCTL_FILE="/etc/sysctl.d/60-hermes-swap.conf"
|
readonly SYSCTL_FILE="/etc/sysctl.d/60-hermes-swap.conf"
|
||||||
|
|
||||||
log() { printf '[+] %s\n' "$*"; }
|
log() { printf '[+] %s\n' "$*"; }
|
||||||
|
warn() { printf '[!] %s\n' "$*" >&2; }
|
||||||
die() { printf '[x] %s\n' "$*" >&2; exit 1; }
|
die() { printf '[x] %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
choose_timezone() {
|
||||||
|
local current_timezone timezone
|
||||||
|
|
||||||
|
current_timezone="$(timedatectl show --property=Timezone --value 2>/dev/null || true)"
|
||||||
|
current_timezone="${current_timezone:-Asia/Novosibirsk}"
|
||||||
|
printf 'Select an IANA timezone, for example Asia/Novosibirsk, Europe/Moscow or UTC.\n'
|
||||||
|
while :; do
|
||||||
|
read -rp "Timezone [${current_timezone}]: " timezone
|
||||||
|
timezone="${timezone:-$current_timezone}"
|
||||||
|
if timedatectl list-timezones | grep -Fqx -- "$timezone"; then
|
||||||
|
timedatectl set-timezone "$timezone"
|
||||||
|
log "Timezone set to ${timezone}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
warn "Unknown timezone: $timezone"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
[[ "$EUID" -eq 0 ]] || die "Run this script as root"
|
[[ "$EUID" -eq 0 ]] || die "Run this script as root"
|
||||||
for command_name in awk blkid fallocate mkswap swapon sysctl; do
|
for command_name in awk blkid fallocate grep mkswap swapon sysctl timedatectl; do
|
||||||
command -v "$command_name" >/dev/null 2>&1 || die "Required command not found: $command_name"
|
command -v "$command_name" >/dev/null 2>&1 || die "Required command not found: $command_name"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
choose_timezone
|
||||||
|
|
||||||
log "Configuring ${SWAPSIZE} swap file"
|
log "Configuring ${SWAPSIZE} swap file"
|
||||||
if [[ ! -e "$SWAPFILE" ]]; then
|
if [[ ! -e "$SWAPFILE" ]]; then
|
||||||
fallocate -l "$SWAPSIZE" "$SWAPFILE"
|
fallocate -l "$SWAPSIZE" "$SWAPFILE"
|
||||||
|
|
@ -44,5 +65,6 @@ chmod 644 "$SYSCTL_FILE"
|
||||||
sysctl -p "$SYSCTL_FILE" >/dev/null
|
sysctl -p "$SYSCTL_FILE" >/dev/null
|
||||||
|
|
||||||
log "Server memory setup is complete"
|
log "Server memory setup is complete"
|
||||||
|
printf 'Timezone: %s\n' "$(timedatectl show --property=Timezone --value)"
|
||||||
swapon --show
|
swapon --show
|
||||||
printf '\nNow run ochenstarik-server-2.sh as root.\n'
|
printf '\nNow run ochenstarik-server-2.sh as root.\n'
|
||||||
|
|
@ -1,13 +1,47 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
readonly SSH_PORT="20202"
|
readonly SSH_PORT_CONFIG="/etc/ochenstarik-server/ssh-port.conf"
|
||||||
readonly TIMEZONE="Asia/Novosibirsk"
|
readonly DEFAULT_SSH_PORT="20202"
|
||||||
|
|
||||||
log() { printf '[+] %s\n' "$*"; }
|
log() { printf '[+] %s\n' "$*"; }
|
||||||
|
warn() { printf '[!] %s\n' "$*" >&2; }
|
||||||
die() { printf '[x] %s\n' "$*" >&2; exit 1; }
|
die() { printf '[x] %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
is_valid_port() {
|
||||||
|
local port="$1"
|
||||||
|
[[ "$port" =~ ^[0-9]{1,5}$ ]] || return 1
|
||||||
|
(( 10#$port >= 1 && 10#$port <= 65535 ))
|
||||||
|
}
|
||||||
|
|
||||||
|
choose_ssh_port() {
|
||||||
|
local default_port="$DEFAULT_SSH_PORT" saved_port selected_port
|
||||||
|
|
||||||
|
if [[ -e "$SSH_PORT_CONFIG" ]]; then
|
||||||
|
[[ -f "$SSH_PORT_CONFIG" && ! -L "$SSH_PORT_CONFIG" ]] \
|
||||||
|
|| die "$SSH_PORT_CONFIG must be a regular non-symlink file"
|
||||||
|
saved_port="$(sed -n 's/^SSH_PORT=//p' "$SSH_PORT_CONFIG" | head -n1 | tr -d '\r')"
|
||||||
|
if is_valid_port "$saved_port"; then
|
||||||
|
default_port="$((10#$saved_port))"
|
||||||
|
else
|
||||||
|
warn "Ignoring invalid saved SSH port: ${saved_port:-empty}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
while :; do
|
||||||
|
read -rp "SSH port for step 3 [${default_port}]: " selected_port
|
||||||
|
selected_port="${selected_port:-$default_port}"
|
||||||
|
if is_valid_port "$selected_port"; then
|
||||||
|
SSH_PORT="$((10#$selected_port))"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
warn "SSH port must be a number between 1 and 65535"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
[[ "$EUID" -eq 0 ]] || die "Run this script as root"
|
[[ "$EUID" -eq 0 ]] || die "Run this script as root"
|
||||||
|
SSH_PORT=""
|
||||||
|
choose_ssh_port
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
log "Updating the operating system"
|
log "Updating the operating system"
|
||||||
|
|
@ -23,11 +57,14 @@ apt-get install -y \
|
||||||
ffmpeg mediainfo p7zip-full unzip zip unrar jq yq csvkit sqlite3 \
|
ffmpeg mediainfo p7zip-full unzip zip unrar jq yq csvkit sqlite3 \
|
||||||
python3-pip python3-venv mc
|
python3-pip python3-venv mc
|
||||||
|
|
||||||
log "Setting timezone to ${TIMEZONE}"
|
install -d -m 700 -o root -g root "$(dirname "$SSH_PORT_CONFIG")"
|
||||||
timedatectl set-timezone "$TIMEZONE"
|
printf 'SSH_PORT=%s\n' "$SSH_PORT" > "$SSH_PORT_CONFIG"
|
||||||
|
chown root:root "$SSH_PORT_CONFIG"
|
||||||
|
chmod 600 "$SSH_PORT_CONFIG"
|
||||||
|
log "Saved SSH port ${SSH_PORT} for step 3"
|
||||||
|
|
||||||
log "Configuring UFW"
|
log "Configuring UFW"
|
||||||
# Keep port 22 open until step 3 moves SSH to 20202 and the new login is tested.
|
# Keep port 22 open until step 3 moves SSH to the selected port and the new login is tested.
|
||||||
ufw allow "22/tcp"
|
ufw allow "22/tcp"
|
||||||
ufw allow "${SSH_PORT}/tcp"
|
ufw allow "${SSH_PORT}/tcp"
|
||||||
ufw allow "80/tcp"
|
ufw allow "80/tcp"
|
||||||
|
|
@ -41,5 +78,6 @@ log "Installed versions"
|
||||||
printf 'Timezone: %s\n' "$(timedatectl show --property=Timezone --value)"
|
printf 'Timezone: %s\n' "$(timedatectl show --property=Timezone --value)"
|
||||||
ufw status verbose
|
ufw status verbose
|
||||||
|
|
||||||
printf '\nDone. Now run ochenstarik-server-3.sh as root.\n'
|
printf '\nDone. Now run ochenstarik-server-user-3.sh as root.\n'
|
||||||
|
printf 'Step 3 will move SSH to port %s.\n' "$SSH_PORT"
|
||||||
printf 'Do not remove the UFW rule for port 22 until SSH login on port %s succeeds.\n' "$SSH_PORT"
|
printf 'Do not remove the UFW rule for port 22 until SSH login on port %s succeeds.\n' "$SSH_PORT"
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ readonly ENV_FILE="${ENV_FILE:-/root/setup-data/env.txt}"
|
||||||
readonly HASH_FILE="${HASH_FILE:-/root/setup-data/password.hash}"
|
readonly HASH_FILE="${HASH_FILE:-/root/setup-data/password.hash}"
|
||||||
readonly SSHD_DROPIN="/etc/ssh/sshd_config.d/00-hermes-hardening.conf"
|
readonly SSHD_DROPIN="/etc/ssh/sshd_config.d/00-hermes-hardening.conf"
|
||||||
readonly FAIL2BAN_JAIL="/etc/fail2ban/jail.d/hermes.local"
|
readonly FAIL2BAN_JAIL="/etc/fail2ban/jail.d/hermes.local"
|
||||||
|
readonly SSH_PORT_CONFIG="/etc/ochenstarik-server/ssh-port.conf"
|
||||||
|
|
||||||
log() { printf '[+] %s\n' "$*"; }
|
log() { printf '[+] %s\n' "$*"; }
|
||||||
warn() { printf '[!] %s\n' "$*" >&2; }
|
warn() { printf '[!] %s\n' "$*" >&2; }
|
||||||
|
|
@ -151,7 +152,7 @@ add_ufw_ports_interactive() {
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_user_configuration() {
|
collect_user_configuration() {
|
||||||
local new_username ssh_port default_ssh_port saved_ssh_port
|
local new_username ssh_port default_ssh_port saved_ssh_port selected_step2_port
|
||||||
local ssh_public_key pass pass2 candidate_uid
|
local ssh_public_key pass pass2 candidate_uid
|
||||||
local key_method source_username source_home source_key_file candidate_key key_test
|
local key_method source_username source_home source_key_file candidate_key key_test
|
||||||
|
|
||||||
|
|
@ -174,22 +175,33 @@ collect_user_configuration() {
|
||||||
done
|
done
|
||||||
|
|
||||||
default_ssh_port="20202"
|
default_ssh_port="20202"
|
||||||
if [[ -f "$ENV_FILE" ]]; then
|
if [[ -e "$SSH_PORT_CONFIG" ]]; then
|
||||||
|
[[ -f "$SSH_PORT_CONFIG" && ! -L "$SSH_PORT_CONFIG" ]] \
|
||||||
|
|| die "$SSH_PORT_CONFIG must be a regular non-symlink file"
|
||||||
|
selected_step2_port="$(sed -n 's/^SSH_PORT=//p' "$SSH_PORT_CONFIG" | head -n1 | tr -d '\r')"
|
||||||
|
is_valid_port "$selected_step2_port" \
|
||||||
|
|| die "Invalid SSH port in $SSH_PORT_CONFIG"
|
||||||
|
ssh_port="$((10#$selected_step2_port))"
|
||||||
|
log "Using SSH port ${ssh_port} selected in step 2"
|
||||||
|
elif [[ -f "$ENV_FILE" ]]; then
|
||||||
saved_ssh_port="$(read_env_value SSH_PORT)"
|
saved_ssh_port="$(read_env_value SSH_PORT)"
|
||||||
if is_valid_port "$saved_ssh_port"; then
|
if is_valid_port "$saved_ssh_port"; then
|
||||||
default_ssh_port="$saved_ssh_port"
|
default_ssh_port="$saved_ssh_port"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while :; do
|
if [[ -z "${ssh_port:-}" ]]; then
|
||||||
read -rp "SSH port [${default_ssh_port}]: " ssh_port
|
warn "The SSH port from step 2 was not found; select it now"
|
||||||
ssh_port="${ssh_port:-$default_ssh_port}"
|
while :; do
|
||||||
if is_valid_port "$ssh_port"; then
|
read -rp "SSH port [${default_ssh_port}]: " ssh_port
|
||||||
ssh_port="$((10#$ssh_port))"
|
ssh_port="${ssh_port:-$default_ssh_port}"
|
||||||
break
|
if is_valid_port "$ssh_port"; then
|
||||||
fi
|
ssh_port="$((10#$ssh_port))"
|
||||||
warn "SSH port must be a number between 1 and 65535"
|
break
|
||||||
done
|
fi
|
||||||
|
warn "SSH port must be a number between 1 and 65535"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
while :; do
|
while :; do
|
||||||
printf '\nSSH key setup:\n'
|
printf '\nSSH key setup:\n'
|
||||||
|
|
@ -329,7 +341,7 @@ fi
|
||||||
|
|
||||||
log "Checking packages installed by step 2"
|
log "Checking packages installed by step 2"
|
||||||
|
|
||||||
for command_name in sshd ssh-keygen visudo systemctl; do
|
for command_name in find sshd ssh-keygen visudo systemctl; do
|
||||||
require_command "$command_name"
|
require_command "$command_name"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
@ -370,13 +382,28 @@ user_group="$(id -gn "$NEW_USERNAME")"
|
||||||
# every run so an existing user can be fixed by running this script again.
|
# every run so an existing user can be fixed by running this script again.
|
||||||
chown "$NEW_USERNAME:$user_group" "$user_home"
|
chown "$NEW_USERNAME:$user_group" "$user_home"
|
||||||
chmod 750 "$user_home"
|
chmod 750 "$user_home"
|
||||||
|
[[ ! -L "$user_home/.ssh" ]] || die "Refusing to use a symbolic link as $user_home/.ssh"
|
||||||
install -d -m 700 -o "$NEW_USERNAME" -g "$user_group" "$user_home/.ssh"
|
install -d -m 700 -o "$NEW_USERNAME" -g "$user_group" "$user_home/.ssh"
|
||||||
|
[[ ! -L "$user_home/.ssh/authorized_keys" ]] \
|
||||||
|
|| die "Refusing to use a symbolic link as $user_home/.ssh/authorized_keys"
|
||||||
touch -- "$user_home/.ssh/authorized_keys"
|
touch -- "$user_home/.ssh/authorized_keys"
|
||||||
chown "$NEW_USERNAME:$user_group" "$user_home/.ssh/authorized_keys"
|
|
||||||
chmod 600 "$user_home/.ssh/authorized_keys"
|
|
||||||
if ! grep -Fqx -- "$SSH_PUBLIC_KEY" "$user_home/.ssh/authorized_keys"; then
|
if ! grep -Fqx -- "$SSH_PUBLIC_KEY" "$user_home/.ssh/authorized_keys"; then
|
||||||
printf '%s\n' "$SSH_PUBLIC_KEY" >> "$user_home/.ssh/authorized_keys"
|
printf '%s\n' "$SSH_PUBLIC_KEY" >> "$user_home/.ssh/authorized_keys"
|
||||||
fi
|
fi
|
||||||
|
chown -R --no-dereference "$NEW_USERNAME:$user_group" "$user_home/.ssh"
|
||||||
|
find "$user_home/.ssh" -xdev -type d -exec chmod 700 {} +
|
||||||
|
find "$user_home/.ssh" -xdev -type f -exec chmod 600 {} +
|
||||||
|
|
||||||
|
id -nG "$NEW_USERNAME" | tr ' ' '\n' | grep -Fqx sudo \
|
||||||
|
|| die "$NEW_USERNAME was not added to the sudo group"
|
||||||
|
[[ "$(stat -c '%U:%G' "$user_home/.ssh")" == "$NEW_USERNAME:$user_group" ]] \
|
||||||
|
|| die "Incorrect owner for $user_home/.ssh"
|
||||||
|
[[ "$(stat -c '%a' "$user_home/.ssh")" == 700 ]] \
|
||||||
|
|| die "Incorrect permissions for $user_home/.ssh"
|
||||||
|
[[ "$(stat -c '%U:%G' "$user_home/.ssh/authorized_keys")" == "$NEW_USERNAME:$user_group" ]] \
|
||||||
|
|| die "Incorrect owner for authorized_keys"
|
||||||
|
[[ "$(stat -c '%a' "$user_home/.ssh/authorized_keys")" == 600 ]] \
|
||||||
|
|| die "Incorrect permissions for authorized_keys"
|
||||||
|
|
||||||
if command -v restorecon >/dev/null 2>&1; then
|
if command -v restorecon >/dev/null 2>&1; then
|
||||||
restorecon -RF "$user_home/.ssh" || warn "SELinux context could not be restored"
|
restorecon -RF "$user_home/.ssh" || warn "SELinux context could not be restored"
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,9 @@ printf '\n'
|
||||||
TMP_DIR="$(mktemp -d)"
|
TMP_DIR="$(mktemp -d)"
|
||||||
chmod 700 "$TMP_DIR"
|
chmod 700 "$TMP_DIR"
|
||||||
RAW_FILE="${TMP_DIR}/subscription.txt"
|
RAW_FILE="${TMP_DIR}/subscription.txt"
|
||||||
|
LINKS_FILE="${TMP_DIR}/vless-links.txt"
|
||||||
LINK_FILE="${TMP_DIR}/vless-link.txt"
|
LINK_FILE="${TMP_DIR}/vless-link.txt"
|
||||||
|
PORTS_FILE="${TMP_DIR}/vless-ports.txt"
|
||||||
GENERATED_CONFIG="${TMP_DIR}/config.json"
|
GENERATED_CONFIG="${TMP_DIR}/config.json"
|
||||||
INSTALLER_FILE="${TMP_DIR}/install-release.sh"
|
INSTALLER_FILE="${TMP_DIR}/install-release.sh"
|
||||||
|
|
||||||
|
|
@ -98,36 +100,93 @@ fi
|
||||||
unset SUBSCRIPTION_INPUT
|
unset SUBSCRIPTION_INPUT
|
||||||
chmod 600 "$RAW_FILE"
|
chmod 600 "$RAW_FILE"
|
||||||
|
|
||||||
python3 - "$RAW_FILE" "$LINK_FILE" <<'PY'
|
python3 - "$RAW_FILE" "$LINKS_FILE" "$PORTS_FILE" <<'PY'
|
||||||
import base64
|
import base64
|
||||||
import pathlib
|
import pathlib
|
||||||
import sys
|
import sys
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
raw = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8").strip()
|
raw = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8").strip()
|
||||||
|
|
||||||
def find_vless(text: str):
|
def find_vless(text: str):
|
||||||
|
result = []
|
||||||
for line in text.replace("\r", "").split("\n"):
|
for line in text.replace("\r", "").split("\n"):
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line.startswith("vless://"):
|
if line.startswith("vless://"):
|
||||||
return line
|
result.append(line)
|
||||||
return None
|
return result
|
||||||
|
|
||||||
link = find_vless(raw)
|
links = find_vless(raw)
|
||||||
if not link:
|
if not links:
|
||||||
try:
|
try:
|
||||||
padded = raw.replace("-", "+").replace("_", "/")
|
padded = raw.replace("-", "+").replace("_", "/")
|
||||||
padded += "=" * (-len(padded) % 4)
|
padded += "=" * (-len(padded) % 4)
|
||||||
decoded = base64.b64decode(padded, validate=False).decode("utf-8")
|
decoded = base64.b64decode(padded, validate=False).decode("utf-8")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise SystemExit(f"Подписка не является Base64/VLESS: {exc}")
|
raise SystemExit(f"Подписка не является Base64/VLESS: {exc}")
|
||||||
link = find_vless(decoded)
|
links = find_vless(decoded)
|
||||||
|
|
||||||
if not link:
|
links = list(dict.fromkeys(links))
|
||||||
|
if not links:
|
||||||
raise SystemExit("В подписке не найдена ссылка vless://")
|
raise SystemExit("В подписке не найдена ссылка vless://")
|
||||||
|
|
||||||
pathlib.Path(sys.argv[2]).write_text(link, encoding="utf-8")
|
ports = []
|
||||||
|
for link in links:
|
||||||
|
try:
|
||||||
|
port = urllib.parse.urlsplit(link).port
|
||||||
|
except ValueError as exc:
|
||||||
|
raise SystemExit(f"Некорректный порт в VLESS-ссылке: {exc}")
|
||||||
|
if port is None:
|
||||||
|
raise SystemExit("В одной из VLESS-ссылок отсутствует порт")
|
||||||
|
if port not in ports:
|
||||||
|
ports.append(port)
|
||||||
|
|
||||||
|
pathlib.Path(sys.argv[2]).write_text("\n".join(links) + "\n", encoding="utf-8")
|
||||||
|
pathlib.Path(sys.argv[3]).write_text(
|
||||||
|
"\n".join(str(port) for port in ports) + "\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
PY
|
||||||
|
chmod 600 "$LINKS_FILE" "$PORTS_FILE"
|
||||||
|
|
||||||
|
mapfile -t SUBSCRIPTION_PORTS < "$PORTS_FILE"
|
||||||
|
(( ${#SUBSCRIPTION_PORTS[@]} > 0 )) || die "В подписке не найдены доступные порты"
|
||||||
|
printf 'Доступные порты в подписке: %s\n' "${SUBSCRIPTION_PORTS[*]}"
|
||||||
|
|
||||||
|
while :; do
|
||||||
|
read -rp "Выберите порт подписки [${SUBSCRIPTION_PORTS[0]}]: " SUBSCRIPTION_PORT
|
||||||
|
SUBSCRIPTION_PORT="${SUBSCRIPTION_PORT:-${SUBSCRIPTION_PORTS[0]}}"
|
||||||
|
[[ "$SUBSCRIPTION_PORT" =~ ^[0-9]{1,5}$ ]] \
|
||||||
|
&& (( 10#$SUBSCRIPTION_PORT >= 1 && 10#$SUBSCRIPTION_PORT <= 65535 )) \
|
||||||
|
|| { warn "Порт должен быть числом от 1 до 65535"; continue; }
|
||||||
|
SUBSCRIPTION_PORT="$((10#$SUBSCRIPTION_PORT))"
|
||||||
|
|
||||||
|
port_found="no"
|
||||||
|
for available_port in "${SUBSCRIPTION_PORTS[@]}"; do
|
||||||
|
if [[ "$SUBSCRIPTION_PORT" == "$available_port" ]]; then
|
||||||
|
port_found="yes"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[[ "$port_found" == yes ]] && break
|
||||||
|
warn "Порт ${SUBSCRIPTION_PORT} отсутствует в подписке"
|
||||||
|
done
|
||||||
|
|
||||||
|
python3 - "$LINKS_FILE" "$LINK_FILE" "$SUBSCRIPTION_PORT" <<'PY'
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
links = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8").splitlines()
|
||||||
|
selected_port = int(sys.argv[3])
|
||||||
|
for link in links:
|
||||||
|
if urllib.parse.urlsplit(link).port == selected_port:
|
||||||
|
pathlib.Path(sys.argv[2]).write_text(link, encoding="utf-8")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise SystemExit(f"VLESS-ссылка для порта {selected_port} не найдена")
|
||||||
PY
|
PY
|
||||||
chmod 600 "$LINK_FILE"
|
chmod 600 "$LINK_FILE"
|
||||||
|
log "Выбран порт подписки ${SUBSCRIPTION_PORT}"
|
||||||
|
|
||||||
VPN_IP="$(python3 - "$LINK_FILE" "$GENERATED_CONFIG" "$TPROXY_PORT" "$SOCKS_PORT" <<'PY'
|
VPN_IP="$(python3 - "$LINK_FILE" "$GENERATED_CONFIG" "$TPROXY_PORT" "$SOCKS_PORT" <<'PY'
|
||||||
import json
|
import json
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue