v2.2.1: 11 security fixes (safe_edit_message, IP validation, os-release protection, config_get return codes, spinner stderr, domain check, mode sanitization, var init, XSS escape)

This commit is contained in:
anten-ka
2026-04-06 20:49:31 +03:00
parent bbd6eab48c
commit 4985ec3679
13 changed files with 5212 additions and 2166 deletions

340
lib/website.sh Normal file
View File

@@ -0,0 +1,340 @@
#!/bin/bash
# GoTelegram v2.2 — Управление сайтом (nginx + certbot + шаблоны)
# ── Установка nginx ──────────────────────────────────────────────────────────
install_nginx() {
if command -v nginx &>/dev/null; then
log_dim "nginx уже установлен"
return 0
fi
log_info "Установка nginx..."
case "$(get_pkg_manager)" in
apt) apt-get update -qq && apt-get install -y -qq nginx ;;
dnf) dnf install -y -q nginx ;;
yum) yum install -y -q nginx ;;
esac
systemctl enable nginx 2>/dev/null
}
# ── Установка certbot ────────────────────────────────────────────────────────
install_certbot() {
if command -v certbot &>/dev/null; then
log_dim "certbot уже установлен"
return 0
fi
log_info "Установка certbot..."
case "$(get_pkg_manager)" in
apt) apt-get install -y -qq certbot python3-certbot-nginx ;;
dnf) dnf install -y -q certbot python3-certbot-nginx ;;
yum) yum install -y -q certbot python3-certbot-nginx ;;
esac
}
# ── Генерация nginx конфига ──────────────────────────────────────────────────
generate_nginx_config() {
local domain="$1"
local proxy_port="${2:-443}"
local use_ssl="${3:-true}"
mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
cat > "$NGINX_SITE_CONF" << 'EONGINX'
# GoTelegram v2.2 — nginx config
# Обслуживает сайт-маскировку для telemt stealth mode
server {
listen 80;
listen [::]:80;
server_name DOMAIN_PLACEHOLDER;
# Let's Encrypt ACME challenge
location /.well-known/acme-challenge/ {
root /var/www/certbot;
allow all;
}
# Редирект на HTTPS
location / {
return 301 https://$server_name$request_uri;
}
}
server {
listen SSL_PORT_PLACEHOLDER ssl http2;
listen [::]:SSL_PORT_PLACEHOLDER ssl http2;
server_name DOMAIN_PLACEHOLDER;
# SSL сертификаты
ssl_certificate /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/privkey.pem;
# Современные TLS настройки
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Корень сайта
root /var/www/gotelegram-site;
index index.html;
location / {
try_files $uri $uri/ =404;
expires 30d;
}
# Кеширование статики
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Скрываем служебные файлы
location ~ /\. { deny all; }
location = /robots.txt { allow all; log_not_found off; access_log off; }
location = /favicon.ico { log_not_found off; access_log off; }
}
EONGINX
# Подставляем значения (используем | как разделитель, чтобы / в домене не ломал sed)
local escaped_domain
escaped_domain=$(printf '%s\n' "$domain" | sed 's/[&/\]/\\&/g')
sed -i "s|DOMAIN_PLACEHOLDER|${escaped_domain}|g" "$NGINX_SITE_CONF"
sed -i "s|SSL_PORT_PLACEHOLDER|443|g" "$NGINX_SITE_CONF"
# Активируем сайт
rm -f /etc/nginx/sites-enabled/default 2>/dev/null
ln -sf "$NGINX_SITE_CONF" "$NGINX_SITE_LINK"
log_success "nginx конфиг создан для $domain"
}
# ── Временный конфиг (до получения SSL) ──────────────────────────────────────
generate_nginx_temp_config() {
local domain="$1"
cat > "$NGINX_SITE_CONF" << EONGINX_TEMP
# GoTelegram — временный конфиг (до получения SSL)
server {
listen 80;
listen [::]:80;
server_name ${domain};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
allow all;
}
root /var/www/gotelegram-site;
index index.html;
location / {
try_files \$uri \$uri/ =404;
}
}
EONGINX_TEMP
rm -f /etc/nginx/sites-enabled/default 2>/dev/null
ln -sf "$NGINX_SITE_CONF" "$NGINX_SITE_LINK"
mkdir -p /var/www/certbot
}
# ── Получение SSL сертификата ────────────────────────────────────────────────
obtain_ssl_certificate() {
local domain="$1"
local email="${2:-}"
if [ ! -d "/etc/letsencrypt/live/$domain" ]; then
log_info "Получение SSL сертификата для $domain..."
# Временный конфиг для ACME challenge
generate_nginx_temp_config "$domain"
systemctl restart nginx 2>/dev/null
local certbot_args=(
certonly
--webroot
-w /var/www/certbot
-d "$domain"
--non-interactive
--agree-tos
)
if [ -n "$email" ]; then
certbot_args+=(--email "$email")
else
certbot_args+=(--register-unsafely-without-email)
fi
if certbot "${certbot_args[@]}" 2>/dev/null; then
log_success "SSL сертификат получен для $domain"
return 0
else
log_error "Не удалось получить SSL сертификат"
log_dim "Убедитесь что домен $domain направлен на IP этого сервера"
log_dim "и порт 80 открыт в файрволе."
return 1
fi
else
log_dim "SSL сертификат уже существует для $domain"
return 0
fi
}
# ── Авто-обновление сертификата ──────────────────────────────────────────────
setup_ssl_auto_renewal() {
# Certbot systemd timer (предпочтительно)
if [ -f /etc/systemd/system/certbot.timer ] || [ -f /lib/systemd/system/certbot.timer ]; then
systemctl enable certbot.timer 2>/dev/null
systemctl start certbot.timer 2>/dev/null
log_success "Авто-обновление SSL через systemd timer"
return 0
fi
# Fallback: cron
if ! crontab -l 2>/dev/null | grep -q "certbot renew"; then
(crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet --post-hook 'systemctl reload nginx'") | crontab -
log_success "Авто-обновление SSL через cron (3:00 ежедневно)"
fi
}
# ── Обновление сертификата вручную ───────────────────────────────────────────
renew_ssl_certificate() {
log_info "Обновление SSL сертификата..."
if certbot renew --quiet --post-hook "systemctl reload nginx" 2>/dev/null; then
log_success "Сертификат обновлён"
return 0
else
log_error "Ошибка обновления сертификата"
return 1
fi
}
# ── Дата истечения SSL ───────────────────────────────────────────────────────
get_ssl_expiry() {
local domain="$1"
local cert="/etc/letsencrypt/live/$domain/fullchain.pem"
if [ -f "$cert" ]; then
openssl x509 -enddate -noout -in "$cert" 2>/dev/null | sed 's/notAfter=//'
else
echo "N/A"
fi
}
# ── Деплой шаблона сайта ─────────────────────────────────────────────────────
deploy_template_to_nginx() {
local template_dir="$1"
if [ ! -d "$template_dir" ] || [ ! -f "$template_dir/index.html" ]; then
log_error "Шаблон не содержит index.html: $template_dir"
return 1
fi
# Бекапим старый сайт
if [ -d "$WEBSITE_ROOT" ] && [ "$(ls -A "$WEBSITE_ROOT" 2>/dev/null)" ]; then
local backup_name="site_backup_$(date +%Y%m%d_%H%M%S)"
mv "$WEBSITE_ROOT" "/tmp/$backup_name" 2>/dev/null
log_dim "Старый сайт сохранён в /tmp/$backup_name"
fi
mkdir -p "$WEBSITE_ROOT"
cp -r "$template_dir"/* "$WEBSITE_ROOT/"
chown -R www-data:www-data "$WEBSITE_ROOT" 2>/dev/null || chown -R nginx:nginx "$WEBSITE_ROOT" 2>/dev/null
chmod -R 755 "$WEBSITE_ROOT"
log_success "Шаблон развёрнут в $WEBSITE_ROOT"
}
# ── Полная установка stealth-режима ──────────────────────────────────────────
setup_stealth_mode() {
local domain="$1"
local template_dir="$2"
local proxy_port="${3:-443}"
local email="${4:-}"
log_step "Настройка stealth-режима"
# 1. Устанавливаем nginx
run_with_spinner "Установка nginx" install_nginx || return 1
# 2. Устанавливаем certbot
run_with_spinner "Установка certbot" install_certbot || return 1
# 3. Деплоим шаблон сайта
deploy_template_to_nginx "$template_dir" || return 1
# 4. Получаем SSL
obtain_ssl_certificate "$domain" "$email" || return 1
# 5. Генерируем полный nginx конфиг с SSL
generate_nginx_config "$domain" "$proxy_port"
# 6. Тестируем и перезапускаем nginx
if nginx -t 2>/dev/null; then
systemctl restart nginx
log_success "nginx запущен с SSL"
else
log_error "Ошибка в конфигурации nginx"
nginx -t
return 1
fi
# 7. Настраиваем авто-обновление SSL
setup_ssl_auto_renewal
# 8. Показываем благодарности авторам шаблонов
show_credits
log_success "Stealth-режим настроен: https://${domain}"
return 0
}
# ── Управление nginx ────────────────────────────────────────────────────────
nginx_status() {
if systemctl is-active --quiet nginx 2>/dev/null; then
echo "running"
else
echo "stopped"
fi
}
restart_nginx() {
if nginx -t 2>/dev/null; then
systemctl restart nginx 2>/dev/null
log_success "nginx перезапущен"
else
log_error "Ошибка конфигурации nginx"
nginx -t
return 1
fi
}
# ── Удаление stealth-режима ──────────────────────────────────────────────────
remove_stealth_mode() {
log_info "Удаление stealth-режима..."
rm -f "$NGINX_SITE_CONF" "$NGINX_SITE_LINK"
rm -rf "$WEBSITE_ROOT"
systemctl restart nginx 2>/dev/null
log_success "Stealth-режим удалён (nginx оставлен)"
}
# ── Смена шаблона ────────────────────────────────────────────────────────────
switch_template() {
local new_template_dir="$1"
deploy_template_to_nginx "$new_template_dir"
# nginx не требует перезапуска — статика обновилась на месте
log_success "Шаблон сайта обновлён"
}