lightweight-server/ochenstarik-server-1.sh
2026-07-15 14:54:41 +07:00

683 lines
39 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -Eeuo pipefail
readonly SWAPFILE="/swapfile"
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-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"
declare -a PROGRAM_GROUP_TITLES=(
"Terminal and everyday tools"
"Network diagnostics"
"System and disk monitoring"
"Ubuntu security and maintenance"
"Development and automation"
"Archives, tables, and local data"
"Documents, PDF, and OCR"
"Images, audio, and video"
"Backup and synchronization"
)
declare -a PROGRAM_GROUP_DESCRIPTIONS=(
"git, wget, curl, jq/yq, ripgrep/fd/fzf, lnav, editors, mc, tmux, htop/btop, ncdu, rsync"
"DNS/ping/MTR/traceroute, tcpdump, nmap, netcat, HTTPie, whois, ethtool, socat"
"iotop, sysstat, SMART, sensors, atop, nethogs, vnStat, process tools"
"auditd, AppArmor tools, Lynis, needrestart, automatic updates, debsums, ACL/xattr"
"compiler, ShellCheck, Python pip/venv, pipx"
"7-Zip/ZIP/RAR, SQLite, csvkit, PostgreSQL and Redis clients"
"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
UI_LANG="${OCHENSTARIK_UI_LANG:-en}"
case "$UI_LANG" in
en|ru|es|de|fr|pt|zh|ja|ar|hi) ;;
*) UI_LANG=en ;;
esac
declare -A UI_TEXT=()
load_ui_text() {
UI_TEXT=(
[input_interrupted]="Input was interrupted"
[invalid_number]="Enter a number from %s to %s"
[no_options]="No options are available for: %s"
[select_option]="Enter the option number: "
[select_region]="Select a region:"
[select_timezone]="Select a timezone in %s:"
[timezone_heading]="Select the server timezone (current: %s):"
[other_timezone]="Other timezone (select by region)"
[unknown_timezone]="Unknown timezone: %s"
[timezone_set]="Timezone set to %s"
[terminal_heading]="Select the language for new terminal sessions (current: %s):"
[terminal_keep]="Keep the current terminal language"
[terminal_other]="Other locale from the full list"
[terminal_prompt]="Language number [1 — keep current]: "
[terminal_unchanged]="Terminal language was not changed"
[terminal_full_list]="Select a locale from the full list:"
[locale_package_missing]="Language package is unavailable and will be skipped: %s"
[locale_selected]="Locale %s selected. It will apply at the next login"
[program_heading]="Select program groups to install:"
[program_none]="Do not install additional programs"
[program_example]="Enter numbers separated by spaces, for example: 1 2 4 9."
[program_prompt]="Selection [Enter — install all groups]: "
[program_skipped]="Installation of additional programs was skipped"
[program_bad_number]="Invalid group number: %s"
[program_out_of_range]="Group number is out of range: %s"
[program_selected]="Selected group: %s"
[package_missing]="Package is unavailable in the configured repositories and will be skipped: %s"
[no_packages]="None of the selected packages were found"
[programs_installed]="Selected programs have been installed"
[apt_update]="Updating the package list"
[root_required]="Run this script as root"
[required_missing]="Required command not found: %s"
[swap_configuring]="Configuring %s swap file"
[setup_complete]="Server memory setup is complete"
[next_script]="Now run ochenstarik-server-2.sh as root."
)
case "$UI_LANG" in
ru) UI_TEXT+=(
[input_interrupted]="Ввод прерван" [invalid_number]="Введите номер от %s до %s"
[select_option]="Введите номер варианта: " [select_region]="Выберите регион:"
[select_timezone]="Выберите часовой пояс в регионе %s:"
[timezone_heading]="Выберите часовой пояс сервера (сейчас: %s):"
[other_timezone]="Другой часовой пояс (выбор по региону)"
[unknown_timezone]="Неизвестный часовой пояс: %s" [timezone_set]="Установлен часовой пояс %s"
[terminal_heading]="Выберите язык новых терминальных сессий (сейчас: %s):"
[terminal_keep]="Не менять язык терминала" [terminal_other]="Другая локаль из полного списка"
[terminal_prompt]="Номер языка [1 — не менять]: " [terminal_unchanged]="Язык терминала не изменён"
[terminal_full_list]="Выберите локаль из полного списка:"
[locale_package_missing]="Языковой пакет недоступен и будет пропущен: %s"
[locale_selected]="Выбрана локаль %s. Она применится при следующем входе"
[program_heading]="Выберите наборы программ для установки:"
[program_none]="Не устанавливать дополнительные программы"
[program_example]="Введите номера через пробел, например: 1 2 4 9."
[program_prompt]="Выбор [Enter — установить все наборы]: "
[program_skipped]="Установка дополнительных программ пропущена"
[program_bad_number]="Некорректный номер набора: %s"
[program_out_of_range]="Номер набора вне диапазона: %s"
[program_selected]="Выбран набор: %s"
[package_missing]="Пакет отсутствует в подключённых репозиториях и будет пропущен: %s"
[no_packages]="Ни один выбранный пакет не найден"
[programs_installed]="Выбранные программы установлены" [apt_update]="Обновление списка пакетов"
[root_required]="Запустите этот скрипт от root"
[required_missing]="Не найдена обязательная команда: %s"
[swap_configuring]="Настройка файла подкачки размером %s"
[setup_complete]="Настройка памяти сервера завершена"
[next_script]="Теперь запустите ochenstarik-server-2.sh от root."
) ;;
es) UI_TEXT+=(
[input_interrupted]="Entrada interrumpida" [invalid_number]="Introduzca un número del %s al %s"
[select_option]="Introduzca el número de opción: " [select_region]="Seleccione una región:"
[select_timezone]="Seleccione una zona horaria en %s:"
[timezone_heading]="Seleccione la zona horaria del servidor (actual: %s):"
[other_timezone]="Otra zona horaria (seleccionar por región)" [timezone_set]="Zona horaria configurada: %s"
[terminal_heading]="Seleccione el idioma para nuevas sesiones de terminal (actual: %s):"
[terminal_keep]="Mantener el idioma actual" [terminal_other]="Otra configuración regional de la lista completa"
[terminal_prompt]="Número de idioma [1 — mantener]: " [terminal_unchanged]="El idioma del terminal no se cambió"
[terminal_full_list]="Seleccione una configuración regional de la lista completa:"
[program_heading]="Seleccione los grupos de programas que desea instalar:"
[program_none]="No instalar programas adicionales"
[program_example]="Introduzca números separados por espacios, por ejemplo: 1 2 4 9."
[program_prompt]="Selección [Enter — instalar todos]: " [program_skipped]="Se omitió la instalación de programas adicionales"
[program_bad_number]="Número de grupo no válido: %s" [program_out_of_range]="Número de grupo fuera de rango: %s"
[program_selected]="Grupo seleccionado: %s" [programs_installed]="Los programas seleccionados se instalaron"
[apt_update]="Actualizando la lista de paquetes" [root_required]="Ejecute este script como root"
[swap_configuring]="Configurando un archivo swap de %s" [setup_complete]="Configuración de memoria completada"
[next_script]="Ahora ejecute ochenstarik-server-2.sh como root."
) ;;
de) UI_TEXT+=(
[input_interrupted]="Eingabe wurde unterbrochen" [invalid_number]="Geben Sie eine Zahl von %s bis %s ein"
[select_option]="Optionsnummer eingeben: " [select_region]="Region auswählen:"
[select_timezone]="Zeitzone in %s auswählen:"
[timezone_heading]="Server-Zeitzone auswählen (aktuell: %s):"
[other_timezone]="Andere Zeitzone (nach Region auswählen)" [timezone_set]="Zeitzone auf %s gesetzt"
[terminal_heading]="Sprache für neue Terminalsitzungen auswählen (aktuell: %s):"
[terminal_keep]="Aktuelle Terminalsprache beibehalten" [terminal_other]="Andere Locale aus der vollständigen Liste"
[terminal_prompt]="Sprachnummer [1 — beibehalten]: " [terminal_unchanged]="Terminalsprache wurde nicht geändert"
[terminal_full_list]="Locale aus der vollständigen Liste auswählen:"
[program_heading]="Zu installierende Programmgruppen auswählen:"
[program_none]="Keine zusätzlichen Programme installieren"
[program_example]="Nummern durch Leerzeichen getrennt eingeben, zum Beispiel: 1 2 4 9."
[program_prompt]="Auswahl [Enter — alle installieren]: " [program_skipped]="Installation zusätzlicher Programme übersprungen"
[program_bad_number]="Ungültige Gruppennummer: %s" [program_out_of_range]="Gruppennummer außerhalb des Bereichs: %s"
[program_selected]="Ausgewählte Gruppe: %s" [programs_installed]="Ausgewählte Programme wurden installiert"
[apt_update]="Paketliste wird aktualisiert" [root_required]="Führen Sie dieses Skript als root aus"
[swap_configuring]="%s-Swap-Datei wird eingerichtet" [setup_complete]="Speichereinrichtung abgeschlossen"
[next_script]="Führen Sie jetzt ochenstarik-server-2.sh als root aus."
) ;;
fr) UI_TEXT+=(
[input_interrupted]="Saisie interrompue" [invalid_number]="Saisissez un nombre de %s à %s"
[select_option]="Saisissez le numéro de loption : " [select_region]="Sélectionnez une région :"
[select_timezone]="Sélectionnez un fuseau horaire dans %s :"
[timezone_heading]="Sélectionnez le fuseau horaire du serveur (actuel : %s) :"
[other_timezone]="Autre fuseau horaire (sélection par région)" [timezone_set]="Fuseau horaire défini sur %s"
[terminal_heading]="Sélectionnez la langue des nouvelles sessions (actuelle : %s) :"
[terminal_keep]="Conserver la langue actuelle" [terminal_other]="Autre locale de la liste complète"
[terminal_prompt]="Numéro de langue [1 — conserver] : " [terminal_unchanged]="La langue du terminal na pas été modifiée"
[terminal_full_list]="Sélectionnez une locale dans la liste complète :"
[program_heading]="Sélectionnez les groupes de programmes à installer :"
[program_none]="Ne pas installer de programmes supplémentaires"
[program_example]="Saisissez les numéros séparés par des espaces, par exemple : 1 2 4 9."
[program_prompt]="Sélection [Entrée — tout installer] : " [program_skipped]="Installation des programmes supplémentaires ignorée"
[program_bad_number]="Numéro de groupe incorrect : %s" [program_out_of_range]="Numéro de groupe hors plage : %s"
[program_selected]="Groupe sélectionné : %s" [programs_installed]="Les programmes sélectionnés ont été installés"
[apt_update]="Mise à jour de la liste des paquets" [root_required]="Exécutez ce script en tant que root"
[swap_configuring]="Configuration dun fichier swap de %s" [setup_complete]="Configuration de la mémoire terminée"
[next_script]="Exécutez maintenant ochenstarik-server-2.sh en tant que root."
) ;;
pt) UI_TEXT+=(
[input_interrupted]="Entrada interrompida" [invalid_number]="Digite um número de %s a %s"
[select_option]="Digite o número da opção: " [select_region]="Selecione uma região:"
[select_timezone]="Selecione um fuso horário em %s:"
[timezone_heading]="Selecione o fuso horário do servidor (atual: %s):"
[other_timezone]="Outro fuso horário (selecionar por região)" [timezone_set]="Fuso horário definido como %s"
[terminal_heading]="Selecione o idioma das novas sessões de terminal (atual: %s):"
[terminal_keep]="Manter o idioma atual" [terminal_other]="Outra localidade da lista completa"
[terminal_prompt]="Número do idioma [1 — manter]: " [terminal_unchanged]="O idioma do terminal não foi alterado"
[terminal_full_list]="Selecione uma localidade na lista completa:"
[program_heading]="Selecione os grupos de programas para instalar:"
[program_none]="Não instalar programas adicionais"
[program_example]="Digite números separados por espaços, por exemplo: 1 2 4 9."
[program_prompt]="Seleção [Enter — instalar todos]: " [program_skipped]="Instalação de programas adicionais ignorada"
[program_bad_number]="Número de grupo inválido: %s" [program_out_of_range]="Número de grupo fora do intervalo: %s"
[program_selected]="Grupo selecionado: %s" [programs_installed]="Os programas selecionados foram instalados"
[apt_update]="Atualizando a lista de pacotes" [root_required]="Execute este script como root"
[swap_configuring]="Configurando arquivo swap de %s" [setup_complete]="Configuração de memória concluída"
[next_script]="Agora execute ochenstarik-server-2.sh como root."
) ;;
zh) UI_TEXT+=(
[input_interrupted]="输入已中断" [invalid_number]="请输入 %s 到 %s 之间的数字"
[select_option]="请输入选项编号:" [select_region]="请选择地区:" [select_timezone]="请选择 %s 的时区:"
[timezone_heading]="请选择服务器时区(当前:%s" [other_timezone]="其他时区(按地区选择)" [timezone_set]="时区已设置为 %s"
[terminal_heading]="请选择新终端会话的语言(当前:%s" [terminal_keep]="保留当前终端语言"
[terminal_other]="从完整列表选择其他区域设置" [terminal_prompt]="语言编号 [1 — 保留当前设置]"
[terminal_unchanged]="终端语言未更改" [terminal_full_list]="请从完整列表选择区域设置:"
[program_heading]="请选择要安装的程序组:" [program_none]="不安装其他程序"
[program_example]="请输入用空格分隔的编号例如1 2 4 9。" [program_prompt]="选择 [Enter — 全部安装]"
[program_skipped]="已跳过其他程序的安装" [program_bad_number]="无效的程序组编号:%s"
[program_out_of_range]="程序组编号超出范围:%s" [program_selected]="已选择程序组:%s"
[programs_installed]="已安装所选程序" [apt_update]="正在更新软件包列表" [root_required]="请以 root 身份运行此脚本"
[swap_configuring]="正在配置 %s 交换文件" [setup_complete]="服务器内存设置完成"
[next_script]="现在请以 root 身份运行 ochenstarik-server-2.sh。"
) ;;
ja) UI_TEXT+=(
[input_interrupted]="入力が中断されました" [invalid_number]="%s から %s の番号を入力してください"
[select_option]="オプション番号を入力してください: " [select_region]="地域を選択してください:"
[select_timezone]="%s のタイムゾーンを選択してください:"
[timezone_heading]="サーバーのタイムゾーンを選択してください(現在: %s:"
[other_timezone]="その他のタイムゾーン(地域から選択)" [timezone_set]="タイムゾーンを %s に設定しました"
[terminal_heading]="新しいターミナルセッションの言語を選択してください(現在: %s:"
[terminal_keep]="現在の言語を維持" [terminal_other]="完全なリストから別のロケールを選択"
[terminal_prompt]="言語番号 [1 — 変更しない]: " [terminal_unchanged]="ターミナルの言語は変更されませんでした"
[terminal_full_list]="完全なリストからロケールを選択してください:"
[program_heading]="インストールするプログラムグループを選択してください:"
[program_none]="追加プログラムをインストールしない"
[program_example]="番号を空白で区切って入力してください。例: 1 2 4 9。"
[program_prompt]="選択 [Enter — すべてインストール]: " [program_skipped]="追加プログラムのインストールをスキップしました"
[program_bad_number]="無効なグループ番号: %s" [program_out_of_range]="グループ番号が範囲外です: %s"
[program_selected]="選択したグループ: %s" [programs_installed]="選択したプログラムをインストールしました"
[apt_update]="パッケージリストを更新しています" [root_required]="このスクリプトを root で実行してください"
[swap_configuring]="%s のスワップファイルを設定しています" [setup_complete]="メモリ設定が完了しました"
[next_script]="次に ochenstarik-server-2.sh を root で実行してください。"
) ;;
ar) UI_TEXT+=(
[input_interrupted]="تم إيقاف الإدخال" [invalid_number]="أدخل رقماً من %s إلى %s"
[select_option]="أدخل رقم الخيار: " [select_region]="اختر المنطقة:" [select_timezone]="اختر منطقة زمنية في %s:"
[timezone_heading]="اختر المنطقة الزمنية للخادم (الحالية: %s):" [other_timezone]="منطقة زمنية أخرى (حسب المنطقة)"
[timezone_set]="تم ضبط المنطقة الزمنية على %s" [terminal_heading]="اختر لغة جلسات الطرفية الجديدة (الحالية: %s):"
[terminal_keep]="الاحتفاظ باللغة الحالية" [terminal_other]="إعداد محلي آخر من القائمة الكاملة"
[terminal_prompt]="رقم اللغة [1 — الاحتفاظ]: " [terminal_unchanged]="لم تتغير لغة الطرفية"
[terminal_full_list]="اختر إعداداً محلياً من القائمة الكاملة:"
[program_heading]="اختر مجموعات البرامج المراد تثبيتها:" [program_none]="عدم تثبيت برامج إضافية"
[program_example]="أدخل الأرقام مفصولة بمسافات، مثال: 1 2 4 9." [program_prompt]="الاختيار [Enter — تثبيت الكل]: "
[program_skipped]="تم تخطي تثبيت البرامج الإضافية" [program_bad_number]="رقم مجموعة غير صالح: %s"
[program_out_of_range]="رقم المجموعة خارج النطاق: %s" [program_selected]="المجموعة المحددة: %s"
[programs_installed]="تم تثبيت البرامج المحددة" [apt_update]="جارٍ تحديث قائمة الحزم"
[root_required]="شغّل هذا البرنامج النصي بصلاحية root" [swap_configuring]="جارٍ إعداد ملف swap بحجم %s"
[setup_complete]="اكتمل إعداد ذاكرة الخادم" [next_script]="شغّل الآن ochenstarik-server-2.sh بصلاحية root."
) ;;
hi) UI_TEXT+=(
[input_interrupted]="इनपुट बाधित हुआ" [invalid_number]="%s से %s तक की संख्या दर्ज करें"
[select_option]="विकल्प संख्या दर्ज करें: " [select_region]="क्षेत्र चुनें:" [select_timezone]="%s में समय क्षेत्र चुनें:"
[timezone_heading]="सर्वर का समय क्षेत्र चुनें (वर्तमान: %s):" [other_timezone]="अन्य समय क्षेत्र (क्षेत्र के अनुसार)"
[timezone_set]="समय क्षेत्र %s पर सेट किया गया" [terminal_heading]="नई टर्मिनल सत्रों की भाषा चुनें (वर्तमान: %s):"
[terminal_keep]="वर्तमान भाषा बनाए रखें" [terminal_other]="पूरी सूची से अन्य लोकेल"
[terminal_prompt]="भाषा संख्या [1 — वर्तमान रखें]: " [terminal_unchanged]="टर्मिनल भाषा नहीं बदली गई"
[terminal_full_list]="पूरी सूची से लोकेल चुनें:"
[program_heading]="इंस्टॉल करने के लिए प्रोग्राम समूह चुनें:" [program_none]="अतिरिक्त प्रोग्राम इंस्टॉल न करें"
[program_example]="खाली स्थान से अलग संख्याएँ दर्ज करें, उदाहरण: 1 2 4 9।" [program_prompt]="चयन [Enter — सभी इंस्टॉल करें]: "
[program_skipped]="अतिरिक्त प्रोग्राम की स्थापना छोड़ दी गई" [program_bad_number]="अमान्य समूह संख्या: %s"
[program_out_of_range]="समूह संख्या सीमा से बाहर है: %s" [program_selected]="चयनित समूह: %s"
[programs_installed]="चयनित प्रोग्राम इंस्टॉल हो गए" [apt_update]="पैकेज सूची अपडेट की जा रही है"
[root_required]="इस स्क्रिप्ट को root के रूप में चलाएँ" [swap_configuring]="%s swap फ़ाइल कॉन्फ़िगर की जा रही है"
[setup_complete]="सर्वर मेमोरी सेटअप पूरा हुआ" [next_script]="अब ochenstarik-server-2.sh को root के रूप में चलाएँ।"
) ;;
esac
}
msg() {
local key="$1"
shift
printf "${UI_TEXT[$key]}" "$@"
}
localize_program_catalog() {
case "$UI_LANG" in
ru) PROGRAM_GROUP_TITLES=(
"Терминал и повседневная работа" "Диагностика сети" "Мониторинг системы и дисков"
"Безопасность и обслуживание Ubuntu" "Разработка и автоматизация"
"Архивы, таблицы и локальные данные" "Документы, PDF и OCR"
"Изображения, аудио и видео" "Резервное копирование и синхронизация"
) ;;
es) PROGRAM_GROUP_TITLES=(
"Terminal y herramientas cotidianas" "Diagnóstico de red" "Supervisión del sistema y discos"
"Seguridad y mantenimiento de Ubuntu" "Desarrollo y automatización"
"Archivos, tablas y datos locales" "Documentos, PDF y OCR"
"Imágenes, audio y vídeo" "Copias de seguridad y sincronización"
) ;;
de) PROGRAM_GROUP_TITLES=(
"Terminal und Alltagswerkzeuge" "Netzwerkdiagnose" "System- und Datenträgerüberwachung"
"Ubuntu-Sicherheit und Wartung" "Entwicklung und Automatisierung"
"Archive, Tabellen und lokale Daten" "Dokumente, PDF und OCR"
"Bilder, Audio und Video" "Sicherung und Synchronisierung"
) ;;
fr) PROGRAM_GROUP_TITLES=(
"Terminal et outils quotidiens" "Diagnostic réseau" "Surveillance du système et des disques"
"Sécurité et maintenance dUbuntu" "Développement et automatisation"
"Archives, tableaux et données locales" "Documents, PDF et OCR"
"Images, audio et vidéo" "Sauvegarde et synchronisation"
) ;;
pt) PROGRAM_GROUP_TITLES=(
"Terminal e ferramentas diárias" "Diagnóstico de rede" "Monitoramento do sistema e discos"
"Segurança e manutenção do Ubuntu" "Desenvolvimento e automação"
"Arquivos, tabelas e dados locais" "Documentos, PDF e OCR"
"Imagens, áudio e vídeo" "Backup e sincronização"
) ;;
zh) PROGRAM_GROUP_TITLES=(
"终端与日常工具" "网络诊断" "系统与磁盘监控" "Ubuntu 安全与维护" "开发与自动化"
"压缩包、表格与本地数据" "文档、PDF 与 OCR" "图像、音频与视频" "备份与同步"
) ;;
ja) PROGRAM_GROUP_TITLES=(
"ターミナルと日常ツール" "ネットワーク診断" "システムとディスクの監視"
"Ubuntu のセキュリティと保守" "開発と自動化" "アーカイブ、表、ローカルデータ"
"文書、PDF、OCR" "画像、音声、動画" "バックアップと同期"
) ;;
ar) PROGRAM_GROUP_TITLES=(
"الطرفية والأدوات اليومية" "تشخيص الشبكة" "مراقبة النظام والأقراص"
"أمان Ubuntu وصيانته" "التطوير والأتمتة" "الأرشيفات والجداول والبيانات المحلية"
"المستندات وPDF وOCR" "الصور والصوت والفيديو" "النسخ الاحتياطي والمزامنة"
) ;;
hi) PROGRAM_GROUP_TITLES=(
"टर्मिनल और दैनिक उपकरण" "नेटवर्क निदान" "सिस्टम और डिस्क निगरानी"
"Ubuntu सुरक्षा और रखरखाव" "विकास और स्वचालन" "अभिलेख, तालिकाएँ और स्थानीय डेटा"
"दस्तावेज़, PDF और OCR" "चित्र, ऑडियो और वीडियो" "बैकअप और सिंक्रोनाइज़ेशन"
) ;;
esac
}
# The unified installer passes the selected dialog language through the
# environment. A directly launched step uses English by default.
load_ui_text
localize_program_catalog
log() { printf '[+] %s\n' "$*"; }
warn() { printf '[!] %s\n' "$*" >&2; }
die() { printf '[x] %s\n' "$*" >&2; exit 1; }
package_is_installed() {
dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -Fqx 'install ok installed'
}
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 "$(msg required_missing "$command_name")"
done
}
ensure_apt_updated() {
[[ "$APT_UPDATED" == no ]] || return 0
ensure_package_tools
log "$(msg apt_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"
[[ ! -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"
chmod 600 "$LOCALE_BACKUP"
else
: > "$LOCALE_ABSENT_MARKER"
chmod 600 "$LOCALE_ABSENT_MARKER"
fi
fi
}
choose_terminal_language() {
local answer selected_locale selected_language primary_language package_name index
local current_locale="${LANG:-не задана}"
local -a titles=(
"$(msg terminal_keep)"
"Русский — 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"
"$(msg terminal_other)"
)
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' "$(msg terminal_heading "$current_locale")"
for index in "${!titles[@]}"; do
printf ' %d) %s\n' "$((index + 1))" "${titles[index]}"
done
read -rp "$(msg terminal_prompt)" answer || die "$(msg input_interrupted)"
answer="${answer:-1}"
[[ "$answer" =~ ^[0-9]+$ ]] && ((answer >= 1 && answer <= ${#titles[@]})) && break
warn "$(msg invalid_number 1 "${#titles[@]}")"
done
((answer == 1)) && { log "$(msg terminal_unchanged)"; 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 "$(msg terminal_full_list)" 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
available_packages+=("$package_name")
package_is_installed "$package_name" || newly_installed+=("$package_name")
else
warn "$(msg locale_package_missing "$package_name")"
fi
done
export DEBIAN_FRONTEND=noninteractive
((${#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[@]}"
log "$(msg locale_selected "$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%s\n' "$(msg program_heading)"
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
printf ' 0) %s\n' "$(msg program_none)"
printf '\n%s\n' "$(msg program_example)"
read -rp "$(msg program_prompt)" answer || die "$(msg input_interrupted)"
answer="${answer//,/ }"
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 "$(msg program_skipped)"
return 0
else
for token in $answer; do
[[ "$token" =~ ^[0-9]+$ ]] \
|| die "$(msg program_bad_number "$token")"
((token >= 1 && token <= ${#PROGRAM_GROUP_TITLES[@]})) \
|| die "$(msg program_out_of_range "$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 "$(msg program_selected "${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 "$(msg package_missing "$package_name")"
fi
done
((${#available[@]} > 0)) || die "$(msg no_packages)"
export DEBIAN_FRONTEND=noninteractive
apt-get install -y "${available[@]}"
record_new_packages "$PROGRAM_PACKAGES" "${newly_installed[@]}"
log "$(msg programs_installed)"
}
choose_numbered_option() {
local prompt="$1" result_variable="$2" answer index
shift 2
local -a options=("$@")
((${#options[@]} > 0)) || die "$(msg no_options "$prompt")"
while :; do
printf '\n%s\n' "$prompt"
for index in "${!options[@]}"; do
printf ' %d) %s\n' "$((index + 1))" "${options[index]}"
done
read -rp "$(msg select_option)" answer || die "$(msg input_interrupted)"
if [[ "$answer" =~ ^[0-9]+$ ]] && ((answer >= 1 && answer <= ${#options[@]})); then
printf -v "$result_variable" '%s' "${options[answer - 1]}"
return 0
fi
warn "$(msg invalid_number 1 "${#options[@]}")"
done
}
choose_timezone_from_full_list() {
local result_variable="$1" region selected_timezone
local -a regions region_timezones
mapfile -t regions < <(timedatectl list-timezones | awk -F/ 'NF > 1 && !seen[$1]++ { print $1 }')
choose_numbered_option "$(msg select_region)" region "${regions[@]}"
mapfile -t region_timezones < <(timedatectl list-timezones | awk -F/ -v selected_region="$region" '$1 == selected_region')
choose_numbered_option "$(msg select_timezone "$region")" selected_timezone "${region_timezones[@]}"
printf -v "$result_variable" '%s' "$selected_timezone"
}
choose_timezone() {
local current_timezone timezone candidate
local full_list_option="$(msg other_timezone)"
local -a timezone_options=()
current_timezone="$(timedatectl show --property=Timezone --value 2>/dev/null || true)"
current_timezone="${current_timezone:-Asia/Novosibirsk}"
for candidate in \
"$current_timezone" \
UTC \
Europe/Moscow \
Europe/Kaliningrad \
Asia/Yekaterinburg \
Asia/Omsk \
Asia/Novosibirsk \
Asia/Krasnoyarsk \
Asia/Irkutsk \
Asia/Yakutsk \
Asia/Vladivostok \
Asia/Magadan \
Asia/Kamchatka; do
if timedatectl list-timezones | grep -Fqx -- "$candidate" &&
[[ ! " ${timezone_options[*]} " =~ " ${candidate} " ]]; then
timezone_options+=("$candidate")
fi
done
timezone_options+=("$full_list_option")
choose_numbered_option "$(msg timezone_heading "$current_timezone")" timezone "${timezone_options[@]}"
if [[ "$timezone" == "$full_list_option" ]]; then
choose_timezone_from_full_list timezone
fi
timedatectl list-timezones | grep -Fqx -- "$timezone" || die "$(msg unknown_timezone "$timezone")"
timedatectl set-timezone "$timezone"
log "$(msg timezone_set "$timezone")"
}
[[ "$EUID" -eq 0 ]] || die "Run this script as root"
for command_name in awk blkid fallocate grep mkswap swapon sysctl timedatectl; do
command -v "$command_name" >/dev/null 2>&1 || die "$(msg required_missing "$command_name")"
done
choose_timezone
choose_terminal_language
choose_and_install_programs
log "$(msg swap_configuring "$SWAPSIZE")"
if [[ ! -e "$SWAPFILE" ]]; then
fallocate -l "$SWAPSIZE" "$SWAPFILE"
chmod 600 "$SWAPFILE"
mkswap "$SWAPFILE"
elif [[ ! -f "$SWAPFILE" || -L "$SWAPFILE" ]]; then
die "$SWAPFILE exists but is not a regular non-symlink file"
else
chmod 600 "$SWAPFILE"
swap_type="$(blkid -p -s TYPE -o value "$SWAPFILE" 2>/dev/null || true)"
[[ "$swap_type" == swap ]] || die "$SWAPFILE exists but does not contain a swap signature"
fi
if ! swapon --show=NAME --noheadings | awk '{$1=$1}; $0 == "/swapfile" { found=1 } END { exit !found }'; then
swapon "$SWAPFILE"
fi
if ! awk '$1 == "/swapfile" && $3 == "swap" { found=1 } END { exit !found }' /etc/fstab; then
cp -a /etc/fstab "/etc/fstab.bak.$(date +%F-%H%M%S-%N)"
printf '%s none swap sw 0 0\n' "$SWAPFILE" >> /etc/fstab
fi
cat > "$SYSCTL_FILE" <<EOF
# Managed by ochenstarik-server-1.sh
vm.swappiness=${SWAPPINESS}
EOF
chmod 644 "$SYSCTL_FILE"
sysctl -p "$SYSCTL_FILE" >/dev/null
log "$(msg setup_complete)"
printf 'Timezone: %s\n' "$(timedatectl show --property=Timezone --value)"
swapon --show
printf '\n%s\n' "$(msg next_script)"