Add language and program selection to step one (#4)

This commit is contained in:
ochenstarik-ui 2026-07-15 13:41:59 +07:00 committed by GitHub
parent a6f53d9ad5
commit 0cca8d9408
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 370 additions and 81 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -15,7 +15,7 @@ declare -a STEP_FILES=(
)
declare -a STEP_TITLES=(
"Часовой пояс, русификация терминала и swap"
"Часовой пояс, язык терминала, набор программ и swap"
"Базовые пакеты, будущий SSH-порт, IPv4/IPv6 и UFW"
"Администратор, перенос SSH и fail2ban"
"Telegram-уведомления о входах по SSH"

View file

@ -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"

View 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'