Версия:
Скопируйте этот скрипт
#!/bin/bash
# ====================================================================
# CCS Deployment - Мастер первичной установки (ROOT VERSION)
# ====================================================================
#
# Этот скрипт запускается от root и автоматически:
# - Клонирует проект CCS.Deployment
# - Создает пользователя для работы с CCS
# - Запускает мастер установки
#
# Создание и запуск:
# vim first.sh && chmod +x first.sh && ./first.sh
#
# Использование с конфигурацией:
# ./first.sh /path/to/config.cfg
#
# ====================================================================
set -e
# Конфигурация репозиториев
declare -A REPO_URLS
REPO_URLS[1]="https://gitflic.ru/project/ccsmskru/ccs-deployment.git"
REPO_URLS[2]="https://github.com/CCSMSKRU/CCS.Deployment.git"
declare -A REPO_NAMES
REPO_NAMES[1]="GitFlic"
REPO_NAMES[2]="GitHub"
declare -A REPO_DESCRIPTIONS
REPO_DESCRIPTIONS[1]="GitFlic (рекомендуется для России)"
REPO_DESCRIPTIONS[2]="GitHub (международный)"
# Цвета для вывода (определяем в начале скрипта)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
NC='\033[0m' # No Color
# Флаг «репозиторий только что клонирован»
JUST_CLONED=0
# Утилита: определение ОС и пакетного менеджера
detect_os_pm() {
OS_ID="unknown"; OS_FAMILY="unknown"; PM=""
if [ -f /etc/os-release ]; then
. /etc/os-release
OS_ID="$ID"
fi
case "$OS_ID" in
debian|ubuntu)
OS_FAMILY="debian"; PM="apt" ;;
rhel|centos|rocky|almalinux|fedora)
OS_FAMILY="rhel"; PM="dnf" ;;
*)
OS_FAMILY="unknown" ;;
esac
}
# Установка базовых зависимостей под root (для всей системы), включая Fail2Ban
ensure_system_dependencies() {
detect_os_pm
echo -e "${BLUE}Проверка и установка системных зависимостей...${NC}"
case "$OS_FAMILY" in
debian)
apt update -y >/dev/null 2>&1 || true
# Базовые инструменты и контейнеры
apt install -y git curl vim mc pwgen htop tmux rsync python3 ufw pipx podman cron >/dev/null 2>&1 || true
# Компоненты для egress-политик: nftables и DNS-утилиты для резолва
apt install -y nftables bind9-dnsutils >/dev/null 2>&1 || true
# Fail2Ban (системный)
apt install -y fail2ban >/dev/null 2>&1 || true
if [ ! -f "/etc/fail2ban/jail.local" ]; then
BANACTION="iptables-multiport"
if command -v ufw >/dev/null 2>&1; then BANACTION="ufw"; fi
if command -v firewall-cmd >/dev/null 2>&1; then BANACTION="firewallcmd-rich-rules"; fi
cat > /etc/fail2ban/jail.local <<EOF
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
banaction = ${BANACTION}
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = systemd
EOF
fi
systemctl enable fail2ban >/dev/null 2>&1 || true
systemctl restart fail2ban >/dev/null 2>&1 || systemctl start fail2ban >/dev/null 2>&1 || true
# Включаем и запускаем cron (для задач бэкапа)
systemctl enable cron >/dev/null 2>&1 || true
systemctl restart cron >/dev/null 2>&1 || systemctl start cron >/dev/null 2>&1 || true
# Разрешить непривилегированные порты с 80 для rootless публикации 80/443
if [ ! -f /etc/sysctl.d/99-ccs-rootless.conf ] || ! grep -q "^net.ipv4.ip_unprivileged_port_start\s*=\s*80" /etc/sysctl.d/99-ccs-rootless.conf 2>/dev/null; then
echo "net.ipv4.ip_unprivileged_port_start = 80" > /etc/sysctl.d/99-ccs-rootless.conf
sysctl --system >/dev/null 2>&1 || true
fi
# Базовые правила firewall (если ufw установлен)
if command -v ufw >/dev/null 2>&1; then
ufw allow 22/tcp >/dev/null 2>&1 || true
ufw allow 80/tcp >/dev/null 2>&1 || true
ufw allow 443/tcp >/dev/null 2>&1 || true
ufw --force enable >/dev/null 2>&1 || true
fi
;;
rhel)
$PM install -y git curl vim mc pwgen htop tmux rsync python3 podman >/dev/null 2>&1 || true
# Компоненты для egress-политик
$PM install -y nftables bind-utils >/dev/null 2>&1 || true
# Fail2Ban
$PM install -y fail2ban >/dev/null 2>&1 || true
systemctl enable fail2ban >/dev/null 2>&1 || true
systemctl restart fail2ban >/dev/null 2>&1 || systemctl start fail2ban >/dev/null 2>&1 || true
# Включаем crond на RHEL-семействе
systemctl enable crond >/dev/null 2>&1 || true
systemctl restart crond >/dev/null 2>&1 || systemctl start crond >/dev/null 2>&1 || true
# Разрешить непривилегированные порты с 80 для rootless публикации 80/443
if [ ! -f /etc/sysctl.d/99-ccs-rootless.conf ] || ! grep -q "^net.ipv4.ip_unprivileged_port_start\s*=\s*80" /etc/sysctl.d/99-ccs-rootless.conf 2>/dev/null; then
echo "net.ipv4.ip_unprivileged_port_start = 80" > /etc/sysctl.d/99-ccs-rootless.conf
sysctl --system >/dev/null 2>&1 || true
fi
# Firewalld базовые правила, если доступен
if command -v firewall-cmd >/dev/null 2>&1; then
systemctl enable firewalld >/dev/null 2>&1 || true
systemctl start firewalld >/dev/null 2>&1 || true
firewall-cmd --add-port=80/tcp --permanent >/dev/null 2>&1 || true
firewall-cmd --add-port=443/tcp --permanent >/dev/null 2>&1 || true
firewall-cmd --reload >/dev/null 2>&1 || true
fi
;;
*)
echo -e "${YELLOW}Неизвестная ОС — пропускаем автоустановку зависимостей${NC}"
;;
esac
# Настройка логирования контейнеров на journald (как в prepareOS/start.sh)
mkdir -p /var/log/journal 2>/dev/null || true
conf_file="/usr/share/containers/containers.conf"
if [ -f "$conf_file" ]; then
if grep -q "log_driver\s*=\s*\"k8s-file\"" "$conf_file"; then
sed -i 's/log_driver\s*=\s*"k8s-file"/log_driver = "journald"/' "$conf_file" 2>/dev/null || true
elif ! grep -q "log_driver" "$conf_file"; then
echo "log_driver = \"journald\"" >> "$conf_file"
fi
sed -i '/# log_driver = "journald"/s/# //' "$conf_file" 2>/dev/null || true
fi
}
# Немедленно обеспечим системные зависимости (root), чтобы дальше всё работало предсказуемо
ensure_system_dependencies
# --------------------------------------------------------------------
# Добавление SSH ключа пользователя
# --------------------------------------------------------------------
add_ssh_key_to_user() {
local target_user="$1"
local creation_type="$2" # "new" or "existing"
local target_home
target_home=$(eval echo ~"$target_user")
local default_choice="N"
[ "$creation_type" == "new" ] && default_choice="Y"
echo -e "\n${BLUE}--- Настройка SSH-ключа для пользователя $target_user ---${NC}"
local prompt_msg="Добавить публичный SSH-ключ для беспарольного входа? [Y/n]"
[ "$default_choice" == "N" ] && prompt_msg="Добавить публичный SSH-ключ для беспарольного входа? [y/N]"
# Попытка получить ранее сделанный выбор из конфига
local saved_ssh_choice=$(get_config "ADD_SSH_KEY" "")
if [ -n "$saved_ssh_choice" ]; then
default_choice="$saved_ssh_choice"
if [[ "$default_choice" =~ ^[Yy]$ ]]; then
prompt_msg="Добавить публичный SSH-ключ для беспарольного входа? [Y/n]"
else
prompt_msg="Добавить публичный SSH-ключ для беспарольного входа? [y/N]"
fi
fi
echo -ne "${YELLOW}$prompt_msg: ${NC}"
read user_ssh_choice
user_ssh_choice=${user_ssh_choice:-$default_choice}
# Сохраняем выбор в конфиг
save_config "ADD_SSH_KEY" "$user_ssh_choice" "Add SSH key choice"
if [[ "$user_ssh_choice" =~ ^[Yy]$ ]]; then
echo -e "\n${CYAN}Как получить публичный ключ на вашем компьютере:${NC}"
echo -e "${WHITE}Windows (PowerShell):${NC} cat ~\.ssh\id_rsa.pub"
echo -e "${WHITE}Windows (Git Bash):${NC} cat ~/.ssh/id_rsa.pub"
echo -e "${WHITE}Mac / Linux:${NC} cat ~/.ssh/id_rsa.pub"
echo -e "${YELLOW}(Если ключа нет, создайте его командой: ssh-keygen)${NC}\n"
echo -e "${BLUE}Вставьте ваш публичный ключ (начинается с ssh-rsa, ecdsa-sha2-... и т.д.):${NC}"
read -r ssh_pub_key
if [ -n "$ssh_pub_key" ]; then
local ssh_dir="$target_home/.ssh"
local auth_file="$ssh_dir/authorized_keys"
mkdir -p "$ssh_dir"
chmod 700 "$ssh_dir"
echo "$ssh_pub_key" >> "$auth_file"
chmod 600 "$auth_file"
chown -R "$target_user:$target_user" "$ssh_dir"
echo -e "${GREEN}✓ SSH-ключ успешно добавлен для пользователя $target_user${NC}"
else
echo -e "${YELLOW}Ключ не введен. Пропуск.${NC}"
fi
else
echo -e "${BLUE}Добавление SSH-ключ пропущено.${NC}"
fi
}
# --------------------------------------------------------------------
# Egress (nftables) — функции установки root-компонентов (идемпотентно)
# --------------------------------------------------------------------
# Находим корень репозитория с файлами prepareOS. Поддерживаем несколько сценариев размещения first.sh
find_repo_root_for_egress() {
local cand
# 1) Относительно расположения текущего скрипта (если скопирован как /root/first.sh, этот путь не сработает)
cand="$(cd "$(dirname "$0")/.." 2>/dev/null && pwd 2>/dev/null)"
if [ -n "$cand" ] && [ -f "$cand/prepareOS/ccs_egress_guard.sh" ]; then
echo "$cand"; return 0
fi
# 2) DEPLOY_DIR, если уже определён в окружении (позже может быть переопределён), проверим на существование
if [ -n "$DEPLOY_DIR" ] && [ -f "$DEPLOY_DIR/prepareOS/ccs_egress_guard.sh" ]; then
echo "$DEPLOY_DIR"; return 0
fi
# 3) Стандартное расположение в домашнем каталоге будущего пользователя
if [ -n "$CCS_HOME" ] && [ -f "$CCS_HOME/CCS.Deployment/prepareOS/ccs_egress_guard.sh" ]; then
echo "$CCS_HOME/CCS.Deployment"; return 0
fi
# 4) Текущая директория (на случай запуска из корня репозитория)
if [ -f "prepareOS/ccs_egress_guard.sh" ]; then
echo "$(pwd)"; return 0
fi
echo ""; return 1
}
ensure_ccs_egress_installed() {
local target_user="$1"
[ -z "$target_user" ] && return 0
local repo_root
repo_root="$(find_repo_root_for_egress)"
if [ -z "$repo_root" ]; then
echo -e "${YELLOW}[egress] Источники для установки не найдены (prepareOS/ccs_egress_guard.sh). Пропуск установки egress-компонентов.${NC}"
return 0
fi
local BIN_DIR="/usr/local/bin"
local SYS_UNIT_DIR="/etc/systemd/system"
local POLICY_DIR="/etc/polkit-1/rules.d"
# Скрипты
install -Dm755 "$repo_root/prepareOS/ccs_egress_guard.sh" "$BIN_DIR/ccs_egress_guard.sh"
install -Dm755 "$repo_root/prepareOS/ccs_container_runner.sh" "$BIN_DIR/ccs_container_runner.sh"
# systemd юниты
install -Dm644 "$repo_root/prepareOS/systemd/ccs-container@.service" "$SYS_UNIT_DIR/ccs-container@.service"
install -Dm644 "$repo_root/prepareOS/systemd/ccs-egress-guard.service" "$SYS_UNIT_DIR/ccs-egress-guard.service"
systemctl daemon-reload >/dev/null 2>&1 || true
systemctl enable ccs-egress-guard.service >/dev/null 2>&1 || true
# Директории для системных политик/окружения контейнеров
mkdir -p /var/lib/ccs-egress/policies /var/lib/ccs-egress/containers 2>/dev/null || true
# polkit правило: разрешить выбранному пользователю управление ccs-юнитами без sudo
mkdir -p "$POLICY_DIR" 2>/dev/null || true
cat > "$POLICY_DIR/50-ccs-egress.rules" <<POLKIT
polkit.addRule(function(action, subject) {
if (subject.isInGroup("${target_user}") || subject.user == "${target_user}") {
if (action.id == "org.freedesktop.systemd1.manage-units") {
var unit = action.lookup("unit");
if (unit && (unit.match(/^ccs-container@.*\.service$/) || unit == "ccs-egress-guard.service")) {
return polkit.Result.YES;
}
}
}
});
POLKIT
# Диагностика (краткая)
echo -e "${BLUE}[egress] Установлены компоненты egress (идемпотентно).${NC}"
}
ensure_podman_network_ccs_egress() {
local NET_NAME="ccs-egress"
if ! podman network inspect "$NET_NAME" >/dev/null 2>&1; then
podman network create "$NET_NAME" >/dev/null 2>&1 || true
echo -e "${BLUE}[egress] Создана сеть Podman: ${NET_NAME}${NC}"
fi
}
# Установка certbot (кросс‑дистрибутивно, с резервными сценариями)
install_certbot() {
detect_os_pm
echo -e "${BLUE}Проверка и установка certbot...${NC}"
# Уже установлен?
if command -v certbot >/dev/null 2>&1 || command -v certbot-3 >/dev/null 2>&1; then
echo -e "${GREEN}certbot уже установлен (${YELLOW}$(command -v certbot || command -v certbot-3)${GREEN})${NC}"
return 0
fi
case "$OS_FAMILY" in
debian)
# Основной вариант — пакет certbot
apt update -y >/dev/null 2>&1 || true
apt install -y certbot >/dev/null 2>&1 || true
if command -v certbot >/dev/null 2>&1; then
echo -e "${GREEN}Установлен certbot из apt${NC}"
return 0
fi
# Резерв — python3-certbot (в некоторых репозиториях)
apt install -y python3-certbot >/dev/null 2>&1 || true
if command -v certbot >/dev/null 2>&1; then
echo -e "${GREEN}Установлен certbot (python3-certbot) из apt${NC}"
return 0
fi
# Последняя линия — snapd
apt install -y snapd >/dev/null 2>&1 || true
systemctl enable snapd >/dev/null 2>&1 || true
systemctl start snapd >/dev/null 2>&1 || true
snap install core >/dev/null 2>&1 || true
snap refresh core >/dev/null 2>&1 || true
snap install --classic certbot >/dev/null 2>&1 || true
if [ -x "/snap/bin/certbot" ]; then
ln -sf /snap/bin/certbot /usr/bin/certbot >/dev/null 2>&1 || true
fi
;;
rhel)
# Попытка через dnf (может требовать EPEL в некоторых версиях)
$PM install -y certbot >/dev/null 2>&1 || true
if command -v certbot >/dev/null 2>&1; then
echo -e "${GREEN}Установлен certbot из dnf${NC}"
return 0
fi
$PM install -y python3-certbot >/dev/null 2>&1 || true
if command -v certbot >/dev/null 2>&1; then
echo -e "${GREEN}Установлен certbot (python3-certbot) из dnf${NC}"
return 0
fi
# Резерв — snapd
$PM install -y snapd >/dev/null 2>&1 || true
systemctl enable snapd >/dev/null 2>&1 || true
systemctl start snapd >/dev/null 2>&1 || true
snap install core >/dev/null 2>&1 || true
snap refresh core >/dev/null 2>&1 || true
snap install --classic certbot >/dev/null 2>&1 || true
if [ -x "/snap/bin/certbot" ]; then
ln -sf /snap/bin/certbot /usr/bin/certbot >/dev/null 2>&1 || true
fi
;;
*)
echo -e "${YELLOW}Неизвестная ОС — пропускаем установку certbot${NC}"
;;
esac
if command -v certbot >/dev/null 2>&1 || command -v certbot-3 >/dev/null 2>&1; then
echo -e "${GREEN}certbot установлен (${YELLOW}$(command -v certbot || command -v certbot-3)${GREEN})${NC}"
return 0
else
echo -e "${RED}Не удалось установить certbot автоматически. Установите вручную и перезапустите установку.${NC}"
return 1
fi
}
install_certbot || true
# Краткий отчёт о системной подготовке
print_prepare_report() {
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════${NC}"
echo -e "${BLUE} ОТЧЁТ О ПОДГОТОВКЕ СИСТЕМЫ ${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════${NC}"
echo "Дата/время: $(date)"
# Sysctl для rootless портов
UNPRIV_START=$(cat /proc/sys/net/ipv4/ip_unprivileged_port_start 2>/dev/null || echo "unknown")
echo "net.ipv4.ip_unprivileged_port_start: ${UNPRIV_START}"
# Лог‑драйвер контейнеров
CONF_FILE="/usr/share/containers/containers.conf"
if [ -f "$CONF_FILE" ]; then
LOG_DRIVER=$(grep -E '^\s*log_driver\s*=\s*' "$CONF_FILE" | sed -E 's/^[^=]+=\s*"?([^" ]+)"?.*/\1/' | tail -n1)
[ -z "$LOG_DRIVER" ] && LOG_DRIVER="(не задан)"
else
LOG_DRIVER="(файл не найден)"
fi
echo "containers.conf log_driver: ${LOG_DRIVER}"
# Fail2Ban
if command -v systemctl >/dev/null 2>&1; then
if systemctl is-active --quiet fail2ban 2>/dev/null; then
echo "Fail2Ban: активен"
elif systemctl is-enabled --quiet fail2ban 2>/dev/null; then
echo "Fail2Ban: установлен, но не активен"
else
echo "Fail2Ban: не установлен/не включён"
fi
fi
# Firewall
if command -v ufw >/dev/null 2>&1; then
UFW_STATUS=$(ufw status 2>/dev/null | head -n1)
echo "UFW: ${UFW_STATUS}"
elif command -v firewall-cmd >/dev/null 2>&1; then
if systemctl is-active --quiet firewalld 2>/dev/null; then
echo "firewalld: активен"
else
echo "firewalld: не активен"
fi
else
echo "Firewall: не обнаружен (UFW/firewalld)"
fi
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════${NC}"
}
print_prepare_report
# Функция для проверки надежности пароля
# Перемещена вверх, чтобы быть определённой до первого вызова
check_password_strength() {
local password="$1"
local errors=()
# Проверяем минимальную длину
if [ ${#password} -lt 8 ]; then
errors+=("Пароль слишком короткий (минимум 8 символов)")
fi
# Проверяем наличие заглавных букв
if [[ ! "$password" =~ [A-Z] ]]; then
errors+=("Отсутствуют заглавные буквы (A-Z)")
fi
# Проверяем наличие строчных букв
if [[ ! "$password" =~ [a-z] ]]; then
errors+=("Отсутствуют строчные буквы (a-z)")
fi
# Проверяем наличие цифр
if [[ ! "$password" =~ [0-9] ]]; then
errors+=("Отсутствуют цифры (0-9)")
fi
# Проверяем наличие специальных символов
if [[ ! "$password" =~ [^A-Za-z0-9] ]]; then
errors+=("Отсутствуют специальные символы (!@#%^&* и т.д.)")
fi
# Проверяем на простые пароли
local simple_passwords=("password" "123456" "qwerty" "admin" "root" "user" "test" "pass" "login")
local lower_password=$(echo "$password" | tr '[:upper:]' '[:lower:]')
for simple in "${simple_passwords[@]}"; do
if [[ "$lower_password" == *"$simple"* ]]; then
errors+=("Пароль содержит простые/распространенные комбинации")
break
fi
done
# Возвращаем результат
if [ ${#errors[@]} -eq 0 ]; then
return 0 # Пароль надежный
else
# Выводим ошибки
echo -e "${RED}Пароль не соответствует требованиям безопасности:${NC}"
for error in "${errors[@]}"; do
echo -e "${YELLOW} • $error${NC}"
done
return 1 # Пароль не надежный
fi
}
# Функция для поиска конфигурационных файлов
find_config_files() {
local search_dirs=(".")
local config_files=()
# Ищем файлы конфигурации в текущей директории
for dir in "${search_dirs[@]}"; do
if [ -d "$dir" ]; then
# Простой поиск файлов по шаблону ccs_install_*.cfg
while IFS= read -r file; do
if [ -f "$file" ]; then
local mtime=$(stat -c %Y "$file" 2>/dev/null || stat -f %m "$file" 2>/dev/null || echo "0")
config_files+=("$mtime $file")
fi
done < <(find "$dir" -maxdepth 1 -name "ccs_install_*.cfg" -type f 2>/dev/null)
fi
done
# Сортируем по времени и берем последние 10 файлов
if [ ${#config_files[@]} -gt 0 ]; then
printf '%s\n' "${config_files[@]}" | sort -rn -k1 | head -10 | cut -d' ' -f2-
fi
}
# Функция для выбора конфигурационного файла
select_config_file() {
local config_files
mapfile -t config_files < <(find_config_files)
if [ ${#config_files[@]} -eq 0 ]; then
echo "Конфигурационные файлы не найдены."
return 1
fi
echo -e "${BLUE}Найдены следующие конфигурационные файлы:${NC}"
echo "0) Не использовать конфигурацию (стандартная установка)"
local i=1
for file in "${config_files[@]}"; do
local file_date=$(stat -c %Y "$file" 2>/dev/null || date -r "$file" +%s 2>/dev/null || echo "0")
local formatted_date=$(date -d "@$file_date" "+%Y-%m-%d %H:%M:%S" 2>/dev/null || date -r "$file" "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "неизвестно")
local file_size=$(du -h "$file" 2>/dev/null | cut -f1 || echo "?")
echo "$i) $(basename "$file") (${formatted_date}, ${file_size})"
((i++))
done
echo ""
while true; do
read -p "Выберите конфигурационный файл (0-$((${#config_files[@]}))) [по умолчанию: 1]: " choice
choice=${choice:-1}
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 0 ] && [ "$choice" -le "${#config_files[@]}" ]; then
if [ "$choice" -eq 0 ]; then
echo "Выбрана стандартная установка без конфигурации"
CONFIG_INPUT=""
return 1
else
local selected_file="${config_files[$((choice-1))]}"
if [ -f "$selected_file" ] && [ -r "$selected_file" ]; then
echo "Выбран конфигурационный файл: $selected_file"
CONFIG_INPUT="$selected_file"
return 0
else
echo -e "${RED}Файл недоступен для чтения: $selected_file${NC}"
fi
fi
else
echo -e "${YELLOW}Введите число от 0 до ${#config_files[@]}${NC}"
fi
done
}
# Проверяем аргументы командной строки и ищем конфиги
CONFIG_INPUT=""
CONFIG_SOURCE_INFO=""
if [ $# -gt 0 ]; then
CONFIG_INPUT="$1"
if [ ! -f "$CONFIG_INPUT" ]; then
echo "ОШИБКА: Файл конфигурации '$CONFIG_INPUT' не найден!"
exit 1
fi
echo "Используется конфигурационный файл: $CONFIG_INPUT"
CONFIG_SOURCE_INFO="Конфигурация загружена из файла командной строки: $CONFIG_INPUT"
else
# Если конфиг не указан в аргументах, ищем автоматически
echo -e "${BLUE}Поиск конфигурационных файлов...${NC}"
if select_config_file; then
echo "Используется найденный конфигурационный файл: $CONFIG_INPUT"
CONFIG_SOURCE_INFO="Конфигурация загружена из выбранного файла: $CONFIG_INPUT"
echo ""
else
echo "Продолжаем без конфигурационного файла"
CONFIG_SOURCE_INFO="Новая установка без использования существующей конфигурации"
echo ""
fi
fi
# Функция для загрузки конфигурации
load_config() {
if [ -n "$CONFIG_INPUT" ] && [ -f "$CONFIG_INPUT" ]; then
echo "Загружаем настройки из конфигурационного файла..."
# Загружаем переменные из конфига, игнорируя комментарии и пустые строки
while IFS= read -r line; do
# Пропускаем комментарии и пустые строки
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "$line" ]]; then
continue
fi
# Экспортируем переменные
if [[ "$line" =~ ^[A-Z_][A-Z0-9_]*= ]]; then
eval "export $line"
fi
done < "$CONFIG_INPUT"
echo "✓ Конфигурация загружена"
echo ""
fi
}
# Функция для получения значения из загруженной конфигурации или окружения
get_config() {
local param_name="$1"
local default_val="$2"
local val
val=$(eval echo "\${$param_name}")
echo "${val:-$default_val}"
}
# Отключаем немедленный выход при ошибках для корректной обработки сигналов
set +e
trap 'set +e; cleanup_on_exit' SIGINT SIGTERM
set -e
# Путь к файлу с паролями (в директории скрипта)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PASSWORD_ENV_FILE="$SCRIPT_DIR/.ccs_install_env"
# Функция для генерации случайного пароля
generate_password() {
local length=${1:-16}
local password=""
# Используем комбинацию методов для генерации надежного пароля
if command -v openssl >/dev/null 2>&1; then
# Генерируем пароль с использованием всех символов включая спецсимволы
# Исключаем только проблематичные символы для bash и SQL: ' " \ ` $
password=$(openssl rand -base64 $((length * 2)) | tr -dc 'A-Za-z0-9!@#%^&*()_+=-[]{}|;:,.<>?' | head -c ${length})
# Если получился слишком короткий пароль, дополняем
while [ ${#password} -lt $length ]; do
additional=$(openssl rand -base64 10 | tr -dc 'A-Za-z0-9!@#%^&*()_+=-[]{}|;:,.<>?' | head -c $((length - ${#password})))
password="${password}${additional}"
done
elif [ -f /dev/urandom ]; then
# Альтернативный метод с /dev/urandom
password=$(tr -dc 'A-Za-z0-9!@#%^&*()_+=-[]{}|;:,.<>?' < /dev/urandom | head -c ${length})
else
# Fallback метод - менее безопасный, но с некоторыми спецсимволами
base=$(date +%s | sha256sum | base64 | head -c $((length - 4)))
# Добавляем гарантированные спецсимволы
password="${base}!@#$"
password=$(echo "$password" | head -c ${length})
fi
# Проверяем что пароль содержит минимум по одному символу каждого типа
if [[ ! "$password" =~ [A-Z] ]] || [[ ! "$password" =~ [a-z] ]] || [[ ! "$password" =~ [0-9] ]] || [[ ! "$password" =~ [^A-Za-z0-9] ]]; then
# Если не хватает разнообразия, принудительно добавляем нужные символы
password="${password:0:$((length-4))}A1!@"
# Перемешиваем символы для большей случайности
password=$(echo "$password" | fold -w1 | shuf | tr -d '\n')
fi
echo "$password"
}
# Функция для получения пароля из файла паролей
get_password_from_env() {
local username="$1"
local password_var="PASS_$(echo "$username" | tr '[:lower:]' '[:upper:]')"
if [ -f "$PASSWORD_ENV_FILE" ]; then
# Ищем пароль в файле
local password_line
password_line=$(grep "^${password_var}=" "$PASSWORD_ENV_FILE" 2>/dev/null | head -n1)
if [ -n "$password_line" ]; then
# Удаляем кавычки если они есть
local password="${password_line#*=}"
password="${password#\"}"
password="${password%\"}"
echo "$password"
return 0
fi
fi
return 1
}
# Функция для сохранения пароля в файл паролей
save_password_to_env() {
local username="$1"
local password="$2"
local password_var="PASS_$(echo "$username" | tr '[:lower:]' '[:upper:]')"
# Создаем файл если его нет
if [ ! -f "$PASSWORD_ENV_FILE" ]; then
cat > "$PASSWORD_ENV_FILE" << EOF
# ====================================================================
# CCS Deployment - Users password file
# Created: $(date)
# ====================================================================
#
# WARNING! This file contains user passwords.
# Ensure its security and limited access!
#
# Format: PASS_USERNAME=password
#
EOF
chmod 600 "$PASSWORD_ENV_FILE"
fi
# Проверяем есть ли уже пароль для этого пользователя
if grep -q "^${password_var}=" "$PASSWORD_ENV_FILE" 2>/dev/null; then
# Обновляем существующий пароль
sed -i "s/^${password_var}=.*/${password_var}=\"${password}\"/g" "$PASSWORD_ENV_FILE"
else
# Добавляем новый пароль
echo "${password_var}=\"${password}\"" >> "$PASSWORD_ENV_FILE"
fi
# Устанавливаем безопасные права доступа
chmod 600 "$PASSWORD_ENV_FILE"
}
# Создаем файл конфигурации для сохранения решений
if [ -n "$CONFIG_INPUT" ] && [ -f "$CONFIG_INPUT" ]; then
CONFIG_PATH="$CONFIG_INPUT"
CONFIG_FILE=$(basename "$CONFIG_PATH")
echo -e "${YELLOW}Будет обновлен существующий файл конфигурации: $CONFIG_PATH${NC}"
else
CONFIG_FILE="ccs_install_$(date +%Y%m%d_%H%M%S).cfg"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_PATH="$SCRIPT_DIR/$CONFIG_FILE"
echo -e "${GREEN}Конфигурация будет сохранена в НОВЫЙ файл: $CONFIG_PATH${NC}"
fi
# Функция для сохранения параметров в конфигурацию
save_config() {
local param_name="$1"
local param_value="$2"
local param_comment="$3"
# Немедленно обновляем значение переменной в текущей сессии
eval "export ${param_name}=\"${param_value}\""
# Создаем временный файл
local tmp_file=$(mktemp)
local found=0
if [ -f "$CONFIG_PATH" ]; then
# Читаем существующий файл и обновляем значение если параметр уже есть
while IFS= read -r line || [ -n "$line" ]; do
if [[ "$line" =~ ^${param_name}= ]]; then
# Мы нашли строку с параметром. Обновляем значение.
# Комментарий выше мы не трогаем, если он есть.
echo "${param_name}=\"${param_value}\"" >> "$tmp_file"
found=1
else
echo "$line" >> "$tmp_file"
fi
done < "$CONFIG_PATH"
fi
# Если параметр не был найден, добавляем его в конец с комментарием
if [ $found -eq 0 ]; then
if [ -s "$tmp_file" ]; then
echo "" >> "$tmp_file"
fi
if [ -n "$param_comment" ]; then
echo "# $param_comment" >> "$tmp_file"
fi
echo "${param_name}=\"${param_value}\"" >> "$tmp_file"
fi
# Заменяем оригинальный файл временным
cat "$tmp_file" > "$CONFIG_PATH"
rm "$tmp_file"
# Немедленно синхронизируем с диском для надежности
sync
}
# Функция для корректного завершения при прерывании
cleanup_on_exit() {
echo ""
echo -e "${YELLOW}Скрипт прерван пользователем или произошла ошибка${NC}"
# Обновляем статус в конфигурации
save_config "INSTALL_STATUS" "interrupted" "Installation status: interrupted - by user or error"
save_config "INTERRUPT_DATE" "$(date)" "Interrupt date and time"
# Копируем конфигурацию в домашнюю директорию если пользователь уже определен
if [ -n "$CCS_USER" ] && [ -n "$CCS_HOME" ]; then
FINAL_CONFIG_PATH="$CCS_HOME/$CONFIG_FILE"
cp "$CONFIG_PATH" "$FINAL_CONFIG_PATH" 2>/dev/null || true
chown "$CCS_USER:$CCS_USER" "$FINAL_CONFIG_PATH" 2>/dev/null || true
chmod 600 "$FINAL_CONFIG_PATH" 2>/dev/null || true
fi
echo -e "${BLUE}Partial configuration saved to: $CONFIG_PATH${NC}"
exit 1
}
# Загружаем конфигурацию если она указана (делаем это ДО инициализации/перезаписи файла CONFIG_PATH)
load_config
# Инициализация конфигурационного файла (если он еще не существует)
if [ ! -f "$CONFIG_PATH" ]; then
cat > "$CONFIG_PATH" << EOF
# ====================================================================
# CCS Deployment - Installation configuration
# Created: $(date)
# Host: $(hostname)
# ====================================================================
#
# This file contains all the decisions made during the installation.
# Can be used for automatic configuration in the future.
#
# $CONFIG_SOURCE_INFO
#
EOF
fi
echo -e "${GREEN}Конфигурация будет сохранена в: $CONFIG_PATH${NC}"
echo ""
# Логотип и приветствие
echo -e "${BLUE}"
cat << 'EOF'
╔══════════════════════════════════════════════════════════════════╗
║ CCS DEPLOYMENT MASTER ║
║ Универсальная система установки ║
║ (ROOT VERSION) ║
╚══════════════════════════════════════════════════════════════════╝
EOF
echo -e "${NC}"
echo -e "${GREEN}Добро пожаловать в мастер установки CCS Deployment!${NC}"
echo -e "${YELLOW}Этот скрипт настроит всю систему от начала до конца${NC}"
echo ""
# Проверяем что запущен от root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}ОШИБКА: Этот скрипт должен запускаться от root!${NC}"
echo -e "${YELLOW}Запустите: sudo ./first.sh${NC}"
exit 1
fi
echo -e "${GREEN}✓ Скрипт запущен от root${NC}"
# Сохраняем информацию об источнике конфигурации в новый файл
save_config "CONFIG_SOURCE_INFO" "$CONFIG_SOURCE_INFO" "Configuration source for this installation"
# Определяем ОС
if [ -f /etc/os-release ]; then
. /etc/os-release
OS_NAME="$ID"
OS_VERSION="$VERSION_ID"
echo -e "${BLUE}Обнаружена ОС: $PRETTY_NAME${NC}"
# Сохраняем информацию об ОС
save_config "OS_NAME" "$OS_NAME" "OS Identifier"
save_config "OS_VERSION" "$OS_VERSION" "OS Version"
save_config "OS_PRETTY_NAME" "$PRETTY_NAME" "OS Pretty Name"
else
echo -e "${RED}Не удалось определить операционную систему${NC}"
exit 1
fi
# Проверяем базовые инструменты и устанавливаем если нужно
echo -e "${BLUE}Проверка и установка базовых зависимостей...${NC}"
REQUIRED_TOOLS=("git" "curl" "wget" "vim")
MISSING_TOOLS=()
for tool in "${REQUIRED_TOOLS[@]}"; do
if ! command -v "$tool" >/dev/null 2>&1; then
MISSING_TOOLS+=("$tool")
fi
done
if [ ${#MISSING_TOOLS[@]} -gt 0 ]; then
echo -e "${YELLOW}Устанавливаем недостающие пакеты: ${MISSING_TOOLS[*]}${NC}"
case "$OS_NAME" in
"debian"|"ubuntu")
apt update
apt install -y git curl wget vim sudo
;;
"rhel"|"centos"|"rocky"|"fedora"|"almalinux")
if command -v dnf >/dev/null 2>&1; then
dnf install -y git curl wget vim sudo
else
yum install -y git curl wget vim sudo
fi
;;
*)
echo -e "${RED}Неподдерживаемая ОС для автоматической установки пакетов${NC}"
echo -e "${YELLOW}Установите вручную: git curl wget vim${NC}"
exit 1
;;
esac
echo -e "${GREEN}Базовые инструменты установлены${NC}"
else
echo -e "${GREEN}✓ Все базовые инструменты уже установлены${NC}"
fi
# Создание или выбор пользователя для CCS
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════${NC}"
echo -e "${BLUE} НАСТРОЙКА ПОЛЬЗОВАТЕЛЯ ${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════${NC}"
echo ""
# Показываем существующих пользователей (исключаем системных)
echo -e "${BLUE}Существующие пользователи в системе:${NC}"
existing_users_array=()
while IFS= read -r user; do
existing_users_array+=("$user")
done < <(getent passwd | awk -F: '$3 >= 1000 && $3 < 65534 {print $1}')
if [ ${#existing_users_array[@]} -gt 0 ]; then
for i in "${!existing_users_array[@]}"; do
echo "$((i+1))) ${existing_users_array[$i]}"
done
else
echo -e "${YELLOW}Обычных пользователей не найдено${NC}"
fi
echo ""
while true; do
# Используем значение из конфига как значение по умолчанию
default_user_choice="${USER_CHOICE:-}"
default_ccs_user="${CCS_USER:-}"
if [ -n "$default_user_choice" ] && [ -n "$default_ccs_user" ]; then
# Если есть сохраненный пользователь, проверяем его существование
if [ "$default_user_choice" = "0" ]; then
# Был создан новый пользователь
if id "$default_ccs_user" &>/dev/null; then
echo -e "${GREEN}Найден пользователь из конфигурации: $default_ccs_user${NC}"
echo -e "${GREEN}Выберите действие:${NC}"
echo "0) Создать нового пользователя для CCS"
if [ ${#existing_users_array[@]} -gt 0 ]; then
echo "1-${#existing_users_array[@]}) Использовать существующего пользователя (номер из списка выше)"
fi
echo ""
if [ ${#existing_users_array[@]} -gt 0 ]; then
read -p "Выберите действие (0-${#existing_users_array[@]}) [Enter - использовать $default_ccs_user, или новый выбор]: " user_choice
else
read -p "Выберите действие (0) [Enter - использовать $default_ccs_user, или новый выбор]: " user_choice
fi
user_choice=${user_choice:-$default_user_choice}
if [ "$user_choice" = "$default_user_choice" ] || [ -z "$user_choice" ]; then
# Используем сохраненного пользователя
CCS_USER="$default_ccs_user"
# Сохраняем "0", так как в конфиге было "0" (создание нового)
save_config "USER_CHOICE" "0" "Action choice: 0 - user from config was newly created"
save_config "CCS_USER" "$CCS_USER" "Username used"
save_config "USER_CREATION" "existing" "User creation type: already created in previous run"
# Убеждаемся что пользователь в нужных группах
usermod -aG sudo "$CCS_USER" 2>/dev/null || usermod -aG wheel "$CCS_USER" 2>/dev/null || true
usermod -aG adm "$CCS_USER" 2>/dev/null || true
loginctl enable-linger "$CCS_USER" 2>/dev/null || true
echo -e "${GREEN}✓ Будет использован пользователь из конфига: $CCS_USER${NC}"
break
fi
else
echo -e "${YELLOW}Пользователь из конфига ($default_ccs_user) больше не существует${NC}"
default_user_choice=""
fi
else
# Был выбран существующий пользователь
if id "$default_ccs_user" &>/dev/null; then
# Находим текущий индекс пользователя в списке
current_idx=0
for i in "${!existing_users_array[@]}"; do
if [ "${existing_users_array[$i]}" = "$default_ccs_user" ]; then
current_idx=$((i+1))
break
fi
done
echo -e "${GREEN}Найден пользователь из конфигурации: $default_ccs_user${NC}"
echo -e "${GREEN}Выберите действие:${NC}"
echo "0) Создать нового пользователя для CCS"
if [ ${#existing_users_array[@]} -gt 0 ]; then
echo "1-${#existing_users_array[@]}) Использовать существующего пользователя (номер из списка выше)"
fi
echo ""
if [ ${#existing_users_array[@]} -gt 0 ]; then
read -p "Выберите действие (0-${#existing_users_array[@]}) [Enter - использовать $default_ccs_user, или новый выбор]: " user_choice
else
read -p "Выберите действие (0) [Enter - использовать $default_ccs_user, или новый выбор]: " user_choice
fi
user_choice=${user_choice:-$default_user_choice}
if [ "$user_choice" = "$default_user_choice" ] || [ -z "$user_choice" ]; then
# Используем сохраненного пользователя
CCS_USER="$default_ccs_user"
# Сохраняем актуальный индекс (если он изменился в списке)
if [ $current_idx -gt 0 ]; then
save_config "USER_CHOICE" "$current_idx" "Action choice: index of existing user"
else
save_config "USER_CHOICE" "0" "Action choice: fallback to 0 (user not in list)"
fi
save_config "CCS_USER" "$CCS_USER" "Username used"
save_config "USER_CREATION" "existing" "User creation type: existing user from previous run"
# Убеждаемся что пользователь в нужных группах
usermod -aG sudo "$CCS_USER" 2>/dev/null || usermod -aG wheel "$CCS_USER" 2>/dev/null || true
usermod -aG adm "$CCS_USER" 2>/dev/null || true
loginctl enable-linger "$CCS_USER" 2>/dev/null || true
echo -e "${GREEN}✓ Будет использован пользователь из конфига: $CCS_USER${NC}"
break
fi
else
echo -e "${YELLOW}Пользователь из конфига ($default_ccs_user) больше не существует${NC}"
default_user_choice=""
fi
fi
fi
# Стандартная логика выбора если нет валидного пользователя из конфига
if [ -z "$user_choice" ]; then
# Определяем значение по умолчанию
final_default_choice="0"
if [ -n "$default_user_choice" ] && [[ "$default_user_choice" =~ ^[0-9]+$ ]]; then
# Проверяем что индекс существующего пользователя в допустимых пределах
if [ "$default_user_choice" -le "${#existing_users_array[@]}" ]; then
final_default_choice="$default_user_choice"
fi
fi
echo -e "${GREEN}Выберите действие:${NC}"
echo "0) Создать нового пользователя для CCS"
if [ ${#existing_users_array[@]} -gt 0 ]; then
echo "1-${#existing_users_array[@]}) Использовать существующего пользователя (номер из списка выше)"
fi
echo ""
if [ ${#existing_users_array[@]} -gt 0 ]; then
read -p "Выберите действие (0-${#existing_users_array[@]}) [по умолчанию: $final_default_choice]: " user_choice
user_choice=${user_choice:-$final_default_choice}
else
read -p "Выберите действие (0) [по умолчанию: $final_default_choice]: " user_choice
user_choice=${user_choice:-$final_default_choice}
fi
fi
# Проверяем корректность выбора
if [[ "$user_choice" =~ ^[0-9]+$ ]]; then
if [ "$user_choice" -eq 0 ]; then
# Создание нового пользователя
save_config "USER_CHOICE" "$user_choice" "Action choice: 0 - create new user, 1+ - use existing by index"
while true; do
# Используем значение из конфига как значение по умолчанию
default_ccs_user="${CCS_USER:-ccsuser}"
read -p "Введите имя нового пользователя [по умолчанию: $default_ccs_user]: " CCS_USER
CCS_USER=${CCS_USER:-$default_ccs_user}
# Проверяем что пользователь не существует
if id "$CCS_USER" &>/dev/null; then
echo -e "${YELLOW}Пользователь $CCS_USER уже существует${NC}"
else
break
fi
done
# Ввод пароля с новой логикой генерации и сохранения
CCS_PASSWORD=""
# Сначала проверяем есть ли уже сохраненный пароль для этого пользователя
saved_password=""
if saved_password=$(get_password_from_env "$CCS_USER" 2>/dev/null); then
echo -e "${GREEN}✓ Найден сохраненный пароль для пользователя $CCS_USER${NC}"
CCS_PASSWORD="$saved_password"
else
# Проверяем есть ли пароль из конфига
config_password="${CCS_PASSWORD:-}"
if [ -n "$config_password" ]; then
echo -e "${GREEN}Найден пароль в конфигурационном файле${NC}"
CCS_PASSWORD="$config_password"
else
# Предлагаем ввести пароль или сгенерировать автоматически
echo -e "${BLUE}Настройка пароля для пользователя $CCS_USER:${NC}"
echo "1) Ввести пароль вручную"
echo "2) Сгенерировать автоматически (рекомендуется)"
read -p "Выберите вариант (1-2) [по умолчанию: 2 - автогенерация]: " pass_choice
pass_choice=${pass_choice:-2}
case "$pass_choice" in
1)
# Цикл ввода пароля с проверкой надежности
while true; do
read -s -p "Введите пароль для пользователя $CCS_USER: " CCS_PASSWORD
echo
if [ -z "$CCS_PASSWORD" ]; then
echo -e "${YELLOW}Пустой пароль, генерируем автоматически...${NC}"
CCS_PASSWORD=$(generate_password 16)
break
fi
# Проверяем надежность введенного пароля
if check_password_strength "$CCS_PASSWORD"; then
echo -e "${GREEN}✓ Пароль соответствует требованиям безопасности${NC}"
break
else
echo ""
echo -e "${BLUE}Рекомендации для надежного пароля:${NC}"
echo -e "${YELLOW} • Минимум 8 символов${NC}"
echo -e "${YELLOW} • Заглавные буквы (A-Z)${NC}"
echo -e "${YELLOW} • Строчные буквы (a-z)${NC}"
echo -e "${YELLOW} • Цифры (0-9)${NC}"
echo -e "${YELLOW} • Специальные символы (!@#%^&* и т.д.)${NC}"
echo ""
while true; do
read -p "Что делать? (1 - ввести другой пароль, 2 - оставить как есть, 3 - сгенерировать автоматически) [по умолчанию: 1]: " action
action=${action:-1}
case "$action" in
1)
echo -e "${BLUE}Введите новый пароль...${NC}"
break
;;
2)
echo -e "${YELLOW}⚠ ВНИМАНИЕ: Используется пароль, не соответствующий требованиям безопасности!${NC}"
break 2 # Выходим из обоих циклов
;;
3)
echo -e "${BLUE}Генерируем надежный пароль...${NC}"
CCS_PASSWORD=$(generate_password 16)
break 2 # Выходим из обоих циклов
;;
*)
echo -e "${YELLOW}Введите 1, 2 или 3${NC}"
;;
esac
done
fi
done
;;
2|*)
echo -e "${BLUE}Генерируем случайный пароль...${NC}"
CCS_PASSWORD=$(generate_password 16)
;;
esac
fi
# Сохраняем пароль в файл паролей
save_password_to_env "$CCS_USER" "$CCS_PASSWORD"
echo -e "${GREEN}✓ Пароль сохранен в файл $PASSWORD_ENV_FILE${NC}"
echo -e "${YELLOW}ВНИМАНИЕ: Пароль доступен в файле $PASSWORD_ENV_FILE${NC}"
fi
# Поиск свободного UID
min_uid=1000
max_uid=2000
next_uid=$min_uid
while [ $next_uid -le $max_uid ]; do
if ! getent passwd $next_uid &>/dev/null; then
break
fi
next_uid=$((next_uid + 1))
done
if [ $next_uid -gt $max_uid ]; then
echo -e "${RED}Не удалось найти свободный UID${NC}"
exit 1
fi
echo -e "${BLUE}Создание пользователя $CCS_USER с UID $next_uid...${NC}"
# Создаем пользователя
groupadd -g $next_uid $CCS_USER
useradd -m -u $next_uid -g $CCS_USER -s /bin/bash "$CCS_USER"
echo -e "$CCS_PASSWORD\n$CCS_PASSWORD" | passwd "$CCS_USER"
# Добавляем в нужные группы
usermod -aG sudo "$CCS_USER" 2>/dev/null || usermod -aG wheel "$CCS_USER" 2>/dev/null || true
usermod -aG adm "$CCS_USER" 2>/dev/null || true
# Включаем lingering для systemd
loginctl enable-linger "$CCS_USER" 2>/dev/null || true
echo -e "${GREEN}✓ Пользователь $CCS_USER создан${NC}"
# Сохраняем в конфигурацию
save_config "CCS_USER" "$CCS_USER" "Created username"
# Пароль сохраняется как пустое поле для возможности ручного заполнения
save_config "CCS_PASSWORD" "" "User password (fill manually for automation)"
save_config "CCS_UID" "$next_uid" "User UID"
save_config "USER_CREATION" "new" "User creation type: new - created new, existing - used existing"
break
elif [ "$user_choice" -ge 1 ] && [ "$user_choice" -le "${#existing_users_array[@]}" ]; then
# Использование существующего пользователя по номеру
CCS_USER="${existing_users_array[$((user_choice-1))]}"
save_config "USER_CHOICE" "$user_choice" "Action choice: 0 - create new user, 1+ - use existing by index"
if id "$CCS_USER" &>/dev/null; then
echo -e "${GREEN}✓ Будет использован пользователь: $CCS_USER${NC}"
# Убеждаемся что пользователь в нужных группах
usermod -aG sudo "$CCS_USER" 2>/dev/null || usermod -aG wheel "$CCS_USER" 2>/dev/null || true
usermod -aG adm "$CCS_USER" 2>/dev/null || true
loginctl enable-linger "$CCS_USER" 2>/dev/null || true
save_config "CCS_USER" "$CCS_USER" "Username used"
save_config "USER_CREATION" "existing" "User creation type: new - created new, existing - used existing"
break
else
echo -e "${RED}Пользователь $CCS_USER не найден${NC}"
fi
else
if [ ${#existing_users_array[@]} -gt 0 ]; then
echo -e "${YELLOW}Введите число от 0 до ${#existing_users_array[@]}${NC}"
else
echo -e "${YELLOW}Введите 0${NC}"
fi
fi
else
if [ ${#existing_users_array[@]} -gt 0 ]; then
echo -e "${YELLOW}Введите число от 0 до ${#existing_users_array[@]}${NC}"
else
echo -e "${YELLOW}Введите 0${NC}"
fi
fi
done
# Настройка переменных путей
CCS_HOME=$(eval echo ~$CCS_USER)
DEPLOY_DIR="$CCS_HOME/CCS.Deployment"
# Добавление SSH ключа
add_ssh_key_to_user "$CCS_USER" "$(get_config "USER_CREATION")"
BACKUP_DIR="$CCS_HOME/CCS.Deployment.backup.$(date +%Y%m%d_%H%M%S)"
echo -e "${BLUE}Домашняя директория пользователя: $CCS_HOME${NC}"
# Rootless режим: отключаем установку rootful-компонентов и диагностику
# --------------------------------------------------------------------
# Rootless egress (nftables per-user) — установка сервиса/таймера (идемпотентно)
# --------------------------------------------------------------------
find_repo_root_for_rootless_egress() {
local cand
# 1) Относительно расположения текущего скрипта
cand="$(cd "$(dirname "$0")/.." 2>/dev/null && pwd 2>/dev/null)"
if [ -n "$cand" ] && [ -f "$cand/prepareOS/ccs_egress_guard_rootless.sh" ]; then
echo "$cand"; return 0
fi
# 2) DEPLOY_DIR, если определён
if [ -n "$DEPLOY_DIR" ] && [ -f "$DEPLOY_DIR/prepareOS/ccs_egress_guard_rootless.sh" ]; then
echo "$DEPLOY_DIR"; return 0
fi
# 3) Текущая директория (на случай запуска из корня репозитория)
if [ -f "prepareOS/ccs_egress_guard_rootless.sh" ]; then
echo "$(pwd)"; return 0
fi
echo ""; return 1
}
install_rootless_egress_timer() {
local repo_root
repo_root="$(find_repo_root_for_rootless_egress || true)"
if [ -z "$repo_root" ]; then
echo -e "${YELLOW}[rootless-egress] Источники скриптов не найдены. Пропуск установки таймера.${NC}"
return 0
fi
local BIN_DIR="/usr/local/bin"
local SYS_UNIT_DIR="/etc/systemd/system"
# Скрипты
install -Dm755 "$repo_root/prepareOS/ccs_egress_guard_rootless.sh" "$BIN_DIR/ccs_egress_guard_rootless.sh"
install -Dm755 "$repo_root/prepareOS/ccs_egress_guard_rootless_all.sh" "$BIN_DIR/ccs_egress_guard_rootless_all.sh"
# systemd units (service + timer)
install -Dm644 "$repo_root/prepareOS/systemd/ccs-rootless-egress.service" "$SYS_UNIT_DIR/ccs-rootless-egress.service"
install -Dm644 "$repo_root/prepareOS/systemd/ccs-rootless-egress.timer" "$SYS_UNIT_DIR/ccs-rootless-egress.timer"
systemctl daemon-reload >/dev/null 2>&1 || true
# Включаем таймер и запускаем немедленно (идемпотентно)
systemctl enable --now ccs-rootless-egress.timer >/dev/null 2>&1 || true
echo -e "${BLUE}[rootless-egress] Установлены скрипты и включён таймер ccs-rootless-egress.timer${NC}"
}
# Выполним установку таймера прямо сейчас (идемпотентно):
install_rootless_egress_timer
# === Запрос: Получить/Обновить CCS.Deployment сейчас? (по умолчанию Да) ===
DO_REPO=1
echo ""
echo -e "${BLUE}Получить/обновить репозиторий CCS.Deployment сейчас? (Y/n) [по умолчанию: Y]${NC}"
read -r WANT_REPO
if [[ $WANT_REPO =~ ^[Nn]$ ]]; then
DO_REPO=0
echo -e "${YELLOW}Шаг получения/обновления репозитория будет пропущен по вашему выбору.${NC}"
fi
if [ "$DO_REPO" = 1 ]; then
# Если директория существует и это уже git-репозиторий — не трогаем (никаких бэкапов/перемещений)
if [ -d "$DEPLOY_DIR/.git" ]; then
echo -e "${GREEN}Найдена существующая установка CCS.Deployment в $DEPLOY_DIR — клонирование будет пропущено${NC}"
else
# Директория есть, но это не git-репозиторий — сделаем резервную копию и подготовим место
if [ -d "$DEPLOY_DIR" ]; then
echo -e "${YELLOW}Обнаружен каталог $DEPLOY_DIR без .git${NC}"
echo -e "${BLUE}Создаем резервную копию в: $BACKUP_DIR${NC}"
mv "$DEPLOY_DIR" "$BACKUP_DIR"
chown -R "$CCS_USER:$CCS_USER" "$BACKUP_DIR"
fi
fi
fi
# Выбор репозитория (как в оригинале)
if [ "$DO_REPO" = 1 ]; then
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════${NC}"
echo -e "${BLUE} ВЫБОР РЕПОЗИТОРИЯ ${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════${NC}"
echo ""
echo -e "${BLUE}Выберите репозиторий для клонирования:${NC}"
# Выводим репозитории в правильном порядке
echo "1) ${REPO_DESCRIPTIONS[1]}: ${REPO_URLS[1]}"
echo "2) ${REPO_DESCRIPTIONS[2]}: ${REPO_URLS[2]}"
echo "3) Ввести URL вручную"
while true; do
# Используем значение из конфига как значение по умолчанию
default_repo_choice="${REPO_CHOICE:-}"
default_repo_url="${REPO_URL:-}"
default_repo_service="${REPO_SERVICE:-}"
if [ -n "$default_repo_choice" ] && [ -n "$default_repo_url" ]; then
# Если есть сохраненный выбор репозитория
if [ "$default_repo_choice" = "1" ] || [ "$default_repo_choice" = "2" ]; then
# Проверяем соответствие URL из конфига с текущим URL для данного выбора
if [ "$default_repo_url" = "${REPO_URLS[$default_repo_choice]}" ]; then
echo -e "${GREEN}Найден репозиторий из конфигурации: ${REPO_NAMES[$default_repo_choice]}${NC}"
read -p "Выберите вариант (1-3) [Enter - использовать ${REPO_NAMES[$default_repo_choice]}, или новый выбор]: " repo_choice
repo_choice=${repo_choice:-$default_repo_choice}
if [ "$repo_choice" = "$default_repo_choice" ]; then
REPO_URL="$default_repo_url"
# Проставим выбор и сервис, если ранее не сохранялся
repo_choice="$default_repo_choice"
if [ -z "$default_repo_service" ]; then
if [ "$repo_choice" = "1" ] || [ "$repo_choice" = "2" ]; then
REPO_SERVICE="${REPO_NAMES[$repo_choice]}"
else
REPO_SERVICE="Custom"
fi
else
REPO_SERVICE="$default_repo_service"
fi
echo -e "${GREEN}✓ Используется репозиторий из конфига: ${REPO_NAMES[$default_repo_choice]}${NC}"
break
fi
else
echo -e "${YELLOW}URL репозитория изменился с момента создания конфига${NC}"
fi
elif [ "$default_repo_choice" = "3" ]; then
echo -e "${GREEN}Найден пользовательский репозиторий из конфигурации: $default_repo_url${NC}"
read -p "Выберите вариант (1-3) [Enter - использовать $default_repo_url, или новый выбор]: " repo_choice
repo_choice=${repo_choice:-3}
if [ "$repo_choice" = "3" ]; then
REPO_URL="$default_repo_url"
# Проставим выбор и сервис для кастомного URL
repo_choice=3
REPO_SERVICE="Custom"
echo -e "${GREEN}✓ Используется репозиторий из конфига: $default_repo_url${NC}"
break
fi
fi
fi
# Стандартная логика выбора с рекомендуемым значением по умолчанию
if [ -n "$default_repo_choice" ] && [ -z "$repo_choice" ]; then
read -p "Выберите вариант (1-3) [по умолчанию из конфига: $default_repo_choice]: " repo_choice
repo_choice=${repo_choice:-$default_repo_choice}
else
# Рекомендуемое значение по умолчанию - GitFlic для России
read -p "Выберите вариант (1-3) [по умолчанию: 1 - GitFlic (рекомендуется)]: " repo_choice
repo_choice=${repo_choice:-1}
fi
case $repo_choice in
1)
REPO_URL="${REPO_URLS[1]}"
REPO_SERVICE="${REPO_NAMES[1]}"
break
;;
2)
REPO_URL="${REPO_URLS[2]}"
REPO_SERVICE="${REPO_NAMES[2]}"
break
;;
3)
# Используем значение из конфига для custom URL
default_repo_url="${REPO_URL:-}"
if [ -n "$default_repo_url" ]; then
read -p "Введите URL репозитория [по умолчанию из конфига: $default_repo_url]: " REPO_URL
REPO_URL=${REPO_URL:-$default_repo_url}
else
read -p "Введите URL репозитория: " REPO_URL
fi
REPO_SERVICE="Custom"
break
;;
*)
echo -e "${YELLOW}Введите число от 1 до 3${NC}"
;;
esac
done
# Сохраняем выбор репозитория
# Подстраховка: если repo_choice не установлен, вычислим его по REPO_URL
if [ -z "$repo_choice" ]; then
if [ "$REPO_URL" = "${REPO_URLS[1]}" ]; then
repo_choice=1
REPO_SERVICE="${REPO_NAMES[1]}"
elif [ "$REPO_URL" = "${REPO_URLS[2]}" ]; then
repo_choice=2
REPO_SERVICE="${REPO_NAMES[2]}"
else
repo_choice=3
[ -z "$REPO_SERVICE" ] && REPO_SERVICE="Custom"
fi
fi
save_config "REPO_CHOICE" "$repo_choice" "Repository choice: 1 - GitFlic, 2 - GitHub, 3 - Custom URL"
save_config "REPO_URL" "$REPO_URL" "Repository URL"
save_config "REPO_SERVICE" "$REPO_SERVICE" "Repository service"
# Выбор ветки с улучшенной логикой
default_branch_from_config="${BRANCH:-}"
if [ -n "$default_branch_from_config" ]; then
echo -e "${GREEN}Найдена ветка из конфигурации: $default_branch_from_config${NC}"
read -p "Введите название ветки [Enter - использовать $default_branch_from_config, или введите новое]: " BRANCH
BRANCH=${BRANCH:-$default_branch_from_config}
if [ "$BRANCH" = "$default_branch_from_config" ]; then
echo -e "${GREEN}✓ Используется ветка из конфига: $BRANCH${NC}"
fi
else
default_branch="master"
read -p "Введите название ветки [по умолчанию: $default_branch]: " BRANCH
BRANCH=${BRANCH:-$default_branch}
fi
# Сохраняем выбор ветки
save_config "BRANCH" "$BRANCH" "Selected repository branch"
# Сохраняем пути
save_config "CCS_HOME" "$CCS_HOME" "User home directory"
save_config "DEPLOY_DIR" "$DEPLOY_DIR" "CCS.Deployment installation directory"
save_config "PREPARE_DIR" "$PREPARE_DIR" "Management scripts directory"
echo -e "${BLUE}Настройки клонирования:${NC}"
echo -e "Пользователь: ${YELLOW}$CCS_USER${NC}"
echo -e "Репозиторий: ${YELLOW}$REPO_URL${NC}"
echo -e "Ветка: ${YELLOW}$BRANCH${NC}"
echo -e "Директория: ${YELLOW}$DEPLOY_DIR${NC}"
# Подтверждение
# Используем значение из конфига для значения по умолчанию
default_confirm=$(get_config "INSTALL_CONFIRM" "y")
prompt_confirm="Y/n"
[[ $default_confirm =~ ^[Nn]$ ]] && prompt_confirm="y/N"
read -p "Продолжить? ($prompt_confirm) [по умолчанию: $default_confirm]: " confirm
confirm=${confirm:-$default_confirm}
# Сохраняем решение в конфигурацию
save_config "INSTALL_CONFIRM" "$confirm" "Installation confirmation before clone"
if [[ $confirm =~ ^[Nn]$ ]]; then
echo -e "${YELLOW}Установка отменена${NC}"
exit 0
fi # DO_REPO
# Клонирование репозитория
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════${NC}"
echo -e "${BLUE} КЛОНИРОВАНИЕ РЕПОЗИТОРИЯ ${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════${NC}"
echo ""
# Создаем .ssh директорию для пользователя
mkdir -p "$CCS_HOME/.ssh"
chmod 700 "$CCS_HOME/.ssh"
chown "$CCS_USER:$CCS_USER" "$CCS_HOME/.ssh"
# Выбор метода авторизации
AUTH_METHOD=""
# Генерируем ключ для JSON хранилища токенов
GIT_AUTH_KEY="${REPO_URL}__${BRANCH}"
GIT_TOKENS_FILE="$CCS_HOME/.git_tokens.json"
# Пытаемся получить существующую авторизацию из JSON если файл существует
SAVED_AUTH_TYPE=""
SAVED_USERNAME=""
SAVED_TOKEN=""
# Функция для чтения из JSON через python (так как мы еще не склонировали репозиторий с утилитами, но python3 должен быть)
get_json_val() {
local key=$1
python3 -c "import sys, json; print(json.load(sys.stdin).get('$key', ''))" 2>/dev/null
}
if [ -f "$GIT_TOKENS_FILE" ]; then
AUTH_DATA=$(python3 -c "import sys, json; data=json.load(open('$GIT_TOKENS_FILE')); print(json.dumps(data.get('$GIT_AUTH_KEY', {})))" 2>/dev/null)
if [ -n "$AUTH_DATA" ] && [ "$AUTH_DATA" != "{}" ]; then
SAVED_AUTH_TYPE=$(echo "$AUTH_DATA" | get_json_val "auth_type")
SAVED_USERNAME=$(echo "$AUTH_DATA" | get_json_val "username")
SAVED_TOKEN=$(echo "$AUTH_DATA" | get_json_val "token")
fi
fi
# Если данных нет в JSON, пробуем взять из конфига .cfg (обратная совместимость)
if [ -z "$SAVED_AUTH_TYPE" ]; then
case "${AUTH_METHOD_CHOICE:-}" in
1) SAVED_AUTH_TYPE="ssh" ;;
2) SAVED_AUTH_TYPE="public" ;;
3) SAVED_AUTH_TYPE="token" ;;
esac
SAVED_USERNAME="${GIT_USERNAME:-}"
SAVED_TOKEN="${GIT_TOKEN:-}"
fi
if [ -n "$SAVED_AUTH_TYPE" ]; then
echo -e "${BLUE}Обнаружены сохраненные данные авторизации:${NC}"
echo -e "Метод: ${YELLOW}$SAVED_AUTH_TYPE${NC}"
if [ "$SAVED_AUTH_TYPE" = "token" ]; then
echo -e "Пользователь: ${YELLOW}$SAVED_USERNAME${NC}"
fi
echo ""
read -p "Использовать сохраненные данные? (Y/n) [по умолчанию: Y]: " use_saved
use_saved=${use_saved:-y}
if [[ $use_saved =~ ^[Yy]$ ]]; then
AUTH_METHOD="$SAVED_AUTH_TYPE"
GIT_USERNAME="$SAVED_USERNAME"
GIT_TOKEN="$SAVED_TOKEN"
echo -e "${GREEN}✓ Используются сохраненные данные авторизации${NC}"
else
# Если пользователь отказался использовать сохраненные данные, сбрасываем их
GIT_USERNAME=""
GIT_TOKEN=""
SAVED_AUTH_TYPE=""
fi
fi
if [ -z "$AUTH_METHOD" ]; then
echo -e "${BLUE}Выберите метод авторизации для репозитория:${NC}"
echo "1) Публичный доступ (без авторизации)"
echo "2) SSH ключ (рекомендуется)"
echo "3) Токен"
# Определяем дефолтный выбор
default_choice="3"
case "$SAVED_AUTH_TYPE" in
"public") default_choice="1" ;;
"ssh") default_choice="2" ;;
"token") default_choice="3" ;;
esac
read -p "Ваш выбор [$default_choice]: " auth_choice
auth_choice=${auth_choice:-$default_choice}
case $auth_choice in
1) AUTH_METHOD="public" ;;
2) AUTH_METHOD="ssh" ;;
3) AUTH_METHOD="token" ;;
*) AUTH_METHOD="public" ;;
esac
fi
# Сохраняем выбор метода в конфиг .cfg для совместимости
case "$AUTH_METHOD" in
"ssh") save_config "AUTH_METHOD_CHOICE" "1" "Auth method: SSH" ;;
"public") save_config "AUTH_METHOD_CHOICE" "2" "Auth method: Public" ;;
"token") save_config "AUTH_METHOD_CHOICE" "3" "Auth method: Token" ;;
esac
# Если выбран токен, запрашиваем данные если их еще нет
if [ "$AUTH_METHOD" = "token" ] && [ -z "$GIT_TOKEN" ]; then
echo -e "${BLUE}Настройка авторизации по токену...${NC}"
# Инструкции
if [[ "$REPO_URL" == *"github.com"* ]]; then
echo -e "${YELLOW}Получите 'Personal Access Token' (classic) с правами 'repo' в настройках GitHub.${NC}"
elif [[ "$REPO_URL" == *"gitflic.ru"* ]]; then
echo -e "${YELLOW}Получите 'Токен доступа' в настройках вашего проекта на GitFlic (Настройки -> Токены доступа).${NC}"
fi
# Если ранее был введен логин, предлагаем его. Иначе пусто.
if [ -n "$GIT_USERNAME" ]; then
read -p "Введите имя пользователя (логин): [${GIT_USERNAME}] " git_username
GIT_USERNAME=${git_username:-${GIT_USERNAME}}
else
read -p "Введите имя пользователя (логин): " GIT_USERNAME
fi
if [ -n "$GIT_TOKEN" ]; then
echo -e "${GREEN}Обнаружен сохраненный токен${NC}"
read -p "Использовать его? (Y/n) [по умолчанию: Y]: " use_saved_token
use_saved_token=${use_saved_token:-y}
if [[ ! $use_saved_token =~ ^[Yy]$ ]]; then
read -sp "Введите НОВЫЙ токен: " git_token
echo ""
GIT_TOKEN="$git_token"
fi
else
read -sp "Введите токен: " git_token
echo ""
GIT_TOKEN="$git_token"
fi
if [ -z "$GIT_USERNAME" ] || [ -z "$GIT_TOKEN" ]; then
echo -e "${RED}Ошибка: Имя пользователя и токен обязательны для этого метода.${NC}"
exit 1
fi
fi
# Сохраняем переменные в конфиг .cfg
if [ "$AUTH_METHOD" = "token" ]; then
save_config "GIT_USERNAME" "$GIT_USERNAME" "Username for token auth"
if [ -n "$GIT_TOKEN" ]; then
save_config "GIT_TOKEN" "$GIT_TOKEN" "Deployment token"
fi
fi
# Подготавливаем JSON для сохранения в .git_tokens.json позже
AUTH_JSON=$(python3 -c "import json, sys; print(json.dumps({'auth_type': sys.argv[1], 'username': sys.argv[2], 'token': sys.argv[3]}))" "$AUTH_METHOD" "$GIT_USERNAME" "$GIT_TOKEN")
# Выполняем клонирование в зависимости от выбранного метода
if [ "$AUTH_METHOD" = "token" ]; then
echo -e "${BLUE}Попытка клонирования через HTTPS с токеном...${NC}"
if [ -d "$DEPLOY_DIR/.git" ]; then
echo -e "${GREEN}Репозиторий уже существует: ${DEPLOY_DIR} — пропускаем клонирование с токеном${NC}"
else
# Формируем URL с токеном (аналогично логике в main.sh)
# GitFlic HTTPS: https://gitflic.ru/project/user/repo.git
TOKEN_URL="https://${GIT_USERNAME}:${GIT_TOKEN}@${REPO_URL#https://}"
# Убеждаемся в наличии .git для GitFlic
if [[ "$TOKEN_URL" == *gitflic.ru* ]] && [[ "$TOKEN_URL" != *.git ]]; then
TOKEN_URL="${TOKEN_URL}.git"
fi
if sudo -u "$CCS_USER" git clone -b "$BRANCH" "$TOKEN_URL" "$DEPLOY_DIR"; then
echo -e "${GREEN}✓ Репозиторий успешно склонирован через токен${NC}"
JUST_CLONED=1
# Сохраняем данные в новый формат .git_tokens.json
echo -e "${BLUE}Сохраняем данные авторизации в .git_tokens.json...${NC}"
KEY="${REPO_URL}__${BRANCH}"
# Убеждаемся, что python3 и утилита доступны
if sudo -u "$CCS_USER" test -f "$DEPLOY_DIR/git_auth_utils.py"; then
sudo -u "$CCS_USER" python3 "$DEPLOY_DIR/git_auth_utils.py" "$CCS_HOME/.git_tokens.json" set "$KEY" "$AUTH_JSON" > /dev/null
else
# Если утилиты еще нет (хотя после клона должна быть), сохраняем напрямую через python
sudo -u "$CCS_USER" python3 -c "import json, sys, os; f='$CCS_HOME/.git_tokens.json'; data=json.load(open(f)) if os.path.exists(f) else {}; data['$KEY']=json.loads(sys.argv[1]); json.dump(data, open(f, 'w'), indent=4)" "$AUTH_JSON"
fi
else
echo -e "${RED}Не удалось склонировать репозиторий через токен${NC}"
exit 1
fi
fi
fi
if [ "$AUTH_METHOD" = "public" ] || [ "$AUTH_METHOD" = "https" ]; then
echo -e "${BLUE}Попытка клонирования через HTTPS (публичный доступ)...${NC}"
if [ -d "$DEPLOY_DIR/.git" ]; then
echo -e "${GREEN}Репозиторий уже существует: ${DEPLOY_DIR} — пропускаем клонирование HTTPS${NC}"
else
if sudo -u "$CCS_USER" git clone -b "$BRANCH" "$REPO_URL" "$DEPLOY_DIR" 2>/dev/null; then
echo -e "${GREEN}✓ Репозиторий успешно склонирован через HTTPS${NC}"
AUTH_METHOD="https"
JUST_CLONED=1
# Сохраняем информацию о публичном доступе
echo -e "${BLUE}Сохраняем данные авторизации (public) в .git_tokens.json...${NC}"
KEY="${REPO_URL}__${BRANCH}"
if sudo -u "$CCS_USER" test -f "$DEPLOY_DIR/git_auth_utils.py"; then
sudo -u "$CCS_USER" python3 "$DEPLOY_DIR/git_auth_utils.py" "$CCS_HOME/.git_tokens.json" set "$KEY" "$AUTH_JSON" > /dev/null
else
sudo -u "$CCS_USER" python3 -c "import json, sys, os; f='$CCS_HOME/.git_tokens.json'; data=json.load(open(f)) if os.path.exists(f) else {}; data['$KEY']=json.loads(sys.argv[1]); json.dump(data, open(f, 'w'), indent=4)" "$AUTH_JSON"
fi
else
echo -e "${YELLOW}HTTPS клонирование не удалось, переходим к SSH...${NC}"
AUTH_METHOD="ssh_fallback"
fi
fi
fi
# SSH авторизация (если выбрана или как fallback)
if [ "$AUTH_METHOD" = "ssh" ] || [ "$AUTH_METHOD" = "ssh_fallback" ]; then
echo -e "${BLUE}Настройка SSH авторизации...${NC}"
# Конвертируем HTTPS URL в SSH
if [[ "$REPO_URL" == https://github.com/* ]]; then
SSH_URL="git@github.com:${REPO_URL#https://github.com/}"
SSH_URL="${SSH_URL%.git}.git"
elif [[ "$REPO_URL" == https://gitflic.ru/* ]]; then
SSH_URL="git@gitflic.ru:${REPO_URL#https://gitflic.ru/project/}"
SSH_URL="${SSH_URL%.git}.git"
else
echo -e "${RED}Не удалось определить SSH URL для $REPO_URL${NC}"
exit 1
fi
# Генерируем SSH ключ
SSH_KEY_FILE="$CCS_HOME/.ssh/id_rsa"
if [ ! -f "$SSH_KEY_FILE" ]; then
echo -e "${YELLOW}Генерируем SSH ключ для пользователя $CCS_USER...${NC}"
sudo -u "$CCS_USER" ssh-keygen -t rsa -b 4096 -f "$SSH_KEY_FILE" -N "" -C "$CCS_USER@$(hostname)-ccs-deployment"
fi
# Показываем публичный ключ
echo -e "${MAGENTA}╔════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${MAGENTA}║ SSH КЛЮЧ ДЛЯ ПОЛЬЗОВАТЕЛЯ $CCS_USER ║${NC}"
echo -e "${MAGENTA}╚════════════════════════════════════════════════════════════════════╝${NC}"
echo -e "${YELLOW}"
cat "$SSH_KEY_FILE.pub"
echo -e "${NC}"
echo -e "${BLUE}Добавьте этот ключ в ваш Git сервис и нажмите Enter для продолжения...${NC}"
read
# Инструкции по добавлению ключа
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ ИНСТРУКЦИИ ПО ДОБАВЛЕНИЮ КЛЮЧА ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════════╝${NC}"
case "$REPO_SERVICE" in
"GitHub")
echo -e "${BLUE}1. Откройте: https://github.com/settings/keys${NC}"
echo -e "${BLUE}2. Нажмите 'New SSH key'${NC}"
echo -e "${BLUE}3. Вставьте публичный ключ выше${NC}"
echo -e "${BLUE}4. Нажмите 'Add SSH key'${NC}"
;;
"GitFlic")
echo -e "${BLUE}1. Откройте: https://gitflic.ru/settings/keys${NC}"
echo -e "${BLUE}2. Нажмите 'Добавить ключ'${NC}"
echo -e "${BLUE}3. Вставьте публичный ключ выше${NC}"
echo -e "${BLUE}4. Нажмите 'Добавить'${NC}"
;;
esac
echo ""
echo -e "${YELLOW}После добавления ключа нажмите Enter...${NC}"
read
# Добавляем хост в known_hosts
ssh_host=$(echo "$SSH_URL" | cut -d'@' -f2 | cut -d':' -f1)
if ! sudo -u "$CCS_USER" ssh-keygen -F "$ssh_host" >/dev/null 2>&1; then
echo -e "${BLUE}Добавляем $ssh_host в known_hosts...${NC}"
sudo -u "$CCS_USER" ssh-keyscan "$ssh_host" >> "$CCS_HOME/.ssh/known_hosts" 2>/dev/null
fi
# Пробуем SSH клонирование
echo -e "${BLUE}Попытка клонирования через SSH...${NC}"
if [ -d "$DEPLOY_DIR/.git" ]; then
echo -e "${GREEN}Репозиторий уже существует: ${DEPLOY_DIR} — пропускаем клонирование SSH${NC}"
else
# Сохраняем информацию об SSH авторизации
echo -e "${BLUE}Сохраняем данные авторизации (ssh) в .git_tokens.json...${NC}"
KEY="${REPO_URL}__${BRANCH}"
if sudo -u "$CCS_USER" test -f "$DEPLOY_DIR/git_auth_utils.py"; then
sudo -u "$CCS_USER" python3 "$DEPLOY_DIR/git_auth_utils.py" "$CCS_HOME/.git_tokens.json" set "$KEY" "$AUTH_JSON" > /dev/null
else
sudo -u "$CCS_USER" python3 -c "import json, sys, os; f='$CCS_HOME/.git_tokens.json'; data=json.load(open(f)) if os.path.exists(f) else {}; data['$KEY']=json.loads(sys.argv[1]); json.dump(data, open(f, 'w'), indent=4)" "$AUTH_JSON"
fi
if sudo -u "$CCS_USER" git clone -b "$BRANCH" "$SSH_URL" "$DEPLOY_DIR"; then
echo -e "${GREEN}✓ Репозиторий успешно склонирован через SSH${NC}"
AUTH_METHOD="ssh"
JUST_CLONED=1
# Сохраняем SSH настройки
save_config "SSH_URL" "$SSH_URL" "SSH repository URL"
# Сохраняем информацию об SSH авторизации в новый формат
echo -e "${BLUE}Сохраняем данные авторизации (SSH) в .git_tokens.json...${NC}"
AUTH_JSON="{\"auth_type\": \"ssh\", \"username\": \"\", \"token\": \"\"}"
KEY="${REPO_URL}__${BRANCH}"
if sudo -u "$CCS_USER" test -f "$DEPLOY_DIR/git_auth_utils.py"; then
sudo -u "$CCS_USER" python3 "$DEPLOY_DIR/git_auth_utils.py" "$CCS_HOME/.git_tokens.json" set "$KEY" "$AUTH_JSON" > /dev/null
fi
else
echo -e "${RED}Не удалось склонировать репозиторий${NC}"
exit 1
fi
fi
fi
# Сохраняем метод аутентификации
save_config "AUTH_METHOD" "$AUTH_METHOD" "Authentication method: https or ssh"
# Проверяем структуру и устанавливаем права
if [ ! -f "$DEPLOY_DIR/start_template.sh" ] || [ ! -d "$DEPLOY_DIR/prepareOS" ]; then
echo -e "${RED}ОШИБКА: Неверная структура проекта${NC}"
exit 1
fi
# Закрываем блок DO_REPO для выбора/клонирования/подтверждения
fi
if [ "$DO_REPO" = 1 ]; then
chown -R "$CCS_USER:$CCS_USER" "$DEPLOY_DIR"
echo -e "${GREEN}✓ Репозиторий CCS.Deployment готов${NC}"
fi
# -------------------------------------------------
# (Опционально) Предложить обновить репозиторий git
# Пропускаем, если только что склонировали в рамках этого запуска
# -------------------------------------------------
if [ "$DO_REPO" = 1 ] && [ "$JUST_CLONED" != "1" ] && [ -d "$DEPLOY_DIR/.git" ]; then
echo ""
echo -e "${BLUE}Обновить CCS.Deployment из удалённого репозитория сейчас? (y/N)${NC}"
read -r want_update
if [[ $want_update =~ ^[Yy]$ ]]; then
echo -e "${BLUE}Подготовка к обновлению репозитория...${NC}"
# Если используется токен, обновляем remote URL
if [ "$AUTH_METHOD" = "token" ] && [ -n "$GIT_TOKEN" ]; then
# Формируем URL с токеном
U_REPO_URL="$REPO_URL"
# Конвертируем SSH URL в HTTPS если нужно (для токенов нужен HTTPS)
if [[ "$U_REPO_URL" == git@github.com:* ]]; then
U_REPO_URL="https://github.com/${U_REPO_URL#git@github.com:}"
elif [[ "$U_REPO_URL" == git@gitflic.ru:* ]]; then
U_REPO_URL="https://gitflic.ru/project/${U_REPO_URL#git@gitflic.ru:}"
fi
# Убеждаемся в наличии .git для GitFlic
if [[ "$U_REPO_URL" == *gitflic.ru* ]] && [[ "$U_REPO_URL" != *.git ]]; then
U_REPO_URL="${U_REPO_URL}.git"
fi
# Формируем финальный URL с авторизацией
if [ -n "$GIT_USERNAME" ] && [ -n "$GIT_TOKEN" ] && [ -n "$U_REPO_URL" ]; then
U_TOKEN_URL="https://${GIT_USERNAME}:${GIT_TOKEN}@${U_REPO_URL#https://}"
# Обновляем remote URL от имени пользователя
sudo -u "$CCS_USER" git -C "$DEPLOY_DIR" remote set-url origin "$U_TOKEN_URL"
else
echo -e "${YELLOW}Предупреждение: Недостаточно данных для обновления токена в git remote (URL: $U_REPO_URL, User: $GIT_USERNAME). Пропускаем set-url.${NC}"
fi
fi
# Все операции git выполняем от имени пользователя, чтобы не ломать владельца файлов
sudo -u "$CCS_USER" bash -lc "
set -e
cd \"$DEPLOY_DIR\" || exit 0
DIRTY=\$(git status --porcelain 2>/dev/null || true)
STASHED=0
if [ -n \"\$DIRTY\" ]; then
echo \"Обнаружены незакоммиченные изменения.\"
echo \"1) Сохранить во временный stash и продолжить [по умолчанию]\"
echo \"2) Отменить обновление\"
read -p \"Ваш выбор (1-2) [по умолчанию: 1]: \" choice
choice=\${choice:-1}
if [ \"\$choice\" = \"1\" ]; then
git stash push -u -m \"ccs-deployment-auto-stash-\$(date +%F_%T)\" || true
STASHED=1
else
echo \"Обновление отменено пользователем.\"
exit 0
fi
fi
git fetch --all --prune || true
if ! git pull --ff-only; then
echo -e \"${YELLOW}Fast-forward недоступен. Возможны конфликты.${NC}\"
read -p \"Попробовать rebase? (y/N) [по умолчанию: N]: \" rb
rb=\${rb:-n}
if [[ \$rb =~ ^[Yy]\$ ]]; then
git pull --rebase || true
else
echo \"Пропущено обновление: fast-forward недоступен.\"
fi
fi
if [ \"\$STASHED\" = \"1\" ]; then
read -p \"Применить сохранённые изменения (stash pop) сейчас? (y/N) [по умолчанию: N]: \" ap
ap=\${ap:-n}
if [[ \$ap =~ ^[Yy]\$ ]]; then
git stash pop || true
fi
fi
echo \"Текущий коммит: \$(git rev-parse --short HEAD) - \$(git log -1 --oneline)\"
" || echo -e "${YELLOW}Предупреждение: обновление репозитория завершилось с предупреждениями/ошибками${NC}"
fi
fi
# Создаем копию скриптов подготовки
PREPARE_DIR="$CCS_HOME/ccs-prepare"
echo -e "${BLUE}Подготовка скриптов управления в: $PREPARE_DIR${NC}"
# Всегда после получения или обновления репозитория — кладём cert.sh в домашний каталог root
if [ -f "$DEPLOY_DIR/prepareOS/cert.sh" ]; then
ROOT_HOME=$(eval echo ~root)
cp -f "$DEPLOY_DIR/prepareOS/cert.sh" "$ROOT_HOME/cert.sh"
chmod +x "$ROOT_HOME/cert.sh" 2>/dev/null || true
echo -e "${GREEN}✓ Скопирован скрипт выпуска сертификатов: ${ROOT_HOME}/cert.sh${NC}"
fi
rm -rf "$PREPARE_DIR" 2>/dev/null || true
mkdir -p "$PREPARE_DIR"
# Копируем все необходимые файлы
cp -r "$DEPLOY_DIR/prepareOS/"* "$PREPARE_DIR/"
cp "$DEPLOY_DIR/funcs.sh" "$PREPARE_DIR/"
cp "$DEPLOY_DIR/log_colors.sh" "$PREPARE_DIR/"
# Создаем симлинк на основной проект
ln -sf "$DEPLOY_DIR" "$PREPARE_DIR/CCS.Deployment"
# Устанавливаем права
chown -R "$CCS_USER:$CCS_USER" "$PREPARE_DIR"
chmod +x "$PREPARE_DIR"/*.sh
echo -e "${GREEN}✓ Скрипты управления готовы${NC}"
# =============================
# Автоподготовка домашней директории пользователя
# Копируем стартовый скрипт и конфиг, если их ещё нет, и выполняем первый запуск
# =============================
echo -e "${BLUE}Подготавливаем рабочее окружение пользователя (${CCS_USER})...${NC}"
# 1) start.sh в домашней директории пользователя
USER_START_SH="$CCS_HOME/start.sh"
if [ ! -f "$USER_START_SH" ]; then
echo -e "${YELLOW}Копируем стартовый скрипт: ${DEPLOY_DIR}/start_template.sh → ${USER_START_SH}${NC}"
cp "$DEPLOY_DIR/start_template.sh" "$USER_START_SH"
chown "$CCS_USER:$CCS_USER" "$USER_START_SH"
chmod +x "$USER_START_SH"
else
echo -e "${GREEN}✓ Найден существующий ${USER_START_SH} — пропускаем копирование${NC}"
fi
# 2) config.sh в домашней директории пользователя
USER_CONFIG_SH="$CCS_HOME/config.sh"
if [ ! -f "$USER_CONFIG_SH" ]; then
echo -e "${YELLOW}Копируем шаблон конфига: ${DEPLOY_DIR}/config_template.sh → ${USER_CONFIG_SH}${NC}"
cp "$DEPLOY_DIR/config_template.sh" "$USER_CONFIG_SH"
chown "$CCS_USER:$CCS_USER" "$USER_CONFIG_SH"
chmod 600 "$USER_CONFIG_SH"
# Гарантируем, что конфиг в состоянии черновика
sed -i 's/^config_is_ready=.*/config_is_ready=0/' "$USER_CONFIG_SH" 2>/dev/null || true
else
echo -e "${GREEN}✓ Найден существующий ${USER_CONFIG_SH} — пропускаем копирование${NC}"
fi
# 2.5) Немедленное применение egress-политики для пользователя (rootless)
# Это гарантирует, что даже до первого срабатывания таймера у пользователя будут верные правила.
if [ -f "/usr/local/bin/ccs_egress_guard_rootless.sh" ]; then
echo -e "${BLUE}Применяем правила исходящего трафика (egress guard) для $CCS_USER...${NC}"
/usr/local/bin/ccs_egress_guard_rootless.sh "$CCS_USER" 2>/dev/null || true
fi
# 3) Предложение запустить настройку под пользователем (пока без автозапуска)
echo ""
# Используем значение из конфига для значения по умолчанию
default_want_user_setup=$(get_config "WANT_USER_SETUP" "n")
prompt_want_user_setup="y/N"
[[ $default_want_user_setup =~ ^[Yy]$ ]] && prompt_want_user_setup="Y/n"
echo -e "${BLUE}Запустить настройку под пользователем сейчас? ($prompt_want_user_setup) [по умолчанию: $default_want_user_setup]${NC}"
read -r WANT_USER_SETUP
WANT_USER_SETUP=${WANT_USER_SETUP:-$default_want_user_setup}
# Сохраняем решение в конфигурацию
save_config "WANT_USER_SETUP" "$WANT_USER_SETUP" "User setup after first run: y/n"
if [[ $WANT_USER_SETUP =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}Функционал автоматического запуска мастера под пользователем пока не готов.${NC}"
echo -e "${BLUE}Пожалуйста, выполните вручную:${NC}"
echo -e " 1) su - $CCS_USER"
echo -e " 2) vim ~/config.sh # заполните и установите config_is_ready=1"
echo -e " 3) ./start.sh install db nginx <ваши_сервисы> backup"
else
echo -e "${YELLOW}Автозапуск настройки под пользователем пропущен (по умолчанию).${NC}"
fi
# Сохраняем финальную информацию в конфигурацию
save_config "INSTALL_STATUS" "completed" "Installation status: completed - success, failed - error"
save_config "INSTALL_DATE" "$(date)" "Installation completion date and time"
save_config "CONFIG_FILE_LOCATION" "$CONFIG_PATH" "Path to this configuration file"
# Копируем конфигурационный файл в домашнюю директорию пользователя
FINAL_CONFIG_PATH="$CCS_HOME/$CONFIG_FILE"
cp "$CONFIG_PATH" "$FINAL_CONFIG_PATH"
chown "$CCS_USER:$CCS_USER" "$FINAL_CONFIG_PATH"
chmod 600 "$FINAL_CONFIG_PATH"
# (Ранее здесь запускался интерактивный мастер пользователя из ccs-prepare. Теперь
# мы выполняем автоматическую инициализацию и выводим понятные следующие шаги.)
# Финальное сообщение с информацией о конфигурации
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════${NC}"
echo -e "${BLUE} УСТАНОВКА ЗАВЕРШЕНА ${NC}"
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════${NC}"
echo ""
echo -e "${GREEN}CCS.Deployment готов к использованию!${NC}"
echo -e "${YELLOW}Для работы с системой используйте пользователя: $CCS_USER${NC}"
echo -e "${YELLOW}Путь к проекту: $DEPLOY_DIR${NC}"
echo -e "${YELLOW}Стартовый скрипт пользователя: ${USER_START_SH}${NC}"
echo -e "${YELLOW}Конфиг пользователя: ${USER_CONFIG_SH}${NC}"
echo ""
echo -e "${BLUE}Конфигурация установки сохранена в:${NC}"
echo -e "${YELLOW} Системная: $CONFIG_PATH${NC}"
echo -e "${YELLOW} Пользователя: $FINAL_CONFIG_PATH${NC}"
echo ""
echo -e "${GREEN}Этот файл конфигурации можно использовать для автоматической${NC}"
echo -e "${GREEN}установки в будущем или для документирования настроек.${NC}"
# Информация о Fail2Ban
F2B_STATUS="не установлен"
if command -v fail2ban-client >/dev/null 2>&1; then
if systemctl is-active --quiet fail2ban 2>/dev/null; then
F2B_STATUS="установлен и активен"
else
F2B_STATUS="установлен, но не активен"
fi
fi
echo ""
echo -e "${BLUE}Статус безопасности:${NC}"
echo -e "Fail2Ban: ${YELLOW}${F2B_STATUS}${NC}"
if [ "$F2B_STATUS" != "установлен и активен" ]; then
echo -e "Под пользователем можно открыть мастер: ${YELLOW}$PREPARE_DIR/start.sh${NC} → меню 'Развертывание CCS' → 'Установка и настройка Fail2Ban'"
fi
echo ""
echo -e "${BLUE}Дальнейшие шаги (под пользователем ${YELLOW}$CCS_USER${BLUE}):${NC}"
cat <<EOS
1) Войти под пользователем:
su - $CCS_USER
2) Открыть и настроить конфиг (установить config_is_ready=1 после заполнения):
vim ~/config.sh
3) Установить сервисы из конфига (пример):
./start.sh install db nginx ccs_app ccs_app_front backup
4) ПРИ ПРОБЛЕМАХ СО СКАЧИВАНИЕМ ОБРАЗОВ (Docker Hub limit):
Если возникает ошибка "toomanyrequests", выполните авторизацию:
podman login docker.io
5) Получить SSL-сертификаты (опционально сейчас, можно позже):
~/ccs-prepare/start.sh → Управление SSL сертификатами → Получить Let's Encrypt сертификаты
6) Для доступа к БД через SSH-туннель (пример):
ssh -N -L 3306:127.0.0.1:33060 $CCS_USER@<server>
# затем в клиенте БД подключайтесь к localhost:3306
EOS