Files

557 строки
21 KiB
Bash

#!/usr/bin/env bash
# =============================================================================
# BotFactory — SaaS Platform Interactive Installer v2.1
# Ubuntu 22.04 / 24.04 clean server installer
# =============================================================================
set -euo pipefail
IFS=$'\n\t'
R='\033[0;31m'; G='\033[0;32m'; Y='\033[1;33m'
C='\033[0;36m'; B='\033[1m'; DIM='\033[2m'; NC='\033[0m'
OK="${G}[✓]${NC}"; ERR="${R}[✗]${NC}"; WARN="${Y}[!]${NC}"; INFO="${C}[→]${NC}"
hdr() { echo -e "\n${B}${C}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n $1\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"; }
log() { echo -e "${OK} $1"; }
info() { echo -e "${INFO} $1"; }
warn() { echo -e "${WARN} $1"; }
die() { echo -e "${ERR} $1"; exit 1; }
[[ $EUID -ne 0 ]] && die "Запустите от root: sudo bash install.sh"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
[[ ! -d "${SCRIPT_DIR}/backend" ]] && die "Запустите из папки проекта: cd botfactory && sudo bash install.sh"
. /etc/os-release 2>/dev/null || true
if [[ "${ID:-}" != "ubuntu" ]]; then
warn "Скрипт рассчитан на Ubuntu 22.04/24.04. Текущая ОС: ${PRETTY_NAME:-неизвестна}"
read -rp "Продолжить? [y/N]: " _c; [[ "$_c" =~ ^[Yy]$ ]] || exit 0
elif [[ "${VERSION_ID:-}" != "22.04" && "${VERSION_ID:-}" != "24.04" ]]; then
warn "Проверены Ubuntu 22.04 и 24.04. Текущая версия: ${VERSION_ID:-неизвестна}"
read -rp "Продолжить? [y/N]: " _c; [[ "$_c" =~ ^[Yy]$ ]] || exit 0
fi
clear
echo -e "${B}${C}"
cat << 'BANNER'
____ _ _____ _
| __ ) ___| |_| ___|_ _ ___| |_ ___ _ __ _ _
| _ \ / _ \ __| |_ / _` |/ __| __/ _ \| '__| | | |
| |_) | __/ |_| _| (_| | (__| || (_) | | | |_| |
|____/ \___|\__|_| \__,_|\___|\__\___/|_| \__, |
|___/
BANNER
echo -e "${NC}${B} SaaS Platform — Interactive Installer v2.1${NC}\n"
ask() {
local var="$1" prompt="$2" default="${3:-}" secret="${4:-}"
local val=""
while [[ -z "$val" ]]; do
if [[ -n "$default" ]]; then
echo -ne "${C}?${NC} ${B}${prompt}${NC} ${DIM}[${default}]${NC}: "
else
echo -ne "${C}?${NC} ${B}${prompt}${NC}: "
fi
if [[ "$secret" == "secret" ]]; then
read -rs val; echo ""
else
read -r val
fi
val="${val:-$default}"
[[ -z "$val" ]] && echo -e " ${WARN} Поле обязательно для заполнения"
done
printf -v "$var" '%s' "$val"
}
ask_optional() {
local var="$1" prompt="$2" default="${3:-}"
if [[ -n "$default" ]]; then
echo -ne "${C}?${NC} ${B}${prompt}${NC} ${DIM}[${default}]${NC}: "
else
echo -ne "${C}?${NC} ${B}${prompt}${NC}: "
fi
local val=""; read -r val
printf -v "$var" '%s' "${val:-$default}"
}
ask_opt() {
local var="$1" prompt="$2" opts="${3:-Y/n}"
echo -ne "${C}?${NC} ${B}${prompt}${NC} ${DIM}[${opts}]${NC}: "
local ans=""; read -r ans
if [[ "$opts" == "Y/n" ]]; then
[[ "$ans" =~ ^[Nn] ]] && printf -v "$var" 'no' || printf -v "$var" 'yes'
else
[[ "$ans" =~ ^[Yy] ]] && printf -v "$var" 'yes' || printf -v "$var" 'no'
fi
}
valid_number() { [[ "$1" =~ ^[0-9]+([.][0-9]+)?$ ]]; }
hdr "Шаг 1 из 3 — Конфигурация"
echo -e "\n${B}▸ Домен и SSL${NC}"
echo -e "${DIM} Если домена нет — оставьте пустым, будет HTTP на IP-адресе${NC}\n"
ask_optional DOMAIN "Доменное имя" ""
DOMAIN="$(echo "$DOMAIN" | tr '[:upper:]' '[:lower:]' | sed -E 's#^https?://##;s#/$##')"
if [[ -n "$DOMAIN" ]]; then
ask CERTBOT_EMAIL "Email для SSL-сертификата" "admin@${DOMAIN}"
ask_opt WITH_WWW "Включить www.${DOMAIN} в сертификат?" "Y/n"
ask_opt SETUP_SSL "Получить SSL-сертификат сейчас?" "Y/n"
else
CERTBOT_EMAIL=""; WITH_WWW="no"; SETUP_SSL="no"
warn "Домен не задан — установка на HTTP (SSL можно добавить позже)"
fi
echo -e "\n${B}▸ Telegram — платформенный бот${NC}"
echo -e "${DIM} Создайте бота через @BotFather → /newbot → скопируйте токен${NC}\n"
ask PLATFORM_BOT_TOKEN "Токен платформенного бота" ""
echo -e "${DIM} Несколько ID через запятую. Свой ID узнайте у @userinfobot${NC}\n"
ask PLATFORM_ADMIN_IDS "Telegram ID администраторов" ""
echo -e "\n${B}▸ База данных PostgreSQL${NC}\n"
ask_opt CUSTOM_DB_PASS "Задать пароль БД вручную?" "y/N"
if [[ "$CUSTOM_DB_PASS" == "yes" ]]; then
ask DB_PASS "Пароль PostgreSQL" "" secret
else
DB_PASS="$(openssl rand -hex 16)"
log "Пароль БД сгенерирован автоматически"
fi
echo -e "\n${B}▸ Комиссии платформы${NC}\n"
ask_opt CUSTOM_COMMISSIONS "Изменить комиссии? (Тест 7д 10% / Trial 10% / Basic 7% / Pro 5% / Ent 3% / Постоплата default 5%)" "y/N"
if [[ "$CUSTOM_COMMISSIONS" == "yes" ]]; then
ask COMM_TRIAL_WEEK "Тест 7 дней (%)" "10"
ask COMM_TRIAL "Trial (%)" "10"
ask COMM_BASIC "Basic (%)" "7"
ask COMM_PRO "Pro (%)" "5"
ask COMM_ENTERPRISE "Enterprise (%)" "3"
ask COMM_POSTPAID_DEFAULT "Постоплата default (%)" "5"
ask POSTPAID_DUE_DAY "День оплаты постоплаты по умолчанию (1-28)" "5"
else
COMM_TRIAL_WEEK=10; COMM_TRIAL=10; COMM_BASIC=7; COMM_PRO=5; COMM_ENTERPRISE=3
COMM_POSTPAID_DEFAULT=5; POSTPAID_DUE_DAY=5
fi
for n in "$COMM_TRIAL_WEEK" "$COMM_TRIAL" "$COMM_BASIC" "$COMM_PRO" "$COMM_ENTERPRISE" "$COMM_POSTPAID_DEFAULT"; do
valid_number "$n" || die "Комиссии должны быть числами"
done
[[ "$POSTPAID_DUE_DAY" =~ ^[0-9]+$ ]] && (( POSTPAID_DUE_DAY >= 1 && POSTPAID_DUE_DAY <= 28 )) || die "День оплаты постоплаты должен быть от 1 до 28"
APP_DIR="/opt/botfactory"
APP_USER="botfactory"
DB_NAME="botfactory"
DB_USER="botfactory"
API_PORT=8000
SECRET_KEY="$(openssl rand -hex 32)"
VENV="${APP_DIR}/venv"
FRONT_DIR="${APP_DIR}/frontend"
PYTHON_BIN="$(command -v python3 || true)"
hdr "Шаг 2 из 3 — Подтверждение"
SERVER_IP="$(curl -4 -s --max-time 4 ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}')"
[[ -z "$SERVER_IP" ]] && SERVER_IP="$(hostname -I | awk '{print $1}')"
echo -e "${B}Параметры установки:${NC}\n"
printf " %-25s %s\n" "Директория:" "${APP_DIR}"
printf " %-25s %s\n" "IP сервера:" "${SERVER_IP}"
printf " %-25s %s\n" "Домен:" "${DOMAIN:-"(нет — HTTP)"}"
printf " %-25s %s\n" "SSL:" "${SETUP_SSL}"
[[ -n "$DOMAIN" ]] && printf " %-25s %s\n" "www в сертификат:" "${WITH_WWW}"
[[ -n "$DOMAIN" ]] && printf " %-25s %s\n" "Email SSL:" "${CERTBOT_EMAIL}"
printf " %-25s %s\n" "Platform Bot Token:" "${PLATFORM_BOT_TOKEN:0:24}…"
printf " %-25s %s\n" "Admin Telegram IDs:" "${PLATFORM_ADMIN_IDS}"
printf " %-25s %s\n" "DB пароль:" "${DB_PASS:0:8}… (скрыт)"
printf " %-25s %s\n" "Комиссии:" "Тест7д ${COMM_TRIAL_WEEK}% | Trial ${COMM_TRIAL}% | Basic ${COMM_BASIC}% | Pro ${COMM_PRO}% | Ent ${COMM_ENTERPRISE}% | Postpay ${COMM_POSTPAID_DEFAULT}%"
printf " %-25s %s\n" "Постоплата default:" "до ${POSTPAID_DUE_DAY} числа"
echo ""
read -rp "$(echo -e "${B}Начать установку?${NC} [y/N]: ")" START
[[ "$START" =~ ^[Yy]$ ]] || { echo "Отменено."; exit 0; }
hdr "Шаг 3 из 3 — Установка"
START_TIME=$(date +%s)
step() { echo -e "\n${B}${C}$1${NC}"; }
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a
wait_for_apt() {
local locks=(/var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/cache/apt/archives/lock)
for i in {1..60}; do
local busy=0
for lock in "${locks[@]}"; do
fuser "$lock" >/dev/null 2>&1 && busy=1 || true
done
[[ "$busy" == "0" ]] && return 0
sleep 2
done
die "APT занят другим процессом. Остановите apt/dpkg и повторите установку."
}
apt_install() {
wait_for_apt
apt-get install -y -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" "$@"
}
step "Системные пакеты"
wait_for_apt
apt-get update -qq
apt_install \
ca-certificates curl wget git nano htop unzip gnupg lsb-release \
build-essential libssl-dev libffi-dev libpq-dev \
python3 python3-venv python3-dev python3-pip \
postgresql postgresql-client \
redis-server nginx \
certbot python3-certbot-nginx \
ufw fail2ban
PYTHON_BIN="$(command -v python3)"
log "Системные пакеты установлены без apt upgrade"
if ! command -v node >/dev/null 2>&1 || [[ "$(node -v 2>/dev/null | cut -d. -f1)" != "v20" ]]; then
info "Устанавливаем Node.js 20 LTS..."
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt_install nodejs
fi
log "Node.js $(node -v) готов"
step "Системный пользователь"
id "$APP_USER" >/dev/null 2>&1 || useradd -m -s /bin/bash "$APP_USER"
log "Пользователь ${APP_USER} готов"
step "PostgreSQL"
systemctl enable --now postgresql
su - postgres -c "psql -tc \"SELECT 1 FROM pg_database WHERE datname='${DB_NAME}'\"" | grep -q 1 || su - postgres -c "createdb ${DB_NAME}"
su - postgres -c "psql -tc \"SELECT 1 FROM pg_roles WHERE rolname='${DB_USER}'\"" | grep -q 1 || su - postgres -c "psql -c \"CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASS}'\""
su - postgres -c "psql -c \"ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASS}'\""
su - postgres -c "psql -c \"GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER}\""
su - postgres -c "psql -c \"ALTER DATABASE ${DB_NAME} OWNER TO ${DB_USER}\""
log "PostgreSQL: база ${DB_NAME} готова"
step "Redis"
systemctl enable --now redis-server
redis-cli ping | grep -q PONG && log "Redis работает"
step "Структура директорий"
mkdir -p "${APP_DIR}"/{backend,frontend,logs,uploads,backups}
chown -R "$APP_USER:$APP_USER" "$APP_DIR"
log "Директории созданы"
step "Конфигурация (.env)"
cat > "${APP_DIR}/.env" << ENV
APP_DIR=${APP_DIR}
DEBUG=false
SECRET_KEY=${SECRET_KEY}
DATABASE_URL=postgresql+asyncpg://${DB_USER}:${DB_PASS}@127.0.0.1:5432/${DB_NAME}
DB_POOL_SIZE=20
REDIS_URL=redis://127.0.0.1:6379/0
PLATFORM_BOT_TOKEN=${PLATFORM_BOT_TOKEN}
PLATFORM_ADMIN_IDS=${PLATFORM_ADMIN_IDS}
API_HOST=127.0.0.1
API_PORT=${API_PORT}
DOMAIN=${DOMAIN}
ALLOWED_ORIGINS=http://localhost:3000${DOMAIN:+,https://$DOMAIN,http://$DOMAIN}
UPLOAD_DIR=${APP_DIR}/uploads
MAX_UPLOAD_MB=10
COMMISSION_TRIAL_WEEK=${COMM_TRIAL_WEEK}
COMMISSION_TRIAL=${COMM_TRIAL}
COMMISSION_BASIC=${COMM_BASIC}
COMMISSION_PRO=${COMM_PRO}
COMMISSION_ENTERPRISE=${COMM_ENTERPRISE}
COMMISSION_POSTPAID_DEFAULT=${COMM_POSTPAID_DEFAULT}
POSTPAID_DEFAULT_DUE_DAY=${POSTPAID_DUE_DAY}
ENV
chmod 600 "${APP_DIR}/.env"
log ".env создан"
step "Копирование backend"
rsync -a --delete "${SCRIPT_DIR}/backend/" "${APP_DIR}/backend/"
PY_COUNT=$(find "${APP_DIR}/backend" -name "*.py" | wc -l)
log "Backend скопирован (${PY_COUNT} Python-файлов)"
step "Python виртуальное окружение"
rm -rf "$VENV"
"$PYTHON_BIN" -m venv "$VENV"
# shellcheck disable=SC1091
source "${VENV}/bin/activate"
pip install -q --upgrade pip wheel setuptools
pip install -q -r "${APP_DIR}/backend/requirements.txt"
deactivate
log "Python venv готов: $(${VENV}/bin/python --version)"
step "Frontend (React + Vite)"
rsync -a --delete "${SCRIPT_DIR}/frontend/" "${FRONT_DIR}/"
cat > "${FRONT_DIR}/.env.production" << FENV
VITE_API_URL=/api
FENV
npm install --prefix "$FRONT_DIR" --silent --no-fund --no-audit
npm run build --prefix "$FRONT_DIR" --silent
[[ -f "${FRONT_DIR}/dist/index.html" ]] || die "Frontend не собрался: нет ${FRONT_DIR}/dist/index.html"
log "Frontend собран"
step "Nginx"
mkdir -p /var/www/certbot
NGINX_CONF="/etc/nginx/sites-available/botfactory"
SERVER_NAMES="_"
[[ -n "$DOMAIN" ]] && SERVER_NAMES="$DOMAIN"
[[ -n "$DOMAIN" && "$WITH_WWW" == "yes" ]] && SERVER_NAMES="$SERVER_NAMES www.$DOMAIN"
cat > "$NGINX_CONF" << NGINX
server {
listen 80;
server_name ${SERVER_NAMES};
client_max_body_size 20M;
gzip on;
gzip_types text/plain text/css application/json application/javascript image/svg+xml;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
root ${FRONT_DIR}/dist;
try_files \$uri \$uri/ /index.html;
add_header Cache-Control "no-cache";
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
root ${FRONT_DIR}/dist;
expires 30d;
add_header Cache-Control "public, immutable";
}
location /api/ {
proxy_pass http://127.0.0.1:${API_PORT};
proxy_http_version 1.1;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 120s;
}
location /uploads/ {
alias ${APP_DIR}/uploads/;
expires 7d;
add_header Cache-Control "public";
}
}
NGINX
rm -f /etc/nginx/sites-enabled/default
ln -sf "$NGINX_CONF" /etc/nginx/sites-enabled/botfactory
nginx -t || die "Ошибка конфигурации Nginx"
systemctl enable nginx
systemctl restart nginx
log "Nginx запущен"
check_dns() {
local host="$1" expected="$2"
local ip
ip="$(getent ahostsv4 "$host" 2>/dev/null | awk '{print $1}' | head -1 || true)"
[[ -z "$ip" ]] && { warn "$host не резолвится. Настройте A-запись → $expected"; return 1; }
[[ "$ip" != "$expected" ]] && { warn "$host указывает на $ip, а сервер: $expected"; return 1; }
log "DNS OK: $host$ip"
return 0
}
step "SSL — Let's Encrypt"
run_certbot() {
local d_args=(-d "$DOMAIN")
check_dns "$DOMAIN" "$SERVER_IP" || return 1
if [[ "$WITH_WWW" == "yes" ]]; then
check_dns "www.$DOMAIN" "$SERVER_IP" || return 1
d_args+=(-d "www.$DOMAIN")
fi
certbot --nginx "${d_args[@]}" \
--email "$CERTBOT_EMAIL" \
--agree-tos --no-eff-email \
--redirect --non-interactive
}
if [[ -n "$DOMAIN" && "$SETUP_SSL" == "yes" ]]; then
if run_certbot; then
systemctl reload nginx
systemctl enable --now certbot.timer >/dev/null 2>&1 || true
log "SSL сертификат получен"
else
warn "SSL не получен. Исправьте DNS и запустите: certbot --nginx -d ${DOMAIN} --email ${CERTBOT_EMAIL} --agree-tos --redirect"
fi
elif [[ -n "$DOMAIN" ]]; then
warn "SSL пропущен. После DNS: certbot --nginx -d ${DOMAIN} --email ${CERTBOT_EMAIL} --agree-tos --redirect"
else
warn "SSL недоступен без домена"
fi
step "Systemd сервисы"
cat > /etc/systemd/system/botfactory-api.service << SERVICE
[Unit]
Description=BotFactory API (FastAPI)
After=network.target postgresql.service redis-server.service
Requires=postgresql.service redis-server.service
StartLimitIntervalSec=60
StartLimitBurst=5
[Service]
Type=exec
User=${APP_USER}
Group=${APP_USER}
WorkingDirectory=${APP_DIR}/backend
Environment=PYTHONPATH=${APP_DIR}/backend
EnvironmentFile=${APP_DIR}/.env
ExecStart=${VENV}/bin/uvicorn main:app --host 127.0.0.1 --port ${API_PORT} --workers 1 --loop uvloop --log-level info
Restart=always
RestartSec=5
TimeoutStopSec=20
KillMode=mixed
StandardOutput=append:${APP_DIR}/logs/api.log
StandardError=append:${APP_DIR}/logs/api-error.log
[Install]
WantedBy=multi-user.target
SERVICE
cat > /etc/systemd/system/botfactory-bots.service << SERVICE
[Unit]
Description=BotFactory Bots Runner (platform + shops)
After=network-online.target postgresql.service redis-server.service
Wants=network-online.target postgresql.service redis-server.service
StartLimitIntervalSec=60
StartLimitBurst=5
[Service]
Type=exec
User=${APP_USER}
Group=${APP_USER}
WorkingDirectory=${APP_DIR}/backend
Environment=PYTHONPATH=${APP_DIR}/backend
EnvironmentFile=${APP_DIR}/.env
ExecStart=${VENV}/bin/python shop_bots_runner.py
Restart=always
RestartSec=10
TimeoutStopSec=20
KillMode=mixed
StandardOutput=append:${APP_DIR}/logs/bots.log
StandardError=append:${APP_DIR}/logs/bots-error.log
[Install]
WantedBy=multi-user.target
SERVICE
systemctl daemon-reload
systemctl enable botfactory-api botfactory-bots
systemctl restart botfactory-api && log "API запущен" || warn "API не стартовал — см. journalctl -u botfactory-api"
systemctl restart botfactory-bots && log "Боты запущены" || warn "Боты не стартовали — см. journalctl -u botfactory-bots"
step "Firewall (UFW)"
ufw allow 22/tcp comment "SSH" >/dev/null || true
ufw allow 80/tcp comment "HTTP" >/dev/null || true
ufw allow 443/tcp comment "HTTPS" >/dev/null || true
ufw --force enable >/dev/null || warn "UFW не удалось включить автоматически"
log "UFW: правила SSH + HTTP + HTTPS добавлены без reset"
step "Fail2ban"
cat > /etc/fail2ban/jail.local << F2B
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8
[sshd]
enabled = true
[nginx-http-auth]
enabled = true
F2B
systemctl enable --now fail2ban || warn "Fail2ban не стартовал"
log "Fail2ban настроен"
step "Права доступа"
chown -R "$APP_USER:$APP_USER" "$APP_DIR"
chmod 755 "$APP_DIR"
chmod 600 "$APP_DIR/.env"
chmod -R 755 "${APP_DIR}/backend" "${APP_DIR}/frontend"
find "${FRONT_DIR}/dist" -type d -exec chmod 755 {} \; 2>/dev/null || true
find "${FRONT_DIR}/dist" -type f -exec chmod 644 {} \; 2>/dev/null || true
chmod -R 770 "${APP_DIR}/logs" "${APP_DIR}/uploads" "${APP_DIR}/backups"
log "Права выставлены: nginx может читать frontend, .env закрыт"
step "Logrotate"
cat > /etc/logrotate.d/botfactory << LR
${APP_DIR}/logs/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0640 ${APP_USER} ${APP_USER}
}
LR
log "Logrotate настроен"
step "Автоматическое резервное копирование"
cat > "${APP_DIR}/backups/backup.sh" << BACKUP
#!/usr/bin/env bash
set -euo pipefail
DIR="${APP_DIR}/backups"
DATE=\$(date +%Y%m%d_%H%M%S)
PGPASSWORD="${DB_PASS}" pg_dump -U ${DB_USER} -h 127.0.0.1 ${DB_NAME} | gzip > "\${DIR}/db_\${DATE}.sql.gz"
tar -czf "\${DIR}/uploads_\${DATE}.tar.gz" -C ${APP_DIR} uploads/ 2>/dev/null || true
find "\${DIR}" -name "*.gz" -mtime +30 -delete 2>/dev/null || true
echo "[\$(date '+%Y-%m-%d %H:%M:%S')] Backup OK: \${DATE}"
BACKUP
chmod +x "${APP_DIR}/backups/backup.sh"
chown "$APP_USER:$APP_USER" "${APP_DIR}/backups/backup.sh"
( crontab -l 2>/dev/null | grep -v "botfactory/backups"; echo "0 2 * * * ${APP_DIR}/backups/backup.sh >> ${APP_DIR}/logs/backup.log 2>&1" ) | crontab -
log "Бекап: ежедневно в 02:00"
cat > "${APP_DIR}/install-info.txt" << INFO
BotFactory — параметры установки
Дата: $(date '+%Y-%m-%d %H:%M:%S')
Директория: ${APP_DIR}
Server IP: ${SERVER_IP}
Домен: ${DOMAIN:-нет}
SSL requested: ${SETUP_SSL}
DB User: ${DB_USER}
DB Name: ${DB_NAME}
DB Pass: ${DB_PASS}
Secret Key: ${SECRET_KEY}
API Port: ${API_PORT}
Комиссии: trial_week=${COMM_TRIAL_WEEK}% trial=${COMM_TRIAL}% basic=${COMM_BASIC}% pro=${COMM_PRO}% enterprise=${COMM_ENTERPRISE}% postpaid_default=${COMM_POSTPAID_DEFAULT}%
Постоплата default due day: ${POSTPAID_DUE_DAY}
INFO
chmod 600 "${APP_DIR}/install-info.txt"
chown "$APP_USER:$APP_USER" "${APP_DIR}/install-info.txt"
ELAPSED=$(( $(date +%s) - START_TIME ))
clear
echo -e "${B}${G}"
echo " ╔═══════════════════════════════════════════╗"
echo " ║ BotFactory успешно установлен! ✓ ║"
printf " ║ Время установки: %-4s сек. ║\n" "${ELAPSED}"
echo " ╚═══════════════════════════════════════════╝"
echo -e "${NC}"
echo -e "${B}▸ Доступ:${NC}"
if [[ -n "$DOMAIN" && -d "/etc/letsencrypt/live/${DOMAIN}" ]]; then
echo -e " ${G}${NC} Панель: ${C}https://${DOMAIN}${NC}"
echo -e " ${G}${NC} API: ${C}https://${DOMAIN}/api${NC}"
else
echo -e " ${G}${NC} Панель: ${C}http://${DOMAIN:-$SERVER_IP}${NC}"
echo -e " ${G}${NC} API: ${C}http://${DOMAIN:-$SERVER_IP}/api${NC}"
fi
echo -e "\n${B}▸ Управление:${NC}"
echo -e " ${DIM}systemctl status botfactory-api${NC}"
echo -e " ${DIM}systemctl status botfactory-bots${NC}"
echo -e " ${DIM}journalctl -u botfactory-api -f${NC}"
echo -e " ${DIM}journalctl -u botfactory-bots -f${NC}"
echo -e "\n${G}${B}⚠ Сохраните ${APP_DIR}/install-info.txt в надёжное место.${NC}\n"