333 lines
14 KiB
Bash
Executable File
333 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
||
# ============================================================================
|
||
# init_system.sh — первичная инициализация системы (BBox / OpenDoor base)
|
||
# ----------------------------------------------------------------------------
|
||
# Выполняется ОДИН раз после развёртывания образа на новом устройстве:
|
||
# * обновление пакетов
|
||
# * установка базовых утилит (tmux, mc, git, net-tools, curl, wget, btop)
|
||
# * установка Docker + Docker Compose
|
||
# * настройка hostname и баннера BBox (bbox.wtf)
|
||
# * сброс уникальных идентификаторов (machine-id, SSH host keys)
|
||
# * установка вспомогательных скриптов и автозапуска
|
||
#
|
||
# Безопасно повторять: повторный запуск пропускает уже выполненные шаги.
|
||
# ============================================================================
|
||
|
||
set -euo pipefail
|
||
|
||
# Цвета
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
NC='\033[0m'
|
||
|
||
BBOX_VERSION="1.2"
|
||
BBOX_URL="bbox.wtf"
|
||
HOSTNAME_DEFAULT="bbox-node"
|
||
DEFAULT_TIMEOUT="${BBOX_TIMEOUT:-5}" # секунд на подтверждение (переопределяется env BBOX_TIMEOUT)
|
||
|
||
log() { echo -e "${GREEN}[init]${NC} $*"; }
|
||
warn() { echo -e "${YELLOW}[init][warn]${NC} $*"; }
|
||
fail() { echo -e "${RED}[init][error]${NC} $*"; exit 1; }
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Подтверждение с таймером: если пользователь не ответил за TIMEOUT секунд,
|
||
# принимается DEFAULT (автовыбор). Возвращает 0 = yes, 1 = no.
|
||
# usage: confirm_default "Вопрос" y|n [timeout]
|
||
# ---------------------------------------------------------------------------
|
||
confirm_default() {
|
||
local msg="$1" default="$2" timeout="${3:-$DEFAULT_TIMEOUT}"
|
||
local ans
|
||
case "$default" in
|
||
y|Y|yes) default="y"; prompt="[Y/n]";;
|
||
*) default="n"; prompt="[y/N]";;
|
||
esac
|
||
printf " %s %s (авто: %s через %ss): " "$msg" "$prompt" "$default" "$timeout"
|
||
if ! read -r -t "$timeout" ans 2>/dev/null; then
|
||
echo ""
|
||
echo " → таймаут, принято значение по умолчанию: $default"
|
||
ans="$default"
|
||
fi
|
||
case "${ans,,}" in
|
||
y|yes) return 0 ;;
|
||
n|no) return 1 ;;
|
||
*) [ "$default" = "y" ] ;;
|
||
esac
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 1. Определение дистрибутива и архитектуры
|
||
# ---------------------------------------------------------------------------
|
||
detect_distro() {
|
||
if [ -f /etc/os-release ]; then
|
||
. /etc/os-release
|
||
DISTRO=$ID
|
||
VERSION_CODENAME=$VERSION_CODENAME
|
||
else
|
||
fail "Не удалось определить дистрибутив."
|
||
fi
|
||
ARCH=$(uname -m)
|
||
log "Дистрибутив: $DISTRO ($VERSION_CODENAME), архитектура: $ARCH"
|
||
}
|
||
|
||
check_install() {
|
||
if command -v "$1" &> /dev/null; then
|
||
log "$1 уже установлен."
|
||
else
|
||
log "Установка $1..."
|
||
if ! apt-get install -y "$1" &> /dev/null; then
|
||
fail "Ошибка при установке $1."
|
||
fi
|
||
log "$1 установлен."
|
||
fi
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 2. Обновление системы
|
||
# ---------------------------------------------------------------------------
|
||
update_system() {
|
||
log "Обновление списков пакетов..."
|
||
apt-get update || fail "Ошибка apt-get update"
|
||
apt-get upgrade -y || warn "apt-get upgrade завершился с предупреждениями"
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 3. Установка базовых утилит
|
||
# ---------------------------------------------------------------------------
|
||
install_base_utils() {
|
||
log "Установка базовых утилит..."
|
||
check_install tmux
|
||
check_install mc
|
||
check_install git
|
||
check_install net-tools
|
||
check_install curl
|
||
check_install wget
|
||
check_install build-essential
|
||
check_install btop
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 4. Установка Docker
|
||
# ---------------------------------------------------------------------------
|
||
install_docker() {
|
||
if command -v docker &> /dev/null; then
|
||
log "Docker уже установлен."
|
||
return
|
||
fi
|
||
log "Установка Docker для $DISTRO..."
|
||
apt-get install -y apt-transport-https ca-certificates curl gnupg
|
||
|
||
mkdir -p /etc/apt/keyrings
|
||
curl -fsSL "https://download.docker.com/linux/$DISTRO/gpg" | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||
|
||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$DISTRO $VERSION_CODENAME stable" \
|
||
> /etc/apt/sources.list.d/docker.list
|
||
|
||
apt-get update
|
||
if ! apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; then
|
||
warn "Официальный репозиторий не подошёл, пробуем docker.io..."
|
||
apt-get install -y docker.io docker-compose-plugin || fail "Не удалось установить Docker."
|
||
fi
|
||
systemctl enable --now docker
|
||
log "Docker установлен: $(docker --version)"
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 5. Настройка hostname
|
||
# ---------------------------------------------------------------------------
|
||
setup_hostname() {
|
||
local NEW_HOSTNAME="${HOSTNAME:-$HOSTNAME_DEFAULT}"
|
||
local CUR_HOSTNAME
|
||
CUR_HOSTNAME=$(hostname)
|
||
if [ "$CUR_HOSTNAME" != "$NEW_HOSTNAME" ]; then
|
||
log "Смена hostname: $CUR_HOSTNAME -> $NEW_HOSTNAME"
|
||
hostnamectl set-hostname "$NEW_HOSTNAME" || warn "Не удалось сменить hostname"
|
||
echo "$NEW_HOSTNAME" > /etc/hostname
|
||
else
|
||
log "Hostname уже установлен: $CUR_HOSTNAME"
|
||
fi
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 6. Баннер BBox (MOTD) — шаблон из репозитория
|
||
# ---------------------------------------------------------------------------
|
||
# Шаблон: motd/10-armbian-header.tpl с плейсхолдерами __BBOX_VERSION__ / __BBOX_URL__.
|
||
# При каждом запуске файл целиком перезаписывается из шаблона, поэтому смена
|
||
# версии в репозитории автоматически обновляет баннер на устройстве.
|
||
# ---------------------------------------------------------------------------
|
||
MOTD_HEADER="/etc/update-motd.d/10-armbian-header"
|
||
MOTD_TPL_REPO="motd/10-armbian-header.tpl"
|
||
|
||
render_motd() {
|
||
local tpl="$1"
|
||
sed -e "s|__BBOX_VERSION__|${BBOX_VERSION}|g" \
|
||
-e "s|__BBOX_URL__|${BBOX_URL}|g" "$tpl"
|
||
}
|
||
|
||
install_motd_template() {
|
||
local tpl_src=""
|
||
local script_dir
|
||
script_dir="$(dirname "$(readlink -f "$0")")"
|
||
# Источники шаблона:
|
||
# 1) рядом со скриптом (репозиторий / временная загрузка)
|
||
# 2) в общем хранилище (после install_scripts.sh -> /usr/local/share/bbox/motd)
|
||
for candidate in \
|
||
"$script_dir/$MOTD_TPL_REPO" \
|
||
"$script_dir/../$MOTD_TPL_REPO" \
|
||
"$script_dir/motd/10-armbian-header.tpl" \
|
||
"/usr/local/share/bbox/motd/$(basename "$MOTD_TPL_REPO")"
|
||
do
|
||
if [ -f "$candidate" ]; then
|
||
tpl_src="$candidate"
|
||
break
|
||
fi
|
||
done
|
||
|
||
if [ -z "$tpl_src" ]; then
|
||
warn "Шаблон баннера не найден ($MOTD_TPL_REPO), баннер не изменён."
|
||
return 1
|
||
fi
|
||
|
||
# Бэкап дефолтного баннера только один раз
|
||
if [ ! -f "${MOTD_HEADER}.bak" ]; then
|
||
cp -f "$MOTD_HEADER" "${MOTD_HEADER}.bak" 2>/dev/null || true
|
||
fi
|
||
|
||
# Запись шаблона с подстановкой версии/URL
|
||
render_motd "$tpl_src" > "$MOTD_HEADER"
|
||
chmod 755 "$MOTD_HEADER"
|
||
log "Баннер BBox установлен из шаблона: v${BBOX_VERSION} (${BBOX_URL})"
|
||
}
|
||
|
||
setup_motd() {
|
||
if [ -f "$MOTD_HEADER" ]; then
|
||
install_motd_template
|
||
else
|
||
warn "Баннер входа не найден ($MOTD_HEADER), пропуск."
|
||
fi
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 7. Сброс уникальных идентификаторов (для клонированных образов)
|
||
# ---------------------------------------------------------------------------
|
||
reset_unique_ids() {
|
||
log "Сброс machine-id..."
|
||
rm -f /etc/machine-id /var/lib/dbus/machine-id
|
||
systemd-machine-id-setup || warn "Не удалось пересоздать machine-id"
|
||
|
||
log "Сброс SSH host keys (будут пересозданы при старте sshd)..."
|
||
rm -f /etc/ssh/ssh_host_*
|
||
systemctl restart ssh || systemctl restart sshd || warn "SSH будет перезапущен при перезагрузке"
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 8. Установка вспомогательных скриптов и автозапуска
|
||
# ---------------------------------------------------------------------------
|
||
install_helper_scripts() {
|
||
local SRC_DIR
|
||
SRC_DIR=$(dirname "$(readlink -f "$0")")
|
||
if [ -x "$SRC_DIR/install_scripts.sh" ]; then
|
||
log "Установка вспомогательных скриптов..."
|
||
"$SRC_DIR/install_scripts.sh" || warn "install_scripts.sh завершился с ошибкой"
|
||
else
|
||
warn "install_scripts.sh не найден рядом ($SRC_DIR), пропуск."
|
||
fi
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# MAIN
|
||
# ---------------------------------------------------------------------------
|
||
FIRSTBOOT=0
|
||
FIRSTBOOT_MARKER="/var/lib/bbox-firstboot-done"
|
||
|
||
main() {
|
||
if [ "$(id -u)" -ne 0 ]; then
|
||
fail "Запустите скрипт от root: sudo ./init_system.sh"
|
||
fi
|
||
|
||
# Парсинг аргументов
|
||
for arg in "$@"; do
|
||
case "$arg" in
|
||
--firstboot) FIRSTBOOT=1 ;;
|
||
--yes|-y) ALL_YES=1 ;;
|
||
--timeout=*) DEFAULT_TIMEOUT="${arg#*=}" ;;
|
||
esac
|
||
done
|
||
|
||
if [ $FIRSTBOOT -eq 1 ]; then
|
||
log "Режим firstboot (автозапуск при первой загрузке)"
|
||
if [ -f "$FIRSTBOOT_MARKER" ]; then
|
||
log "Инициализация уже выполнялась, пропуск."
|
||
exit 0
|
||
fi
|
||
fi
|
||
|
||
log "=== BBox (bbox.wtf) v${BBOX_VERSION} — первичная инициализация ==="
|
||
echo
|
||
echo " Будет выполнено:"
|
||
echo " 1. Обновление системы"
|
||
echo " 2. Установка базовых утилит (tmux, mc, git, net-tools, curl, wget, build-essential, btop)"
|
||
echo " 3. Установка Docker + Docker Compose"
|
||
echo " 4. Настройка hostname"
|
||
echo " 5. Установка баннера BBox (bbox.wtf)"
|
||
echo " 6. Установка вспомогательных скриптов и автозапуска"
|
||
echo
|
||
|
||
# Подтверждение запуска (автовыбор YES через 5 сек)
|
||
if [ "${ALL_YES:-0}" -eq 1 ] || confirm_default "Начать инициализацию?" "y"; then
|
||
log "Запуск инициализации..."
|
||
else
|
||
fail "Отменено пользователем."
|
||
fi
|
||
|
||
detect_distro
|
||
update_system
|
||
install_base_utils
|
||
install_docker
|
||
|
||
# Выбор hostname (автовыбор bbox-node через 5 сек)
|
||
if [ "${ALL_YES:-0}" -eq 1 ] || confirm_default "Сменить hostname на '${HOSTNAME:-bbox-node}'?" "y"; then
|
||
setup_hostname
|
||
else
|
||
log "Hostname оставлен прежним: $(hostname)"
|
||
fi
|
||
|
||
# Баннер BBox (автовыбор YES через 5 сек)
|
||
if [ "${ALL_YES:-0}" -eq 1 ] || confirm_default "Установить баннер BBox (bbox.wtf)?" "y"; then
|
||
setup_motd
|
||
else
|
||
warn "Баннер не установлен."
|
||
fi
|
||
|
||
if [ $FIRSTBOOT -eq 1 ]; then
|
||
# При автозапуске не трогаем ssh/machine-id до перезагрузки
|
||
warn "Режим firstboot: сброс идентификаторов выполняется при следующем запуске"
|
||
touch "$FIRSTBOOT_MARKER"
|
||
else
|
||
# Сброс идентификаторов (автовыбор YES — иначе клоны будут одинаковыми)
|
||
if [ "${ALL_YES:-0}" -eq 1 ] || confirm_default "Сбросить machine-id и SSH host keys (для клонирования)?" "y"; then
|
||
reset_unique_ids
|
||
else
|
||
warn "Идентификаторы не сброшены."
|
||
fi
|
||
fi
|
||
|
||
# Установка вспомогательных скриптов (автовыбор YES через 5 сек)
|
||
if [ "${ALL_YES:-0}" -eq 1 ] || confirm_default "Установить вспомогательные скрипты и автозапуск?" "y"; then
|
||
install_helper_scripts
|
||
else
|
||
warn "Скрипты не установлены."
|
||
fi
|
||
|
||
log "Инициализация завершена."
|
||
if [ $FIRSTBOOT -eq 1 ]; then
|
||
log "Сервис автозапуска завершён. Перезагрузка не требуется."
|
||
else
|
||
echo
|
||
echo "Рекомендуется перезагрузить устройство: reboot"
|
||
fi
|
||
}
|
||
|
||
main "$@"
|