Add language and program selection to step one (#4)
This commit is contained in:
parent
a6f53d9ad5
commit
0cca8d9408
7 changed files with 370 additions and 81 deletions
7
.github/workflows/bash-syntax.yml
vendored
7
.github/workflows/bash-syntax.yml
vendored
|
|
@ -18,7 +18,10 @@ jobs:
|
|||
- name: Check every installer
|
||||
run: |
|
||||
set -e
|
||||
for script in ./*.sh; do
|
||||
while IFS= read -r -d '' script; do
|
||||
echo "Checking ${script}"
|
||||
bash -n "${script}"
|
||||
done
|
||||
done < <(find . -type f -name '*.sh' -print0)
|
||||
|
||||
- name: Test step 1 selections
|
||||
run: bash tests/test-step1-selection.sh
|
||||
|
|
|
|||
26
README.md
26
README.md
|
|
@ -7,8 +7,8 @@
|
|||
| Файл | Назначение | Можно запускать отдельно |
|
||||
| --- | --- | --- |
|
||||
| `ochenstarik-server-install.sh` | Единый пошаговый мастер: установить, пропустить или завершить на каждом этапе | Да; рекомендуемый способ полной настройки |
|
||||
| `ochenstarik-server-1.sh` | Предлагает выбрать часовой пояс и русификацию терминала, создаёт swap-файл 2 ГБ и задаёт `vm.swappiness=20` | Да |
|
||||
| `ochenstarik-server-2.sh` | Предлагает выбрать будущий SSH-порт и режим IPv4/IPv6, обновляет систему, ставит пакеты и настраивает UFW | Да |
|
||||
| `ochenstarik-server-1.sh` | Предлагает выбрать часовой пояс, язык терминала и наборы программ, создаёт swap-файл 2 ГБ | Да |
|
||||
| `ochenstarik-server-2.sh` | Предлагает будущий SSH-порт и IPv4/IPv6, обновляет систему, ставит обязательные пакеты и настраивает UFW | Да |
|
||||
| `ochenstarik-server-user-3.sh` | Создаёт администратора, переносит SSH на порт из этапа 2, проверяет права и включает fail2ban | После установки необходимых пакетов; проще всего сначала запустить скрипт 2 |
|
||||
| `ochenstarik-server-tg-4.sh` | Отправляет уведомления в Telegram при успешном входе по SSH | После установки OpenSSH и `curl`; обычно после скрипта 2 или 3 |
|
||||
| `ochenstarik-server-vpn-5.sh` | Устанавливает Xray и направляет системный трафик через VLESS + REALITY | Да; зависимости устанавливаются автоматически |
|
||||
|
|
@ -67,13 +67,27 @@ chmod 700 ./*.sh
|
|||
bash -n ./ИМЯ-СКРИПТА.sh
|
||||
```
|
||||
|
||||
## 1. Часовой пояс и настройка swap
|
||||
## 1. Часовой пояс, язык, программы и swap
|
||||
|
||||
Файл: `ochenstarik-server-1.sh`.
|
||||
|
||||
Сначала показывает нумерованное меню популярных часовых поясов, включая `Asia/Novosibirsk`, `Europe/Moscow` и `UTC`. Нужную зону достаточно выбрать по номеру. Для остальных зон предусмотрен полный список с последовательным выбором региона и часового пояса — вводить имя вручную не требуется.
|
||||
|
||||
После выбора времени скрипт спрашивает, нужна ли русификация терминала. При согласии он устанавливает доступные пакеты русской локали, создаёт `ru_RU.UTF-8` и задаёт русский язык для новых терминальных сессий. Прежний `/etc/default/locale` и список только что установленных языковых пакетов сохраняются в `/etc/ochenstarik-server`, поэтому отдельный скрипт очистки может вернуть исходную локаль. Без явного согласия языковые настройки не меняются.
|
||||
После выбора времени скрипт показывает меню языков терминала: оставить текущий, русский, английский, немецкий, французский, испанский, итальянский, португальский, польский, украинский, турецкий, китайский, японский, корейский либо выбрать другую локаль из полного системного списка. Выбранный язык применяется к новым терминальным сессиям. Исходный `/etc/default/locale` сохраняется для последующего восстановления.
|
||||
|
||||
Затем можно отметить несколько наборов программ номерами через пробел, например `1 2 4 9`. Пустой ввод устанавливает все наборы, а `0` пропускает дополнительные программы:
|
||||
|
||||
1. Терминал и повседневная работа — Git, wget/curl, jq/yq, ripgrep, fd, fzf, lnav, редакторы, MC, tmux/screen, htop/btop, ncdu, rsync и strace.
|
||||
2. Диагностика сети — DNS, ping, MTR, traceroute, tcpdump, nmap, netcat, HTTPie, whois, ethtool и socat.
|
||||
3. Мониторинг системы и дисков — iotop, sysstat, SMART, lm-sensors, atop, nethogs, vnStat и psmisc.
|
||||
4. Безопасность и обслуживание — auditd, AppArmor tools, Lynis, needrestart, unattended-upgrades, debsums, ACL и extended attributes.
|
||||
5. Разработка и автоматизация — компилятор, ShellCheck, Python pip/venv и pipx.
|
||||
6. Архивы и данные — 7-Zip, ZIP/RAR, SQLite, csvkit, клиенты PostgreSQL и Redis.
|
||||
7. Документы, PDF и OCR — Poppler, qpdf, Ghostscript, OCRmyPDF/Tesseract, LibreOffice, Pandoc, antiword и catdoc.
|
||||
8. Изображения, аудио и видео — ImageMagick, ExifTool, WebP, FFmpeg и MediaInfo.
|
||||
9. Резервное копирование — restic, BorgBackup и rclone.
|
||||
|
||||
Скрипт проверяет доступность каждого пакета в подключённых репозиториях. Недоступные для конкретной версии или набора репозиториев пакеты пропускаются с предупреждением. Список программ, которых не было до запуска, сохраняется в `/etc/ochenstarik-server/step1-installed-packages.list` для безопасной очистки.
|
||||
|
||||
Затем скрипт создаёт `/swapfile` размером 2 ГБ, добавляет его в `/etc/fstab` и устанавливает `vm.swappiness=20`. Повторный запуск предусмотрен.
|
||||
|
||||
|
|
@ -95,7 +109,7 @@ sysctl vm.swappiness
|
|||
|
||||
Файл: `ochenstarik-server-2.sh`.
|
||||
|
||||
При каждом запуске скрипт предлагает три действия: полную установку или обновление, изменение режима IP без переустановки пакетов либо добавление открытых портов. При полной установке он запрашивает SSH-порт для этапа 3, обновляет систему и устанавливает OpenSSH, UFW, fail2ban, инструменты обработки документов, изображений, аудио и видео.
|
||||
При каждом запуске скрипт предлагает три действия: полную установку или обновление, изменение режима IP без переустановки пакетов либо добавление открытых портов. При полной установке он запрашивает SSH-порт для этапа 3, обновляет систему и устанавливает только обязательные компоненты: OpenSSH, UFW, fail2ban, `sudo`, TLS-сертификаты и сетевые инструменты. Дополнительные программы выбираются на этапе 1.
|
||||
|
||||
Для входящих подключений можно выбрать:
|
||||
|
||||
|
|
@ -344,7 +358,7 @@ VPN, 3x-ui, WARP, Telegram-уведомления и автоматически
|
|||
|
||||
Скрипт удаляет управляемые правила UFW, настройки IPv4/IPv6, SSH drop-in, fail2ban-конфигурацию, Telegram-hook, Xray, 3x-ui, WARP, расписания резервного копирования, swap и служебные файлы проекта. Перед изменением SSH открывается порт `22/tcp`. Правило порта, через который работает текущая SSH-сессия, сохраняется, чтобы не потерять доступ.
|
||||
|
||||
Удаление администратора и самих архивов резервных копий требует отдельных подтверждений. Общие системные пакеты, обновления Ubuntu и программы вроде OpenSSH, UFW и `curl` не удаляются: они могли существовать до запуска проекта и необходимы серверу. Пакеты русификации удаляются только если первый скрипт зафиксировал, что до русификации их не было.
|
||||
Удаление администратора и самих архивов резервных копий требует отдельных подтверждений. Обязательные системные пакеты второго этапа и обновления Ubuntu сохраняются, чтобы не потерять доступ к серверу. Языковые пакеты и дополнительные программы удаляются только если первый скрипт зафиксировал, что до его запуска их не было.
|
||||
|
||||
```bash
|
||||
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-uninstall.sh
|
||||
|
|
|
|||
|
|
@ -6,53 +6,116 @@ readonly SWAPSIZE="2G"
|
|||
readonly SWAPPINESS="20"
|
||||
readonly SYSCTL_FILE="/etc/sysctl.d/60-hermes-swap.conf"
|
||||
readonly CONFIG_DIR="/etc/ochenstarik-server"
|
||||
readonly LOCALE_BACKUP="${CONFIG_DIR}/locale-before-russian.conf"
|
||||
readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent"
|
||||
readonly LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list"
|
||||
readonly LOCALE_BACKUP="${CONFIG_DIR}/locale-before-selection.conf"
|
||||
readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-selection.absent"
|
||||
readonly LOCALE_PACKAGES="${CONFIG_DIR}/locale-installed-packages.list"
|
||||
readonly LEGACY_LOCALE_BACKUP="${CONFIG_DIR}/locale-before-russian.conf"
|
||||
readonly LEGACY_LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent"
|
||||
readonly LEGACY_LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list"
|
||||
readonly PROGRAM_PACKAGES="${CONFIG_DIR}/step1-installed-packages.list"
|
||||
|
||||
readonly -a PROGRAM_GROUP_TITLES=(
|
||||
"Терминал и повседневная работа"
|
||||
"Диагностика сети"
|
||||
"Мониторинг системы и дисков"
|
||||
"Безопасность и обслуживание Ubuntu"
|
||||
"Разработка и автоматизация"
|
||||
"Архивы, таблицы и локальные данные"
|
||||
"Документы, PDF и OCR"
|
||||
"Изображения, аудио и видео"
|
||||
"Резервное копирование и синхронизация"
|
||||
)
|
||||
|
||||
readonly -a PROGRAM_GROUP_DESCRIPTIONS=(
|
||||
"git, wget, curl, jq/yq, ripgrep/fd/fzf, lnav, редакторы, mc, tmux, htop/btop, ncdu, rsync"
|
||||
"DNS/ping/MTR/traceroute, tcpdump, nmap, netcat, HTTPie, whois, ethtool и socat"
|
||||
"iotop, sysstat, SMART, датчики, atop, nethogs, vnStat и инструменты процессов"
|
||||
"auditd, AppArmor tools, Lynis, needrestart, автоматические обновления, debsums, ACL/xattr"
|
||||
"компилятор, ShellCheck, Python pip/venv и pipx"
|
||||
"7-Zip/ZIP/RAR, SQLite, csvkit, клиенты PostgreSQL и Redis"
|
||||
"Poppler, qpdf, Ghostscript, OCRmyPDF/Tesseract, LibreOffice, Pandoc, antiword/catdoc"
|
||||
"ImageMagick, ExifTool, WebP, FFmpeg и MediaInfo"
|
||||
"restic, BorgBackup и rclone"
|
||||
)
|
||||
|
||||
readonly -a PROGRAM_GROUP_PACKAGES=(
|
||||
"git wget curl ca-certificates jq yq ripgrep fd-find fzf lnav tree less nano vim mc tmux screen htop btop ncdu lsof strace rsync"
|
||||
"dnsutils iputils-ping mtr-tiny traceroute tcpdump nmap netcat-openbsd httpie whois ethtool socat"
|
||||
"iotop sysstat smartmontools lm-sensors atop nethogs vnstat psmisc"
|
||||
"auditd apparmor-utils lynis needrestart unattended-upgrades debsums acl attr"
|
||||
"build-essential shellcheck python3-pip python3-venv pipx"
|
||||
"p7zip-full unzip zip unrar sqlite3 csvkit postgresql-client redis-tools"
|
||||
"poppler-utils qpdf ghostscript ocrmypdf tesseract-ocr tesseract-ocr-eng tesseract-ocr-rus libreoffice pandoc antiword catdoc"
|
||||
"imagemagick libimage-exiftool-perl webp ffmpeg mediainfo"
|
||||
"restic borgbackup rclone"
|
||||
)
|
||||
|
||||
APT_UPDATED=no
|
||||
|
||||
log() { printf '[+] %s\n' "$*"; }
|
||||
warn() { printf '[!] %s\n' "$*" >&2; }
|
||||
die() { printf '[x] %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
ask_yes_no() {
|
||||
local prompt="$1" default_answer="${2:-no}" answer suffix
|
||||
[[ "$default_answer" == yes || "$default_answer" == no ]] \
|
||||
|| die "Invalid default answer: $default_answer"
|
||||
[[ "$default_answer" == yes ]] && suffix='[Y/n]' || suffix='[y/N]'
|
||||
|
||||
while :; do
|
||||
read -rp "$prompt $suffix " answer || die "Input was interrupted"
|
||||
answer="${answer:-$default_answer}"
|
||||
case "${answer,,}" in
|
||||
y|yes|д|да) return 0 ;;
|
||||
n|no|н|нет) return 1 ;;
|
||||
*) warn "Ответьте yes/no или да/нет" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
package_is_installed() {
|
||||
dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -Fqx 'install ok installed'
|
||||
}
|
||||
|
||||
install_russian_terminal_locale() {
|
||||
local package_name
|
||||
local -a packages=(locales) newly_installed=()
|
||||
|
||||
ask_yes_no "Установить русскую локаль терминала ru_RU.UTF-8?" no || {
|
||||
log "Русификация терминала пропущена"
|
||||
return 0
|
||||
}
|
||||
|
||||
for package_name in apt-get apt-cache dpkg-query; do
|
||||
command -v "$package_name" >/dev/null 2>&1 \
|
||||
|| die "Required command not found: $package_name"
|
||||
ensure_package_tools() {
|
||||
local command_name
|
||||
for command_name in apt-get apt-cache dpkg-query; do
|
||||
command -v "$command_name" >/dev/null 2>&1 \
|
||||
|| die "Required command not found: $command_name"
|
||||
done
|
||||
}
|
||||
|
||||
log "Обновление списка пакетов для русификации"
|
||||
ensure_apt_updated() {
|
||||
[[ "$APT_UPDATED" == no ]] || return 0
|
||||
ensure_package_tools
|
||||
log "Обновление списка пакетов"
|
||||
apt-get update
|
||||
APT_UPDATED=yes
|
||||
}
|
||||
|
||||
record_new_packages() {
|
||||
local destination="$1" package_name
|
||||
shift
|
||||
install -d -m 700 -o root -g root "$CONFIG_DIR"
|
||||
[[ ! -L "$destination" ]] || die "Отказ от записи через ссылку: $destination"
|
||||
touch "$destination"
|
||||
for package_name in "$@"; do
|
||||
grep -Fqx -- "$package_name" "$destination" \
|
||||
|| printf '%s\n' "$package_name" >> "$destination"
|
||||
done
|
||||
chown root:root "$destination"
|
||||
chmod 600 "$destination"
|
||||
}
|
||||
|
||||
migrate_legacy_locale_state() {
|
||||
local package_name
|
||||
install -d -m 700 -o root -g root "$CONFIG_DIR"
|
||||
[[ ! -L "$LOCALE_BACKUP" && ! -L "$LOCALE_ABSENT_MARKER" \
|
||||
&& ! -L "$LOCALE_PACKAGES" ]] || die "Файл состояния локали не должен быть ссылкой"
|
||||
if [[ -f "$LEGACY_LOCALE_BACKUP" && ! -L "$LEGACY_LOCALE_BACKUP" \
|
||||
&& ! -e "$LOCALE_BACKUP" ]]; then
|
||||
mv -- "$LEGACY_LOCALE_BACKUP" "$LOCALE_BACKUP"
|
||||
fi
|
||||
if [[ -f "$LEGACY_LOCALE_ABSENT_MARKER" \
|
||||
&& ! -L "$LEGACY_LOCALE_ABSENT_MARKER" && ! -e "$LOCALE_ABSENT_MARKER" ]]; then
|
||||
mv -- "$LEGACY_LOCALE_ABSENT_MARKER" "$LOCALE_ABSENT_MARKER"
|
||||
fi
|
||||
if [[ -f "$LEGACY_LOCALE_PACKAGES" && ! -L "$LEGACY_LOCALE_PACKAGES" ]]; then
|
||||
touch "$LOCALE_PACKAGES"
|
||||
while IFS= read -r package_name || [[ -n "$package_name" ]]; do
|
||||
[[ -z "$package_name" ]] || grep -Fqx -- "$package_name" "$LOCALE_PACKAGES" \
|
||||
|| printf '%s\n' "$package_name" >> "$LOCALE_PACKAGES"
|
||||
done < "$LEGACY_LOCALE_PACKAGES"
|
||||
rm -f -- "$LEGACY_LOCALE_PACKAGES"
|
||||
fi
|
||||
}
|
||||
|
||||
save_original_locale() {
|
||||
migrate_legacy_locale_state
|
||||
|
||||
if [[ ! -e "$LOCALE_BACKUP" && ! -e "$LOCALE_ABSENT_MARKER" ]]; then
|
||||
if [[ -f /etc/default/locale && ! -L /etc/default/locale ]]; then
|
||||
cp -a -- /etc/default/locale "$LOCALE_BACKUP"
|
||||
|
|
@ -62,31 +125,156 @@ install_russian_terminal_locale() {
|
|||
chmod 600 "$LOCALE_ABSENT_MARKER"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
for package_name in language-pack-ru manpages-ru; do
|
||||
choose_terminal_language() {
|
||||
local answer selected_locale selected_language primary_language package_name index
|
||||
local current_locale="${LANG:-не задана}"
|
||||
local -a titles=(
|
||||
"Не менять язык терминала"
|
||||
"Русский — ru_RU.UTF-8"
|
||||
"English (US) — en_US.UTF-8"
|
||||
"Deutsch — de_DE.UTF-8"
|
||||
"Français — fr_FR.UTF-8"
|
||||
"Español — es_ES.UTF-8"
|
||||
"Italiano — it_IT.UTF-8"
|
||||
"Português (Brasil) — pt_BR.UTF-8"
|
||||
"Polski — pl_PL.UTF-8"
|
||||
"Українська — uk_UA.UTF-8"
|
||||
"Türkçe — tr_TR.UTF-8"
|
||||
"简体中文 — zh_CN.UTF-8"
|
||||
"日本語 — ja_JP.UTF-8"
|
||||
"한국어 — ko_KR.UTF-8"
|
||||
"Другая локаль из полного списка"
|
||||
)
|
||||
local -a locales=(
|
||||
"" ru_RU.UTF-8 en_US.UTF-8 de_DE.UTF-8 fr_FR.UTF-8 es_ES.UTF-8
|
||||
it_IT.UTF-8 pt_BR.UTF-8 pl_PL.UTF-8 uk_UA.UTF-8 tr_TR.UTF-8
|
||||
zh_CN.UTF-8 ja_JP.UTF-8 ko_KR.UTF-8 ""
|
||||
)
|
||||
local -a language_values=(
|
||||
"" ru_RU:ru en_US:en de_DE:de fr_FR:fr es_ES:es it_IT:it
|
||||
pt_BR:pt pl_PL:pl uk_UA:uk tr_TR:tr zh_CN:zh ja_JP:ja ko_KR:ko ""
|
||||
)
|
||||
local -a language_packages=(
|
||||
"" "language-pack-ru manpages-ru" language-pack-en language-pack-de
|
||||
language-pack-fr language-pack-es language-pack-it language-pack-pt
|
||||
language-pack-pl language-pack-uk language-pack-tr language-pack-zh-hans
|
||||
language-pack-ja language-pack-ko ""
|
||||
)
|
||||
local -a supported_locales packages=(locales) available_packages=() \
|
||||
newly_installed=() extra_packages=()
|
||||
|
||||
while :; do
|
||||
printf '\nВыберите язык новых терминальных сессий (сейчас: %s):\n' "$current_locale"
|
||||
for index in "${!titles[@]}"; do
|
||||
printf ' %d) %s\n' "$((index + 1))" "${titles[index]}"
|
||||
done
|
||||
read -rp 'Номер языка [1 — не менять]: ' answer || die "Ввод прерван"
|
||||
answer="${answer:-1}"
|
||||
[[ "$answer" =~ ^[0-9]+$ ]] && ((answer >= 1 && answer <= ${#titles[@]})) && break
|
||||
warn "Введите номер от 1 до ${#titles[@]}"
|
||||
done
|
||||
|
||||
((answer == 1)) && { log "Язык терминала не изменён"; return 0; }
|
||||
ensure_apt_updated
|
||||
save_original_locale
|
||||
|
||||
if ((answer == ${#titles[@]})); then
|
||||
package_is_installed locales || newly_installed+=(locales)
|
||||
apt-get install -y locales
|
||||
mapfile -t supported_locales < <(awk '{print $1}' /usr/share/i18n/SUPPORTED | sort -u)
|
||||
choose_numbered_option "Выберите локаль из полного списка:" selected_locale \
|
||||
"${supported_locales[@]}"
|
||||
primary_language="${selected_locale%%_*}"
|
||||
selected_language="${selected_locale%%.*}:${primary_language}"
|
||||
packages+=("language-pack-${primary_language}")
|
||||
else
|
||||
selected_locale="${locales[answer - 1]}"
|
||||
selected_language="${language_values[answer - 1]}"
|
||||
read -r -a extra_packages <<< "${language_packages[answer - 1]}"
|
||||
packages+=("${extra_packages[@]}")
|
||||
fi
|
||||
|
||||
for package_name in "${packages[@]}"; do
|
||||
[[ -n "$package_name" ]] || continue
|
||||
if apt-cache show "$package_name" >/dev/null 2>&1; then
|
||||
packages+=("$package_name")
|
||||
available_packages+=("$package_name")
|
||||
package_is_installed "$package_name" || newly_installed+=("$package_name")
|
||||
else
|
||||
warn "Языковой пакет недоступен и будет пропущен: $package_name"
|
||||
fi
|
||||
done
|
||||
for package_name in "${packages[@]}"; do
|
||||
package_is_installed "$package_name" || newly_installed+=("$package_name")
|
||||
done
|
||||
|
||||
log "Установка пакетов русской локали"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get install -y "${packages[@]}"
|
||||
locale-gen ru_RU.UTF-8
|
||||
update-locale LANG=ru_RU.UTF-8 LANGUAGE=ru_RU:ru LC_MESSAGES=ru_RU.UTF-8
|
||||
((${#available_packages[@]} == 0)) || apt-get install -y "${available_packages[@]}"
|
||||
locale-gen "$selected_locale"
|
||||
update-locale LANG="$selected_locale" LANGUAGE="$selected_language" \
|
||||
LC_MESSAGES="$selected_locale"
|
||||
record_new_packages "$LOCALE_PACKAGES" "${newly_installed[@]}"
|
||||
|
||||
touch "$LOCALE_PACKAGES"
|
||||
for package_name in "${newly_installed[@]}"; do
|
||||
grep -Fqx -- "$package_name" "$LOCALE_PACKAGES" \
|
||||
|| printf '%s\n' "$package_name" >> "$LOCALE_PACKAGES"
|
||||
log "Выбрана локаль ${selected_locale}. Она применится при следующем входе"
|
||||
}
|
||||
|
||||
choose_and_install_programs() {
|
||||
local answer token group_index package_name
|
||||
local -a selected_groups=() group_packages=() requested=() available=() newly_installed=()
|
||||
local -A selected=() seen=()
|
||||
|
||||
printf '\nВыберите наборы программ для установки:\n'
|
||||
for group_index in "${!PROGRAM_GROUP_TITLES[@]}"; do
|
||||
printf ' %d) %s\n %s\n' "$((group_index + 1))" \
|
||||
"${PROGRAM_GROUP_TITLES[group_index]}" "${PROGRAM_GROUP_DESCRIPTIONS[group_index]}"
|
||||
done
|
||||
chown root:root "$LOCALE_PACKAGES"
|
||||
chmod 600 "$LOCALE_PACKAGES"
|
||||
printf ' 0) Не устанавливать дополнительные программы\n'
|
||||
printf '\nВведите номера через пробел, например: 1 2 4 9.\n'
|
||||
read -rp 'Выбор [Enter — установить все наборы]: ' answer || die "Ввод прерван"
|
||||
answer="${answer//,/ }"
|
||||
|
||||
log "Русская локаль включена. Она применится при следующем входе в терминал"
|
||||
if [[ -z "${answer//[[:space:]]/}" ]]; then
|
||||
for group_index in "${!PROGRAM_GROUP_TITLES[@]}"; do
|
||||
selected_groups+=("$group_index")
|
||||
done
|
||||
elif [[ "$answer" =~ ^[[:space:]]*0[[:space:]]*$ ]]; then
|
||||
log "Установка дополнительных программ пропущена"
|
||||
return 0
|
||||
else
|
||||
for token in $answer; do
|
||||
[[ "$token" =~ ^[0-9]+$ ]] \
|
||||
|| die "Некорректный номер набора: $token"
|
||||
((token >= 1 && token <= ${#PROGRAM_GROUP_TITLES[@]})) \
|
||||
|| die "Номер набора вне диапазона: $token"
|
||||
group_index="$((token - 1))"
|
||||
[[ -n "${selected[$group_index]:-}" ]] || selected_groups+=("$group_index")
|
||||
selected[$group_index]=yes
|
||||
done
|
||||
fi
|
||||
|
||||
ensure_apt_updated
|
||||
for group_index in "${selected_groups[@]}"; do
|
||||
log "Выбран набор: ${PROGRAM_GROUP_TITLES[group_index]}"
|
||||
read -r -a group_packages <<< "${PROGRAM_GROUP_PACKAGES[group_index]}"
|
||||
for package_name in "${group_packages[@]}"; do
|
||||
[[ -n "${seen[$package_name]:-}" ]] && continue
|
||||
seen[$package_name]=yes
|
||||
requested+=("$package_name")
|
||||
done
|
||||
done
|
||||
|
||||
for package_name in "${requested[@]}"; do
|
||||
if apt-cache show "$package_name" >/dev/null 2>&1; then
|
||||
available+=("$package_name")
|
||||
package_is_installed "$package_name" || newly_installed+=("$package_name")
|
||||
else
|
||||
warn "Пакет отсутствует в подключённых репозиториях и пропущен: $package_name"
|
||||
fi
|
||||
done
|
||||
|
||||
((${#available[@]} > 0)) || die "Ни один выбранный пакет не найден"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get install -y "${available[@]}"
|
||||
record_new_packages "$PROGRAM_PACKAGES" "${newly_installed[@]}"
|
||||
log "Выбранные программы установлены"
|
||||
}
|
||||
|
||||
choose_numbered_option() {
|
||||
|
|
@ -165,7 +353,8 @@ for command_name in awk blkid fallocate grep mkswap swapon sysctl timedatectl; d
|
|||
done
|
||||
|
||||
choose_timezone
|
||||
install_russian_terminal_locale
|
||||
choose_terminal_language
|
||||
choose_and_install_programs
|
||||
|
||||
log "Configuring ${SWAPSIZE} swap file"
|
||||
if [[ ! -e "$SWAPFILE" ]]; then
|
||||
|
|
|
|||
|
|
@ -326,14 +326,9 @@ if [[ "$ACTION" == install ]]; then
|
|||
apt-get update
|
||||
apt-get upgrade -y
|
||||
|
||||
log "Устанавливаю серверные пакеты и средства обработки документов"
|
||||
log "Устанавливаю обязательные пакеты SSH, брандмауэра и защиты"
|
||||
apt-get install -y \
|
||||
sudo ufw fail2ban curl ca-certificates openssl openssh-server logrotate iproute2 \
|
||||
poppler-utils qpdf ghostscript ocrmypdf \
|
||||
tesseract-ocr tesseract-ocr-eng tesseract-ocr-rus \
|
||||
libreoffice pandoc antiword catdoc imagemagick libimage-exiftool-perl webp \
|
||||
ffmpeg mediainfo p7zip-full unzip zip unrar jq yq csvkit sqlite3 \
|
||||
python3-pip python3-venv mc
|
||||
sudo ufw fail2ban curl ca-certificates openssl openssh-server logrotate iproute2
|
||||
|
||||
for command_name in ip sed sysctl ufw; do
|
||||
require_command "$command_name"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ declare -a STEP_FILES=(
|
|||
)
|
||||
|
||||
declare -a STEP_TITLES=(
|
||||
"Часовой пояс, русификация терминала и swap"
|
||||
"Часовой пояс, язык терминала, набор программ и swap"
|
||||
"Базовые пакеты, будущий SSH-порт, IPv4/IPv6 и UFW"
|
||||
"Администратор, перенос SSH и fail2ban"
|
||||
"Telegram-уведомления о входах по SSH"
|
||||
|
|
|
|||
|
|
@ -26,9 +26,13 @@ readonly XRAY_HELPER="/usr/local/sbin/ochenstarik-xray-routing"
|
|||
readonly XRAY_SERVICE="/etc/systemd/system/ochenstarik-xray-routing.service"
|
||||
readonly BACKUP_CONFIG="${CONFIG_DIR}/backup.conf"
|
||||
readonly BACKUP_RUNNER="/usr/local/sbin/ochenstarik-server-backup"
|
||||
readonly LOCALE_BACKUP="${CONFIG_DIR}/locale-before-russian.conf"
|
||||
readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent"
|
||||
readonly LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list"
|
||||
readonly LOCALE_BACKUP="${CONFIG_DIR}/locale-before-selection.conf"
|
||||
readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-selection.absent"
|
||||
readonly LOCALE_PACKAGES="${CONFIG_DIR}/locale-installed-packages.list"
|
||||
readonly LEGACY_LOCALE_BACKUP="${CONFIG_DIR}/locale-before-russian.conf"
|
||||
readonly LEGACY_LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent"
|
||||
readonly LEGACY_LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list"
|
||||
readonly PROGRAM_PACKAGES="${CONFIG_DIR}/step1-installed-packages.list"
|
||||
|
||||
log() { printf '[+] %s\n' "$*"; }
|
||||
warn() { printf '[!] %s\n' "$*" >&2; }
|
||||
|
|
@ -263,26 +267,52 @@ remove_backup_automation() {
|
|||
}
|
||||
|
||||
restore_locale() {
|
||||
local package_name
|
||||
local package_name package_file
|
||||
local -a packages=()
|
||||
if [[ -f "$LOCALE_BACKUP" && ! -L "$LOCALE_BACKUP" ]]; then
|
||||
cp -a -- "$LOCALE_BACKUP" /etc/default/locale
|
||||
log "Предыдущая системная локаль восстановлена"
|
||||
elif [[ -f "$LEGACY_LOCALE_BACKUP" && ! -L "$LEGACY_LOCALE_BACKUP" ]]; then
|
||||
cp -a -- "$LEGACY_LOCALE_BACKUP" /etc/default/locale
|
||||
log "Предыдущая системная локаль восстановлена"
|
||||
elif [[ -f "$LOCALE_ABSENT_MARKER" && ! -L "$LOCALE_ABSENT_MARKER" ]]; then
|
||||
rm -f -- /etc/default/locale
|
||||
log "Созданный скриптом файл локали удалён"
|
||||
elif [[ -f "$LEGACY_LOCALE_ABSENT_MARKER" \
|
||||
&& ! -L "$LEGACY_LOCALE_ABSENT_MARKER" ]]; then
|
||||
rm -f -- /etc/default/locale
|
||||
log "Созданный скриптом файл локали удалён"
|
||||
fi
|
||||
|
||||
if [[ -f "$LOCALE_PACKAGES" && ! -L "$LOCALE_PACKAGES" ]]; then
|
||||
for package_file in "$LOCALE_PACKAGES" "$LEGACY_LOCALE_PACKAGES"; do
|
||||
[[ -f "$package_file" && ! -L "$package_file" ]] || continue
|
||||
while IFS= read -r package_name || [[ -n "$package_name" ]]; do
|
||||
[[ "$package_name" =~ ^[a-z0-9][a-z0-9+.-]*$ ]] && packages+=("$package_name")
|
||||
done < "$LOCALE_PACKAGES"
|
||||
if ((${#packages[@]} > 0)) && ask_yes_no \
|
||||
"Удалить пакеты русификации, которые ранее отсутствовали?" yes; then
|
||||
DEBIAN_FRONTEND=noninteractive apt-get purge -y "${packages[@]}"
|
||||
fi
|
||||
[[ "$package_name" =~ ^[a-z0-9][a-z0-9+.-]*$ ]] \
|
||||
&& [[ ! " ${packages[*]} " =~ " ${package_name} " ]] \
|
||||
&& packages+=("$package_name")
|
||||
done < "$package_file"
|
||||
done
|
||||
if ((${#packages[@]} > 0)) && ask_yes_no \
|
||||
"Удалить языковые пакеты, которые ранее отсутствовали?" yes; then
|
||||
DEBIAN_FRONTEND=noninteractive apt-get purge -y "${packages[@]}"
|
||||
fi
|
||||
rm -f -- "$LOCALE_BACKUP" "$LOCALE_ABSENT_MARKER" "$LOCALE_PACKAGES"
|
||||
rm -f -- "$LOCALE_BACKUP" "$LOCALE_ABSENT_MARKER" "$LOCALE_PACKAGES" \
|
||||
"$LEGACY_LOCALE_BACKUP" "$LEGACY_LOCALE_ABSENT_MARKER" \
|
||||
"$LEGACY_LOCALE_PACKAGES"
|
||||
}
|
||||
|
||||
remove_step1_programs() {
|
||||
local package_name
|
||||
local -a packages=()
|
||||
[[ -f "$PROGRAM_PACKAGES" && ! -L "$PROGRAM_PACKAGES" ]] || return 0
|
||||
while IFS= read -r package_name || [[ -n "$package_name" ]]; do
|
||||
[[ "$package_name" =~ ^[a-z0-9][a-z0-9+.-]*$ ]] && packages+=("$package_name")
|
||||
done < "$PROGRAM_PACKAGES"
|
||||
if ((${#packages[@]} > 0)) && ask_yes_no \
|
||||
"Удалить дополнительные программы, установленные первым этапом?" yes; then
|
||||
DEBIAN_FRONTEND=noninteractive apt-get purge -y "${packages[@]}"
|
||||
fi
|
||||
rm -f -- "$PROGRAM_PACKAGES"
|
||||
}
|
||||
|
||||
remove_swap() {
|
||||
|
|
@ -307,9 +337,9 @@ remove_swap() {
|
|||
cat <<'WARNING'
|
||||
|
||||
Этот скрипт удалит настройки, службы и данные, созданные проектом lightweight-server.
|
||||
Он не откатывает обновления Ubuntu и не удаляет общие системные пакеты (OpenSSH,
|
||||
UFW, curl, мультимедийные библиотеки и другие), потому что они могли существовать
|
||||
до установки и нужны для доступа к серверу. Их наличие не мешает установке заново.
|
||||
Он не откатывает обновления Ubuntu и не удаляет обязательные общие пакеты второго
|
||||
этапа (OpenSSH, UFW и другие), потому что они нужны для доступа к серверу. Пакеты,
|
||||
которые первый этап зафиксировал как новые, можно удалить отдельным подтверждением.
|
||||
|
||||
Сначала будет открыт SSH-порт 22. Не закрывайте текущую сессию, пока не проверите
|
||||
новый вход после очистки.
|
||||
|
|
@ -326,6 +356,7 @@ remove_xray
|
|||
remove_panel_and_warp
|
||||
remove_backup_automation
|
||||
restore_locale
|
||||
remove_step1_programs
|
||||
remove_swap
|
||||
|
||||
rm -f -- "$SSH_PORT_CONFIG" "$IP_FAMILY_CONFIG" "$MANAGED_PORTS_CONFIG"
|
||||
|
|
|
|||
57
tests/test-step1-selection.sh
Normal file
57
tests/test-step1-selection.sh
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
|
||||
source <(awk '/^\[\[ "\$EUID"/{exit} {print}' ochenstarik-server-1.sh)
|
||||
|
||||
declare -a MOCK_APT_PACKAGES=()
|
||||
MOCK_LOCALE=""
|
||||
MOCK_UPDATE_LOCALE=""
|
||||
|
||||
ensure_apt_updated() { :; }
|
||||
apt-cache() { return 0; }
|
||||
package_is_installed() { return 1; }
|
||||
record_new_packages() { :; }
|
||||
save_original_locale() { :; }
|
||||
|
||||
apt-get() {
|
||||
[[ "${1:-}" == install ]] || return 0
|
||||
shift
|
||||
[[ "${1:-}" != -y ]] || shift
|
||||
MOCK_APT_PACKAGES=("$@")
|
||||
}
|
||||
|
||||
locale-gen() { MOCK_LOCALE="$1"; }
|
||||
update-locale() { MOCK_UPDATE_LOCALE="$*"; }
|
||||
|
||||
contains_package() {
|
||||
local expected="$1" package_name
|
||||
for package_name in "${MOCK_APT_PACKAGES[@]}"; do
|
||||
[[ "$package_name" != "$expected" ]] || return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
choose_and_install_programs <<< '1 3' >/dev/null
|
||||
contains_package git
|
||||
contains_package iotop
|
||||
! contains_package nmap
|
||||
! contains_package ffmpeg
|
||||
|
||||
MOCK_APT_PACKAGES=()
|
||||
choose_and_install_programs <<< '' >/dev/null
|
||||
contains_package nmap
|
||||
contains_package ffmpeg
|
||||
contains_package restic
|
||||
contains_package postgresql-client
|
||||
|
||||
MOCK_APT_PACKAGES=()
|
||||
choose_and_install_programs <<< '0' >/dev/null
|
||||
((${#MOCK_APT_PACKAGES[@]} == 0))
|
||||
|
||||
choose_terminal_language <<< '2' >/dev/null
|
||||
[[ "$MOCK_LOCALE" == ru_RU.UTF-8 ]]
|
||||
[[ "$MOCK_UPDATE_LOCALE" == *'LANG=ru_RU.UTF-8'* ]]
|
||||
[[ "$MOCK_UPDATE_LOCALE" == *'LANGUAGE=ru_RU:ru'* ]]
|
||||
|
||||
printf 'Step 1 selection tests passed.\n'
|
||||
Loading…
Reference in a new issue