lightweight-server/ochenstarik-server-1.sh
2026-07-15 11:57:11 +07:00

202 lines
6.9 KiB
Bash

#!/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-russian.conf"
readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent"
readonly LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list"
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"
done
log "Обновление списка пакетов для русификации"
apt-get update
install -d -m 700 -o root -g root "$CONFIG_DIR"
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
for package_name in language-pack-ru manpages-ru; do
if apt-cache show "$package_name" >/dev/null 2>&1; then
packages+=("$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
touch "$LOCALE_PACKAGES"
for package_name in "${newly_installed[@]}"; do
grep -Fqx -- "$package_name" "$LOCALE_PACKAGES" \
|| printf '%s\n' "$package_name" >> "$LOCALE_PACKAGES"
done
chown root:root "$LOCALE_PACKAGES"
chmod 600 "$LOCALE_PACKAGES"
log "Русская локаль включена. Она применится при следующем входе в терминал"
}
choose_numbered_option() {
local prompt="$1" result_variable="$2" answer index
shift 2
local -a options=("$@")
((${#options[@]} > 0)) || die "No options are available for: ${prompt}"
while :; do
printf '\n%s\n' "$prompt"
for index in "${!options[@]}"; do
printf ' %d) %s\n' "$((index + 1))" "${options[index]}"
done
read -rp "Enter the option number: " answer || die "Input was interrupted"
if [[ "$answer" =~ ^[0-9]+$ ]] && ((answer >= 1 && answer <= ${#options[@]})); then
printf -v "$result_variable" '%s' "${options[answer - 1]}"
return 0
fi
warn "Enter a number from 1 to ${#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 "Select a region:" region "${regions[@]}"
mapfile -t region_timezones < <(timedatectl list-timezones | awk -F/ -v selected_region="$region" '$1 == selected_region')
choose_numbered_option "Select a timezone in ${region}:" selected_timezone "${region_timezones[@]}"
printf -v "$result_variable" '%s' "$selected_timezone"
}
choose_timezone() {
local current_timezone timezone candidate
local full_list_option="Other timezone (select by region)"
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 "Select the server timezone (current: ${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 "Unknown timezone: $timezone"
timedatectl set-timezone "$timezone"
log "Timezone set to ${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 "Required command not found: $command_name"
done
choose_timezone
install_russian_terminal_locale
log "Configuring ${SWAPSIZE} swap file"
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 "Server memory setup is complete"
printf 'Timezone: %s\n' "$(timedatectl show --property=Timezone --value)"
swapon --show
printf '\nNow run ochenstarik-server-2.sh as root.\n'