Add language and program selection to step one
This commit is contained in:
parent
a6f53d9ad5
commit
1a98b40789
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
|
- name: Check every installer
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
for script in ./*.sh; do
|
while IFS= read -r -d '' script; do
|
||||||
echo "Checking ${script}"
|
echo "Checking ${script}"
|
||||||
bash -n "${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-install.sh` | Единый пошаговый мастер: установить, пропустить или завершить на каждом этапе | Да; рекомендуемый способ полной настройки |
|
||||||
| `ochenstarik-server-1.sh` | Предлагает выбрать часовой пояс и русификацию терминала, создаёт swap-файл 2 ГБ и задаёт `vm.swappiness=20` | Да |
|
| `ochenstarik-server-1.sh` | Предлагает выбрать часовой пояс, язык терминала и наборы программ, создаёт swap-файл 2 ГБ | Да |
|
||||||
| `ochenstarik-server-2.sh` | Предлагает выбрать будущий SSH-порт и режим IPv4/IPv6, обновляет систему, ставит пакеты и настраивает UFW | Да |
|
| `ochenstarik-server-2.sh` | Предлагает будущий SSH-порт и IPv4/IPv6, обновляет систему, ставит обязательные пакеты и настраивает UFW | Да |
|
||||||
| `ochenstarik-server-user-3.sh` | Создаёт администратора, переносит SSH на порт из этапа 2, проверяет права и включает fail2ban | После установки необходимых пакетов; проще всего сначала запустить скрипт 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 | Да; зависимости устанавливаются автоматически |
|
||||||
|
|
@ -67,13 +67,27 @@ chmod 700 ./*.sh
|
||||||
bash -n ./ИМЯ-СКРИПТА.sh
|
bash -n ./ИМЯ-СКРИПТА.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## 1. Часовой пояс и настройка swap
|
## 1. Часовой пояс, язык, программы и swap
|
||||||
|
|
||||||
Файл: `ochenstarik-server-1.sh`.
|
Файл: `ochenstarik-server-1.sh`.
|
||||||
|
|
||||||
Сначала показывает нумерованное меню популярных часовых поясов, включая `Asia/Novosibirsk`, `Europe/Moscow` и `UTC`. Нужную зону достаточно выбрать по номеру. Для остальных зон предусмотрен полный список с последовательным выбором региона и часового пояса — вводить имя вручную не требуется.
|
Сначала показывает нумерованное меню популярных часовых поясов, включая `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`. Повторный запуск предусмотрен.
|
Затем скрипт создаёт `/swapfile` размером 2 ГБ, добавляет его в `/etc/fstab` и устанавливает `vm.swappiness=20`. Повторный запуск предусмотрен.
|
||||||
|
|
||||||
|
|
@ -95,7 +109,7 @@ sysctl vm.swappiness
|
||||||
|
|
||||||
Файл: `ochenstarik-server-2.sh`.
|
Файл: `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-сессия, сохраняется, чтобы не потерять доступ.
|
Скрипт удаляет управляемые правила UFW, настройки IPv4/IPv6, SSH drop-in, fail2ban-конфигурацию, Telegram-hook, Xray, 3x-ui, WARP, расписания резервного копирования, swap и служебные файлы проекта. Перед изменением SSH открывается порт `22/tcp`. Правило порта, через который работает текущая SSH-сессия, сохраняется, чтобы не потерять доступ.
|
||||||
|
|
||||||
Удаление администратора и самих архивов резервных копий требует отдельных подтверждений. Общие системные пакеты, обновления Ubuntu и программы вроде OpenSSH, UFW и `curl` не удаляются: они могли существовать до запуска проекта и необходимы серверу. Пакеты русификации удаляются только если первый скрипт зафиксировал, что до русификации их не было.
|
Удаление администратора и самих архивов резервных копий требует отдельных подтверждений. Обязательные системные пакеты второго этапа и обновления Ubuntu сохраняются, чтобы не потерять доступ к серверу. Языковые пакеты и дополнительные программы удаляются только если первый скрипт зафиксировал, что до его запуска их не было.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-uninstall.sh
|
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 SWAPPINESS="20"
|
||||||
readonly SYSCTL_FILE="/etc/sysctl.d/60-hermes-swap.conf"
|
readonly SYSCTL_FILE="/etc/sysctl.d/60-hermes-swap.conf"
|
||||||
readonly CONFIG_DIR="/etc/ochenstarik-server"
|
readonly CONFIG_DIR="/etc/ochenstarik-server"
|
||||||
readonly LOCALE_BACKUP="${CONFIG_DIR}/locale-before-russian.conf"
|
readonly LOCALE_BACKUP="${CONFIG_DIR}/locale-before-selection.conf"
|
||||||
readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent"
|
readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-selection.absent"
|
||||||
readonly LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list"
|
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' "$*"; }
|
log() { printf '[+] %s\n' "$*"; }
|
||||||
warn() { printf '[!] %s\n' "$*" >&2; }
|
warn() { printf '[!] %s\n' "$*" >&2; }
|
||||||
die() { printf '[x] %s\n' "$*" >&2; exit 1; }
|
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() {
|
package_is_installed() {
|
||||||
dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -Fqx 'install ok installed'
|
dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -Fqx 'install ok installed'
|
||||||
}
|
}
|
||||||
|
|
||||||
install_russian_terminal_locale() {
|
ensure_package_tools() {
|
||||||
local package_name
|
local command_name
|
||||||
local -a packages=(locales) newly_installed=()
|
for command_name in apt-get apt-cache dpkg-query; do
|
||||||
|
command -v "$command_name" >/dev/null 2>&1 \
|
||||||
ask_yes_no "Установить русскую локаль терминала ru_RU.UTF-8?" no || {
|
|| die "Required command not found: $command_name"
|
||||||
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"
|
|
||||||
done
|
done
|
||||||
|
}
|
||||||
|
|
||||||
log "Обновление списка пакетов для русификации"
|
ensure_apt_updated() {
|
||||||
|
[[ "$APT_UPDATED" == no ]] || return 0
|
||||||
|
ensure_package_tools
|
||||||
|
log "Обновление списка пакетов"
|
||||||
apt-get update
|
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"
|
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 [[ ! -e "$LOCALE_BACKUP" && ! -e "$LOCALE_ABSENT_MARKER" ]]; then
|
||||||
if [[ -f /etc/default/locale && ! -L /etc/default/locale ]]; then
|
if [[ -f /etc/default/locale && ! -L /etc/default/locale ]]; then
|
||||||
cp -a -- /etc/default/locale "$LOCALE_BACKUP"
|
cp -a -- /etc/default/locale "$LOCALE_BACKUP"
|
||||||
|
|
@ -62,31 +125,156 @@ install_russian_terminal_locale() {
|
||||||
chmod 600 "$LOCALE_ABSENT_MARKER"
|
chmod 600 "$LOCALE_ABSENT_MARKER"
|
||||||
fi
|
fi
|
||||||
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
|
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
|
fi
|
||||||
done
|
done
|
||||||
for package_name in "${packages[@]}"; do
|
|
||||||
package_is_installed "$package_name" || newly_installed+=("$package_name")
|
|
||||||
done
|
|
||||||
|
|
||||||
log "Установка пакетов русской локали"
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
apt-get install -y "${packages[@]}"
|
((${#available_packages[@]} == 0)) || apt-get install -y "${available_packages[@]}"
|
||||||
locale-gen ru_RU.UTF-8
|
locale-gen "$selected_locale"
|
||||||
update-locale LANG=ru_RU.UTF-8 LANGUAGE=ru_RU:ru LC_MESSAGES=ru_RU.UTF-8
|
update-locale LANG="$selected_locale" LANGUAGE="$selected_language" \
|
||||||
|
LC_MESSAGES="$selected_locale"
|
||||||
|
record_new_packages "$LOCALE_PACKAGES" "${newly_installed[@]}"
|
||||||
|
|
||||||
touch "$LOCALE_PACKAGES"
|
log "Выбрана локаль ${selected_locale}. Она применится при следующем входе"
|
||||||
for package_name in "${newly_installed[@]}"; do
|
}
|
||||||
grep -Fqx -- "$package_name" "$LOCALE_PACKAGES" \
|
|
||||||
|| printf '%s\n' "$package_name" >> "$LOCALE_PACKAGES"
|
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
|
done
|
||||||
chown root:root "$LOCALE_PACKAGES"
|
printf ' 0) Не устанавливать дополнительные программы\n'
|
||||||
chmod 600 "$LOCALE_PACKAGES"
|
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() {
|
choose_numbered_option() {
|
||||||
|
|
@ -165,7 +353,8 @@ for command_name in awk blkid fallocate grep mkswap swapon sysctl timedatectl; d
|
||||||
done
|
done
|
||||||
|
|
||||||
choose_timezone
|
choose_timezone
|
||||||
install_russian_terminal_locale
|
choose_terminal_language
|
||||||
|
choose_and_install_programs
|
||||||
|
|
||||||
log "Configuring ${SWAPSIZE} swap file"
|
log "Configuring ${SWAPSIZE} swap file"
|
||||||
if [[ ! -e "$SWAPFILE" ]]; then
|
if [[ ! -e "$SWAPFILE" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -326,14 +326,9 @@ if [[ "$ACTION" == install ]]; then
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get upgrade -y
|
apt-get upgrade -y
|
||||||
|
|
||||||
log "Устанавливаю серверные пакеты и средства обработки документов"
|
log "Устанавливаю обязательные пакеты SSH, брандмауэра и защиты"
|
||||||
apt-get install -y \
|
apt-get install -y \
|
||||||
sudo ufw fail2ban curl ca-certificates openssl openssh-server logrotate iproute2 \
|
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
|
|
||||||
|
|
||||||
for command_name in ip sed sysctl ufw; do
|
for command_name in ip sed sysctl ufw; do
|
||||||
require_command "$command_name"
|
require_command "$command_name"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ declare -a STEP_FILES=(
|
||||||
)
|
)
|
||||||
|
|
||||||
declare -a STEP_TITLES=(
|
declare -a STEP_TITLES=(
|
||||||
"Часовой пояс, русификация терминала и swap"
|
"Часовой пояс, язык терминала, набор программ и swap"
|
||||||
"Базовые пакеты, будущий SSH-порт, IPv4/IPv6 и UFW"
|
"Базовые пакеты, будущий SSH-порт, IPv4/IPv6 и UFW"
|
||||||
"Администратор, перенос SSH и fail2ban"
|
"Администратор, перенос SSH и fail2ban"
|
||||||
"Telegram-уведомления о входах по SSH"
|
"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 XRAY_SERVICE="/etc/systemd/system/ochenstarik-xray-routing.service"
|
||||||
readonly BACKUP_CONFIG="${CONFIG_DIR}/backup.conf"
|
readonly BACKUP_CONFIG="${CONFIG_DIR}/backup.conf"
|
||||||
readonly BACKUP_RUNNER="/usr/local/sbin/ochenstarik-server-backup"
|
readonly BACKUP_RUNNER="/usr/local/sbin/ochenstarik-server-backup"
|
||||||
readonly LOCALE_BACKUP="${CONFIG_DIR}/locale-before-russian.conf"
|
readonly LOCALE_BACKUP="${CONFIG_DIR}/locale-before-selection.conf"
|
||||||
readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent"
|
readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-selection.absent"
|
||||||
readonly LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list"
|
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' "$*"; }
|
log() { printf '[+] %s\n' "$*"; }
|
||||||
warn() { printf '[!] %s\n' "$*" >&2; }
|
warn() { printf '[!] %s\n' "$*" >&2; }
|
||||||
|
|
@ -263,26 +267,52 @@ remove_backup_automation() {
|
||||||
}
|
}
|
||||||
|
|
||||||
restore_locale() {
|
restore_locale() {
|
||||||
local package_name
|
local package_name package_file
|
||||||
local -a packages=()
|
local -a packages=()
|
||||||
if [[ -f "$LOCALE_BACKUP" && ! -L "$LOCALE_BACKUP" ]]; then
|
if [[ -f "$LOCALE_BACKUP" && ! -L "$LOCALE_BACKUP" ]]; then
|
||||||
cp -a -- "$LOCALE_BACKUP" /etc/default/locale
|
cp -a -- "$LOCALE_BACKUP" /etc/default/locale
|
||||||
log "Предыдущая системная локаль восстановлена"
|
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
|
elif [[ -f "$LOCALE_ABSENT_MARKER" && ! -L "$LOCALE_ABSENT_MARKER" ]]; then
|
||||||
rm -f -- /etc/default/locale
|
rm -f -- /etc/default/locale
|
||||||
log "Созданный скриптом файл локали удалён"
|
log "Созданный скриптом файл локали удалён"
|
||||||
|
elif [[ -f "$LEGACY_LOCALE_ABSENT_MARKER" \
|
||||||
|
&& ! -L "$LEGACY_LOCALE_ABSENT_MARKER" ]]; then
|
||||||
|
rm -f -- /etc/default/locale
|
||||||
|
log "Созданный скриптом файл локали удалён"
|
||||||
fi
|
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
|
while IFS= read -r package_name || [[ -n "$package_name" ]]; do
|
||||||
[[ "$package_name" =~ ^[a-z0-9][a-z0-9+.-]*$ ]] && packages+=("$package_name")
|
[[ "$package_name" =~ ^[a-z0-9][a-z0-9+.-]*$ ]] \
|
||||||
done < "$LOCALE_PACKAGES"
|
&& [[ ! " ${packages[*]} " =~ " ${package_name} " ]] \
|
||||||
if ((${#packages[@]} > 0)) && ask_yes_no \
|
&& packages+=("$package_name")
|
||||||
"Удалить пакеты русификации, которые ранее отсутствовали?" yes; then
|
done < "$package_file"
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get purge -y "${packages[@]}"
|
done
|
||||||
fi
|
if ((${#packages[@]} > 0)) && ask_yes_no \
|
||||||
|
"Удалить языковые пакеты, которые ранее отсутствовали?" yes; then
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get purge -y "${packages[@]}"
|
||||||
fi
|
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() {
|
remove_swap() {
|
||||||
|
|
@ -307,9 +337,9 @@ remove_swap() {
|
||||||
cat <<'WARNING'
|
cat <<'WARNING'
|
||||||
|
|
||||||
Этот скрипт удалит настройки, службы и данные, созданные проектом lightweight-server.
|
Этот скрипт удалит настройки, службы и данные, созданные проектом lightweight-server.
|
||||||
Он не откатывает обновления Ubuntu и не удаляет общие системные пакеты (OpenSSH,
|
Он не откатывает обновления Ubuntu и не удаляет обязательные общие пакеты второго
|
||||||
UFW, curl, мультимедийные библиотеки и другие), потому что они могли существовать
|
этапа (OpenSSH, UFW и другие), потому что они нужны для доступа к серверу. Пакеты,
|
||||||
до установки и нужны для доступа к серверу. Их наличие не мешает установке заново.
|
которые первый этап зафиксировал как новые, можно удалить отдельным подтверждением.
|
||||||
|
|
||||||
Сначала будет открыт SSH-порт 22. Не закрывайте текущую сессию, пока не проверите
|
Сначала будет открыт SSH-порт 22. Не закрывайте текущую сессию, пока не проверите
|
||||||
новый вход после очистки.
|
новый вход после очистки.
|
||||||
|
|
@ -326,6 +356,7 @@ remove_xray
|
||||||
remove_panel_and_warp
|
remove_panel_and_warp
|
||||||
remove_backup_automation
|
remove_backup_automation
|
||||||
restore_locale
|
restore_locale
|
||||||
|
remove_step1_programs
|
||||||
remove_swap
|
remove_swap
|
||||||
|
|
||||||
rm -f -- "$SSH_PORT_CONFIG" "$IP_FAMILY_CONFIG" "$MANAGED_PORTS_CONFIG"
|
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