diff --git a/rproxy b/rproxy index 0d19aac..8b4acde 100644 --- a/rproxy +++ b/rproxy @@ -3,7 +3,7 @@ # Публикация локальных сервисов через SSH-туннели + nginx на VPS # https://github.com/l-ptrol/rProxy -VERSION="1.7.0" +VERSION="1.8.0" export PATH="/opt/bin:/opt/sbin:$PATH" CONF_DIR="/opt/etc/rproxy" CONF_FILE="$CONF_DIR/rproxy.conf" @@ -12,6 +12,7 @@ VPS_DIR="$CONF_DIR/vps" PID_DIR="/opt/var/run/rproxy" SSH_KEY="$CONF_DIR/id_ed25519" REMOTE_NGINX_DIR="/etc/nginx/sites-enabled" +REMOTE_LOCATIONS_DIR="/etc/nginx/rproxy_locations" BASE_TUNNEL_PORT=10000 BASE_EXT_PORT=26000 CERTBOT_EMAIL="" # Будет заполнено из конфига @@ -595,10 +596,10 @@ do_add_interactive() { " fi + msg "Деплой конфигурации на VPS..." local tmp="/tmp/rproxy_$name.conf" generate_nginx_conf "$name" "$t_host" "$t_port" "$tunnel_port" "$domain" "$ext_port" "$use_ndm_auth" "$path" "$tmp" - # Деплой # Деплой htpasswd если нужно if [ "$use_ndm_auth" = "yes" ]; then local ht_tmp="/tmp/rproxy_$name.htpasswd" @@ -607,15 +608,35 @@ do_add_interactive() { rm -f "$ht_tmp" fi - # Деплой nginx - scp_cmd "$tmp" "$VPS_USER@$VPS_HOST:$REMOTE_NGINX_DIR/rproxy_$name.conf" || { err "Ошибка деплоя nginx"; rm -f "$tmp"; pause; return; } + # Деплой сниппета локации + local remote_loc_dir + if [ -n "$domain" ]; then + remote_loc_dir="$REMOTE_LOCATIONS_DIR/$domain" + else + remote_loc_dir="$REMOTE_LOCATIONS_DIR/_ip_$name" + fi + + ssh_cmd "mkdir -p $remote_loc_dir" + scp_cmd "$tmp" "$VPS_USER@$VPS_HOST:$remote_loc_dir/$name.conf" || { err "Ошибка деплоя сниппета"; rm -f "$tmp"; pause; return; } rm -f "$tmp" - ssh_cmd "nginx -t && systemctl reload nginx" >/dev/null 2>&1 - # SSL если нужно + # Пересборка основного конфига + if [ -n "$domain" ]; then + rebuild_vhost_config "$domain" "$ext_port" "$vps_id" + else + rebuild_ip_config "$name" "$ext_port" "$tunnel_port" "$vps_id" + fi + + # SSL (теперь Certbot вызывается только если сертификата НЕТ) if [ "$use_ssl" = "yes" ]; then - msg "Получаю SSL через Certbot (может занять время)..." - ssh_cmd "certbot --nginx -d $domain --non-interactive --agree-tos -m $CERTBOT_EMAIL" || warn "Certbot вернул ошибку" + if ssh_cmd "[ ! -f /etc/letsencrypt/live/$domain/fullchain.pem ]"; then + msg "SSL сертификат не найден. Запускаю Certbot..." + ssh_cmd "certbot --nginx -d $domain --non-interactive --agree-tos -m $CERTBOT_EMAIL" || warn "Certbot вернул ошибку" + # После Certbot пересобираем конфиг, чтобы он прописал SSL пути + rebuild_vhost_config "$domain" "$ext_port" "$vps_id" + else + msg "SSL сертификат уже существует на сервере. Пропускаю Certbot." + fi fi # Сохранение конфига сервиса (используем printf для защиты от спецсимволов и одинарные кавычки) @@ -683,6 +704,9 @@ do_ssl_interactive() { sed -i "s/SVC_SSL=.*/SVC_SSL=\"yes\"/" "$SERVICES_DIR/$name.conf" sed -i "s/SVC_EXT_PORT=.*/SVC_EXT_PORT=\"443\"/" "$SERVICES_DIR/$name.conf" + # Пересборка основного конфига (теперь он подхватит сертификат) + rebuild_vhost_config "$SVC_DOMAIN" "443" "$SVC_VPS" + # Проверка автопродления (добавляем таймер/крон если нет) ssh_cmd "systemctl is-active certbot.timer >/dev/null 2>&1 || (crontab -l 2>/dev/null | grep -q certbot || (crontab -l 2>/dev/null; echo \"0 0,12 * * * certbot renew -q\") | crontab -)" else @@ -796,10 +820,34 @@ do_remove_interactive() { load_service "$name" || { pause; return; } is_running "$name" && do_stop_service "$name" - msg "Удаляю конфиги Nginx и пароли на VPS..." - ssh_cmd "rm -f $REMOTE_NGINX_DIR/rproxy_$name.conf /etc/nginx/rproxy_$name.htpasswd && nginx -t && systemctl reload nginx" >/dev/null 2>&1 || { - warn "Не удалось полностью очистить VPS (возможно файлы уже удалены)" - } + msg "Удаляю конфигурации на VPS..." + local remote_loc_dir + local is_domain=0 + if [ -n "$SVC_DOMAIN" ]; then + remote_loc_dir="$REMOTE_LOCATIONS_DIR/$SVC_DOMAIN" + is_domain=1 + else + remote_loc_dir="$REMOTE_LOCATIONS_DIR/_ip_$name" + fi + + # Удаляем сниппет и htpasswd + ssh_cmd "rm -f $remote_loc_dir/$name.conf /etc/nginx/rproxy_$name.htpasswd" + + if [ "$is_domain" -eq 1 ]; then + # Проверяем, остались ли другие сервисы для этого домена + local remaining=$(ssh_cmd "ls $remote_loc_dir/*.conf 2>/dev/null | wc -l") + if [ "$remaining" -gt 0 ]; then + msg "Пересборка конфига домена (осталось $remaining сервисов)..." + rebuild_vhost_config "$SVC_DOMAIN" "$SVC_EXT_PORT" "$SVC_VPS" + else + msg "Последний сервис для домена. Удаляю основной конфиг..." + ssh_cmd "rm -f $REMOTE_NGINX_DIR/rproxy_dom_$SVC_DOMAIN.conf && rmdir $remote_loc_dir" + fi + else + ssh_cmd "rm -f $REMOTE_NGINX_DIR/rproxy_svc_$name.conf && rm -rf $remote_loc_dir" + fi + + ssh_cmd "nginx -t && systemctl reload nginx" >/dev/null 2>&1 rm -f "$SERVICES_DIR/$name.conf" rm -f "$(get_pid_file "$name")" @@ -896,13 +944,36 @@ do_edit_interactive() { local tmp="/tmp/rproxy_edit_$name.conf" generate_nginx_conf "$name" "$SVC_TARGET_HOST" "$SVC_TARGET_PORT" "$SVC_TUNNEL_PORT" "$SVC_DOMAIN" "$SVC_EXT_PORT" "$SVC_NDM_AUTH" "$SVC_PATH" "$tmp" - scp_cmd "$tmp" "$VPS_USER@$VPS_HOST:$REMOTE_NGINX_DIR/rproxy_$name.conf" - rm -f "$tmp" - ssh_cmd "nginx -t && systemctl reload nginx" >/dev/null 2>&1 + local remote_loc_dir + if [ -n "$SVC_DOMAIN" ]; then + remote_loc_dir="$REMOTE_LOCATIONS_DIR/$SVC_DOMAIN" + else + remote_loc_dir="$REMOTE_LOCATIONS_DIR/_ip_$name" + fi - # SSL если он был настроен - восстанавливаем слушателей 443 - if [ "$SVC_SSL" = "yes" ]; then - ssh_cmd "certbot --nginx -d $SVC_DOMAIN --non-interactive --agree-tos -m $CERTBOT_EMAIL" >/dev/null 2>&1 + msg "Обновление конфигурации на VPS..." + ssh_cmd "mkdir -p $remote_loc_dir" + # Удаляем старый файл если он был в корне sites-enabled (миграция) + ssh_cmd "rm -f $REMOTE_NGINX_DIR/rproxy_$name.conf" + + scp_cmd "$tmp" "$VPS_USER@$VPS_HOST:$remote_loc_dir/$name.conf" + rm -f "$tmp" + + # Пересборка основного конфига + if [ -n "$SVC_DOMAIN" ]; then + rebuild_vhost_config "$SVC_DOMAIN" "$SVC_EXT_PORT" "$SVC_VPS" + else + rebuild_ip_config "$name" "$SVC_EXT_PORT" "$SVC_TUNNEL_PORT" "$SVC_VPS" + fi + + # SSL если он был настроен или сертификат уже есть + if [ "$SVC_SSL" = "yes" ] || { [ -n "$SVC_DOMAIN" ] && ssh_cmd "[ -f /etc/letsencrypt/live/$SVC_DOMAIN/fullchain.pem ]"; }; then + # Если SVC_SSL была "no", но сертификат нашли — помечаем как "yes" + if [ "$SVC_SSL" = "no" ]; then + sed -i "s/SVC_SSL=.*/SVC_SSL=\"yes\"/" "$SERVICES_DIR/$name.conf" + # Пересобираем vhost чтобы включить SSL блок + rebuild_vhost_config "$SVC_DOMAIN" "$SVC_EXT_PORT" "$SVC_VPS" + fi fi # Обновление htpasswd на VPS если включено @@ -1144,13 +1215,9 @@ generate_nginx_conf() { " fi - if [ -n "$domain" ]; then - cat > "$target_file" << NGINXEOF -server { - listen $ext_port; - server_name "$domain"; - proxy_buffering off; - proxy_request_buffering off; + # Теперь эта функция генерирует ТОЛЬКО блок location + # Основной конфиг сервера будет собираться отдельно + cat > "$target_file" << NGINXEOF location $path { $auth_config proxy_pass http://127.0.0.1:$tunnel_port/; @@ -1166,30 +1233,97 @@ server { proxy_cookie_domain "$t_host" "\$host"; proxy_redirect off; } -} NGINXEOF +} + +# Функция для сборки основного конфига Nginx на VPS +rebuild_vhost_config() { + local domain="$1" + local ext_port="$2" + local vps_id="$3" + + load_vps "$vps_id" + + msg "Пересборка конфигурации для домена $domain..." + + # Создаем директорию для локаций + ssh_cmd "mkdir -p $REMOTE_LOCATIONS_DIR/$domain" + + # Проверяем наличие SSL сертификата на VPS + local cert_file="/etc/letsencrypt/live/$domain/fullchain.pem" + local key_file="/etc/letsencrypt/live/$domain/privkey.pem" + local has_ssl=0 + ssh_cmd "[ -f $cert_file ] && [ -f $key_file ]" && has_ssl=1 + + local tmp_vhost="/tmp/rproxy_vhost_$domain.conf" + + if [ -n "$domain" ]; then + # Конфиг для домена + cat > "$tmp_vhost" << EOF +server { + listen $ext_port; + server_name "$domain"; + proxy_buffering off; + proxy_request_buffering off; + + include $REMOTE_LOCATIONS_DIR/$domain/*.conf; +EOF + + if [ "$has_ssl" -eq 1 ]; then + cat >> "$tmp_vhost" << EOF + listen 443 ssl; + ssl_certificate $cert_file; + ssl_certificate_key $key_file; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; +EOF + fi + echo "}" >> "$tmp_vhost" + + # Редирект с 80 на 443 если есть SSL + if [ "$has_ssl" -eq 1 ] && [ "$ext_port" != "80" ]; then + cat >> "$tmp_vhost" << EOF +server { + listen 80; + server_name "$domain"; + return 301 https://\$host\$request_uri; +} +EOF + fi + + scp_cmd "$tmp_vhost" "$VPS_USER@$VPS_HOST:$REMOTE_NGINX_DIR/rproxy_dom_$domain.conf" else - cat > "$target_file" << NGINXEOF + # Это не должно вызываться для IP-публикаций напрямую через эту функцию + # Но на всякий случай оставим заглушку + return 1 + fi + rm -f "$tmp_vhost" + ssh_cmd "nginx -t && systemctl reload nginx" >/dev/null 2>&1 +} + +# Специальная функция для IP-публикаций (они остаются одиночными) +rebuild_ip_config() { + local name="$1" + local ext_port="$2" + local tunnel_port="$3" + local vps_id="$4" + + load_vps "$vps_id" + local tmp_ip="/tmp/rproxy_ip_$name.conf" + + cat > "$tmp_ip" << EOF server { listen $ext_port; proxy_buffering off; proxy_request_buffering off; - location $path { - $auth_config - proxy_pass http://127.0.0.1:$tunnel_port/; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host "$stealth_host"; - proxy_set_header Origin "http://$stealth_host"; - proxy_set_header X-Real-IP \$remote_addr; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_cookie_domain "$t_host" "\$host"; - proxy_redirect off; - } + + include $REMOTE_LOCATIONS_DIR/_ip_$name/*.conf; } -NGINXEOF - fi +EOF + ssh_cmd "mkdir -p $REMOTE_LOCATIONS_DIR/_ip_$name" + scp_cmd "$tmp_ip" "$VPS_USER@$VPS_HOST:$REMOTE_NGINX_DIR/rproxy_svc_$name.conf" + rm -f "$tmp_ip" + ssh_cmd "nginx -t && systemctl reload nginx" >/dev/null 2>&1 } do_start_all() {