Scripts: Dokploy + Nginx Proxy Manager(OpenAppSec)
#!/bin/bash
# =============================================================================
# DOKPLOY + NGINX PROXY MANAGER + OPENAPPSEC INSTALLATION SCRIPT
# =============================================================================
#
# Этот скрипт автоматически устанавливает и настраивает:
# - Dokploy (платформа для развертывания Docker приложений)
# - Nginx Proxy Manager (обратный прокси с веб-интерфейсом)
# - OpenAppSec (система защиты веб-приложений)
#
# Архитектура: Internet → NPM + OpenAppSec → Dokploy Apps (БЕЗ Traefik)
#
# Использование:
# sudo ./install-dokploy-nginx-organized.sh # Установка
# sudo ./install-dokploy-nginx-organized.sh update # Обновление
# sudo ./install-dokploy-nginx-organized.sh clean # Очистка
# =============================================================================
set -e
# =============================================================================
# РАЗДЕЛ 1: НАСТРОЙКИ И ПЕРЕМЕННЫЕ
# =============================================================================
# Здесь можно изменить основные параметры установки
# Основные настройки
DOKPLOY_PORT="${PORT:-3000}" # Порт для Dokploy Dashboard
ADMIN_EMAIL="${ADMIN_EMAIL:-admin@example.com}" # Email администратора NPM
NPM_ADMIN_PASSWORD="${NPM_ADMIN_PASSWORD:-SecurePassword123!}" # Пароль NPM
RELEASE_TAG="${RELEASE_TAG:-latest}" # Версия Dokploy
# Цвета для логирования
RED="\033[0;31m"
GREEN="\033[0;32m"
YELLOW="\033[1;33m"
BLUE="\033[0;34m"
NC="\033[0m" # No Color
# =============================================================================
# РАЗДЕЛ 2: ФУНКЦИИ ЛОГИРОВАНИЯ
# =============================================================================
# Функции для красивого вывода сообщений
log_info() {
printf "${BLUE}[INFO]${NC} $1\n"
}
log_success() {
printf "${GREEN}[SUCCESS]${NC} $1\n"
}
log_warning() {
printf "${YELLOW}[WARNING]${NC} $1\n"
}
log_error() {
printf "${RED}[ERROR]${NC} $1\n"
}
# =============================================================================
# РАЗДЕЛ 3: ПРОВЕРКА ПОРТОВ И СИСТЕМЫ
# =============================================================================
# Проверяет доступность портов и системные требования
check_port_open() {
local port=$1
local protocol=${2:-tcp}
# Проверяем, слушает ли порт
if ss -tulnp | grep ":${port} " >/dev/null 2>&1; then
return 0 # Порт открыт/слушает
else
return 1 # Порт не открыт/не слушает
fi
}
check_requirements() {
log_info "Проверка системных требований..."
# Проверяем, что запущено от root
if [ "$(id -u)" != "0" ]; then
log_error "Этот скрипт должен быть запущен от имени root"
exit 1
fi
# Проверяем, что это не Mac OS
if [ "$(uname)" = "Darwin" ]; then
log_error "Этот скрипт должен быть запущен на Linux"
exit 1
fi
# Проверяем, что не запущено внутри контейнера
if [ -f /.dockerenv ]; then
log_error "Этот скрипт должен быть запущен на хост-системе Linux, не внутри контейнера"
exit 1
fi
# Проверяем, что порт 80 свободен (будет использоваться NPM)
if ss -tulnp | grep ':80 ' >/dev/null; then
log_error "Порт 80 уже используется (требуется для Nginx Proxy Manager)"
exit 1
fi
# Проверяем, что порт 443 свободен (будет использоваться NPM)
if ss -tulnp | grep ':443 ' >/dev/null; then
log_error "Порт 443 уже используется (требуется для Nginx Proxy Manager)"
exit 1
fi
# Проверяем, что порт Dokploy свободен
if ss -tulnp | grep ":${DOKPLOY_PORT} " >/dev/null; then
log_error "Порт ${DOKPLOY_PORT} уже используется (требуется для Dokploy)"
exit 1
fi
log_success "Проверка системных требований пройдена"
}
# =============================================================================
# РАЗДЕЛ 4: НАСТРОЙКА FIREWALL (NFTABLES)
# =============================================================================
# Настраивает firewall для открытия только портов 80 и 443
detect_and_remove_ufw() {
log_info "Проверка UFW firewall..."
# Проверяем, установлен ли UFW
if command -v ufw >/dev/null 2>&1; then
log_info "UFW обнаружен, удаляем его..."
# Отключаем UFW
ufw --force disable >/dev/null 2>&1 || true
# Останавливаем сервис UFW
systemctl stop ufw >/dev/null 2>&1 || true
systemctl disable ufw >/dev/null 2>&1 || true
# Удаляем пакет UFW
if command -v apt-get >/dev/null 2>&1; then
apt-get remove -y ufw >/dev/null 2>&1 || true
apt-get purge -y ufw >/dev/null 2>&1 || true
elif command -v yum >/dev/null 2>&1; then
yum remove -y ufw >/dev/null 2>&1 || true
elif command -v dnf >/dev/null 2>&1; then
dnf remove -y ufw >/dev/null 2>&1 || true
fi
log_success "UFW успешно удален"
else
log_info "UFW не найден, продолжаем настройку nftables"
fi
}
check_ports_accessible() {
log_info "Проверка конфигурации firewall..."
# Тестируем, доступны ли порты 80 и 443
local port_80_ok=false
local port_443_ok=false
# Проверяем, блокирует ли nftables порты
if command -v nft >/dev/null 2>&1; then
# Проверяем, разрешены ли порты в nftables
if nft list ruleset 2>/dev/null | grep -q "tcp dport { 80, 443 } accept\|tcp dport 80 accept.*tcp dport 443 accept"; then
port_80_ok=true
port_443_ok=true
fi
else
# Если нет nftables, предполагаем, что порты нужно настроить
port_80_ok=false
port_443_ok=false
fi
if [ "$port_80_ok" = true ] && [ "$port_443_ok" = true ]; then
log_success "Порты 80 и 443 правильно настроены"
return 0
else
log_info "Порты 80 и 443 нуждаются в настройке"
return 1
fi
}
install_nftables() {
log_info "Установка nftables..."
# Исправляем проблему с рабочей директорией
cd /tmp
# Устанавливаем nftables
if command -v apt-get >/dev/null 2>&1; then
apt-get update -qq
apt-get install -y -qq nftables
elif command -v yum >/dev/null 2>&1; then
yum install -y nftables
elif command -v dnf >/dev/null 2>&1; then
dnf install -y nftables
else
log_error "Менеджер пакетов не найден, не удается установить nftables"
return 1
fi
# Включаем и запускаем сервис nftables
systemctl enable nftables
systemctl start nftables
log_success "nftables установлен и запущен"
}
remove_other_firewalls() {
log_info "Отключение ufw..."
if command -v ufw >/dev/null 2>&1; then
ufw --force disable
systemctl stop ufw
systemctl disable ufw
log_success "ufw отключен"
fi
# Также отключаем iptables-persistent если присутствует
if systemctl is-enabled netfilter-persistent >/dev/null 2>&1; then
systemctl stop netfilter-persistent >/dev/null 2>&1 || true
systemctl disable netfilter-persistent >/dev/null 2>&1 || true
log_info "netfilter-persistent отключен"
fi
}
configure_nftables() {
log_info "Настройка правил nftables firewall..."
# Создаем конфигурацию nftables
cat > /etc/nftables.conf << 'EOF'
#!/usr/sbin/nft -f
# Очищаем все предыдущие состояния
flush ruleset
# Определяем переменные - ТОЛЬКО порты 80 и 443
define ALLOWED_TCP_PORTS = { 80, 443 }
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
# Разрешаем loopback трафик
iif "lo" accept
# Разрешаем установленные и связанные соединения
ct state established,related accept
# Разрешаем ICMP (ping)
ip protocol icmp accept
ip6 nexthdr ipv6-icmp accept
# Разрешаем ТОЛЬКО HTTP (80) и HTTPS (443) отовсюду
tcp dport $ALLOWED_TCP_PORTS accept
# Логируем и отбрасываем все остальное
log prefix "nftables-drop: " drop
}
chain forward {
type filter hook forward priority filter; policy accept;
# Разрешаем Docker контейнерам общаться
iifname "docker*" accept
oifname "docker*" accept
}
chain output {
type filter hook output priority filter; policy accept;
}
}
# NAT таблица для Docker (если нужно)
table ip nat {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
# Masquerade для Docker сетей
ip saddr 172.16.0.0/12 oifname != "docker*" masquerade
}
}
EOF
# Применяем конфигурацию
nft -f /etc/nftables.conf
if [ $? -eq 0 ]; then
log_success "Правила nftables успешно применены"
# Сохраняем конфигурацию для постоянного использования
if command -v netfilter-persistent >/dev/null 2>&1; then
netfilter-persistent save
fi
# Показываем текущие правила
log_info "Текущие правила nftables:"
nft list ruleset | head -20
return 0
else
log_error "Не удалось применить правила nftables"
return 1
fi
}
setup_firewall() {
log_info "Настройка конфигурации firewall..."
# Шаг 1: Удаляем UFW если присутствует
detect_and_remove_ufw
# Шаг 2: Устанавливаем nftables если не присутствует
if ! command -v nft >/dev/null 2>&1; then
install_nftables
fi
# Шаг 3: Настраиваем nftables для открытия ТОЛЬКО портов 80 и 443
if ! check_ports_accessible; then
log_info "Настройка nftables для открытия портов 80 и 443..."
remove_other_firewalls
configure_nftables
# Проверяем конфигурацию
sleep 2
if check_ports_accessible; then
log_success "Firewall настроен успешно - порты 80 и 443 открыты"
else
log_warning "Конфигурация firewall может потребовать ручной настройки"
fi
else
log_success "Firewall уже правильно настроен - порты 80 и 443 открыты"
fi
}
# =============================================================================
# РАЗДЕЛ 5: УСТАНОВКА И НАСТРОЙКА DOCKER
# =============================================================================
# Устанавливает Docker, docker-compose и настраивает права пользователей
install_docker() {
log_info "Установка Docker..."
# Исправляем проблему с рабочей директорией
cd /tmp
command_exists() {
command -v "$@" > /dev/null 2>&1
}
if command_exists docker; then
log_info "Docker уже установлен"
else
log_info "Docker не найден, устанавливаем..."
curl -sSL https://get.docker.com | sh
log_success "Docker успешно установлен"
fi
# Включаем и запускаем сервис Docker
log_info "Включение сервиса Docker..."
systemctl enable docker
if ! systemctl is-active --quiet docker 2>/dev/null; then
log_info "Запуск Docker daemon..."
systemctl start docker
# Ждем запуска Docker daemon
local attempts=0
local max_attempts=30
while [ $attempts -lt $max_attempts ]; do
if systemctl is-active --quiet docker 2>/dev/null; then
log_success "Docker daemon успешно запущен"
break
fi
log_info "Ожидание запуска Docker daemon... (попытка $((attempts + 1))/$max_attempts)"
sleep 2
attempts=$((attempts + 1))
done
if [ $attempts -eq $max_attempts ]; then
log_error "Docker daemon не удалось запустить"
exit 1
fi
fi
# Проверяем, что Docker работает
if ! docker version >/dev/null 2>&1; then
log_error "Docker работает неправильно"
exit 1
fi
# Устанавливаем docker-compose если не присутствует
if ! command_exists docker-compose; then
log_info "Установка docker-compose..."
local compose_version="v2.29.7"
local compose_url="https://github.com/docker/compose/releases/download/${compose_version}/docker-compose-$(uname -s)-$(uname -m)"
if curl -L "$compose_url" -o /usr/local/bin/docker-compose; then
chmod +x /usr/local/bin/docker-compose
ln -sf /usr/local/bin/docker-compose /usr/bin/docker-compose
log_success "Docker-compose успешно установлен"
else
log_warning "Не удалось скачать docker-compose, пробуем альтернативный метод..."
if docker compose version >/dev/null 2>&1; then
log_info "Используем Docker Compose plugin вместо этого"
echo '#!/bin/bash' > /usr/local/bin/docker-compose
echo 'docker compose "$@"' >> /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
log_success "Создан wrapper для docker-compose"
else
log_error "Не удалось установить docker-compose"
exit 1
fi
fi
fi
# Добавляем текущего пользователя в группу docker если не root и SUDO_USER установлен
if [ "$(id -u)" = "0" ] && [ -n "$SUDO_USER" ]; then
log_info "Добавление пользователя $SUDO_USER в группу docker..."
usermod -aG docker "$SUDO_USER"
log_success "Пользователь $SUDO_USER добавлен в группу docker"
log_info "Примечание: Пользователю может потребоваться выйти и войти снова для применения изменений группы"
fi
# Устанавливаем дополнительные инструменты
log_info "Установка дополнительных необходимых инструментов..."
if command_exists apt-get; then
apt-get update -qq
apt-get install -y -qq jq curl wget
elif command_exists yum; then
yum install -y jq curl wget
elif command_exists dnf; then
dnf install -y jq curl wget
else
log_warning "Менеджер пакетов не найден, предполагаем, что инструменты уже установлены"
fi
log_success "Установка Docker завершена"
}
# =============================================================================
# РАЗДЕЛ 6: ОЧИСТКА СУЩЕСТВУЮЩИХ УСТАНОВОК
# =============================================================================
# Удаляет предыдущие установки для чистой установки
cleanup_existing() {
log_info "Очистка существующих установок..."
# Останавливаем и удаляем NPM контейнеры
docker stop npm-attachment appsec-agent 2>/dev/null || true
docker rm npm-attachment appsec-agent 2>/dev/null || true
# Удаляем сервисы Dokploy
docker service rm dokploy-postgres dokploy-redis dokploy 2>/dev/null || true
# Удаляем Traefik если существует (он нам не нужен)
docker stop dokploy-traefik 2>/dev/null || true
docker rm dokploy-traefik 2>/dev/null || true
# Останавливаем OpenAppSec compose если директория существует
if [ -d "/opt/openappsec-npm" ]; then
cd /opt/openappsec-npm && docker-compose down 2>/dev/null || true
fi
# Удаляем директорию OpenAppSec
rm -rf /opt/openappsec-npm
# Покидаем swarm если в нем
docker swarm leave --force 2>/dev/null || true
log_success "Очистка завершена"
}
# =============================================================================
# РАЗДЕЛ 7: НАСТРОЙКА DOCKER SWARM
# =============================================================================
# Инициализирует Docker Swarm для Dokploy
setup_docker_swarm() {
log_info "Настройка Docker Swarm..."
get_ip() {
local ip=""
# Сначала пробуем IPv4
ip=$(curl -4s --connect-timeout 5 https://ifconfig.io 2>/dev/null)
if [ -z "$ip" ]; then
ip=$(curl -4s --connect-timeout 5 https://icanhazip.com 2>/dev/null)
fi
if [ -z "$ip" ]; then
ip=$(curl -4s --connect-timeout 5 https://ipecho.net/plain 2>/dev/null)
fi
# Если нет IPv4, пробуем IPv6
if [ -z "$ip" ]; then
ip=$(curl -6s --connect-timeout 5 https://ifconfig.io 2>/dev/null)
if [ -z "$ip" ]; then
ip=$(curl -6s --connect-timeout 5 https://icanhazip.com 2>/dev/null)
fi
if [ -z "$ip" ]; then
ip=$(curl -6s --connect-timeout 5 https://ipecho.net/plain 2>/dev/null)
fi
fi
if [ -z "$ip" ]; then
log_error "Не удалось автоматически определить IP адрес сервера"
log_error "Пожалуйста, установите переменную окружения ADVERTISE_ADDR вручную"
exit 1
fi
echo "$ip"
}
advertise_addr="${ADVERTISE_ADDR:-$(get_ip)}"
log_info "Используем advertise address: $advertise_addr"
docker swarm init --advertise-addr $advertise_addr
if [ $? -ne 0 ]; then
log_error "Не удалось инициализировать Docker Swarm"
exit 1
fi
log_success "Docker Swarm инициализирован"
}
# =============================================================================
# РАЗДЕЛ 8: СОЗДАНИЕ DOCKER СЕТИ
# =============================================================================
# Создает единую сеть для всех сервисов
create_network() {
log_info "Создание Docker сети..."
# Удаляем существующую сеть если она есть
docker network rm -f dokploy-network 2>/dev/null || true
# Создаем overlay сеть
docker network create --driver overlay --attachable dokploy-network
log_success "Сеть 'dokploy-network' создана"
}
# =============================================================================
# РАЗДЕЛ 9: УСТАНОВКА DOKPLOY СЕРВИСОВ
# =============================================================================
# Устанавливает PostgreSQL, Redis и Dokploy (БЕЗ Traefik)
install_dokploy_services() {
log_info "Установка сервисов Dokploy (без Traefik)..."
# Создаем директории dokploy
mkdir -p /etc/dokploy
chmod 777 /etc/dokploy
# Сервис PostgreSQL
log_info "Создание сервиса PostgreSQL..."
docker service create \
--name dokploy-postgres \
--constraint 'node.role==manager' \
--network dokploy-network \
--env POSTGRES_USER=dokploy \
--env POSTGRES_DB=dokploy \
--env POSTGRES_PASSWORD=amukds4wi9001583845717ad2 \
--mount type=volume,source=dokploy-postgres-database,target=/var/lib/postgresql/data \
postgres:16
# Сервис Redis
log_info "Создание сервиса Redis..."
docker service create \
--name dokploy-redis \
--constraint 'node.role==manager' \
--network dokploy-network \
--mount type=volume,source=redis-data-volume,target=/data \
redis:7
# Скачиваем образ Dokploy
docker pull dokploy/dokploy:${RELEASE_TAG}
# Основной сервис Dokploy (без Traefik)
log_info "Создание основного сервиса Dokploy..."
# Устанавливаем переменные окружения для Dokploy
local dokploy_env=""
if [ -n "$DATABASE_URL" ]; then
dokploy_env="$dokploy_env -e DATABASE_URL=$DATABASE_URL"
fi
if [ -n "$REDIS_HOST" ]; then
dokploy_env="$dokploy_env -e REDIS_HOST=$REDIS_HOST"
fi
docker service create \
--name dokploy \
--replicas 1 \
--network dokploy-network \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
--publish published=${DOKPLOY_PORT},target=3000,mode=host \
--update-parallelism 1 \
--update-order stop-first \
--constraint 'node.role == manager' \
-e ADVERTISE_ADDR=$advertise_addr \
$dokploy_env \
dokploy/dokploy:${RELEASE_TAG}
log_success "Сервисы Dokploy созданы (без Traefik)"
}
# =============================================================================
# РАЗДЕЛ 10: СОЗДАНИЕ КОНФИГУРАЦИИ OPENAPPSEC + NPM
# =============================================================================
# Создает docker-compose.yml и конфигурацию для OpenAppSec + NPM
create_openappsec_compose() {
log_info "Создание конфигурации OpenAppSec + NPM..."
mkdir -p /opt/openappsec-npm
cd /opt/openappsec-npm
# Создаем docker-compose.yml (БЕЗ устаревшей строки version)
cat > docker-compose.yml << 'EOF'
services:
appsec-npm:
container_name: npm-attachment
image: 'ghcr.io/openappsec/nginx-proxy-manager-attachment:latest'
ipc: host
restart: unless-stopped
ports:
- '80:80' # Публичный HTTP порт
- '443:443' # Публичный HTTPS порт
- '81:81' # Админский веб-порт
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
- ./appsec-logs:/ext/appsec-logs
- ./appsec-localconfig:/ext/appsec
networks:
- dokploy-network
- default
appsec-agent:
container_name: appsec-agent
image: 'ghcr.io/openappsec/agent:latest'
network_mode: service:appsec-npm
ipc: host
restart: unless-stopped
environment:
- user_email=${APPSEC_EMAIL}
- nginxproxymanager=true
- autoPolicyLoad=true
volumes:
- ./appsec-config:/etc/cp/conf
- ./appsec-data:/etc/cp/data
- ./appsec-logs:/var/log/nano_agent
- ./appsec-localconfig:/ext/appsec
command: /cp-nano-agent --standalone
networks:
dokploy-network:
external: true
EOF
# Создаем .env файл
cat > .env << EOF
APPSEC_EMAIL=${ADMIN_EMAIL}
APPSEC_LEARNING_MODE=true
APPSEC_ML_ENABLED=true
EOF
log_success "Конфигурация OpenAppSec + NPM создана"
}
# =============================================================================
# РАЗДЕЛ 11: УСТАНОВКА OPENAPPSEC + NPM
# =============================================================================
# Устанавливает и настраивает OpenAppSec с оптимизированной политикой
install_openappsec() {
log_info "Установка OpenAppSec + Nginx Proxy Manager..."
cd /opt/openappsec-npm
# Создаем необходимые директории
mkdir -p data letsencrypt appsec-logs appsec-localconfig appsec-config appsec-data
# Создаем оптимизированную конфигурацию политики OpenAppSec
log_info "Создание оптимизированной политики OpenAppSec (local_policy.yaml)..."
cat > appsec-localconfig/local_policy.yaml << 'EOF'
policies:
default:
triggers:
- appsec-smart-log-trigger
mode: detect
practices:
- webapp-smart-practice
custom-response: appsec-smart-response
specific-rules:
# Полное доверие к локальным админским интерфейсам
- name: localhost-admin-bypass
host:
- "127.0.0.1"
- "127.0.0.1:81"
- "127.0.0.1:3000"
- "localhost"
- "localhost:81"
- "localhost:3000"
uri:
- "/api/*"
- "/dashboard/*"
- "/_next/*"
- "/static/*"
- "/admin/*"
- "/login"
- "/auth/*"
- "/nginx/*"
- "/openappsec-log/*"
- "/settings"
- "/settings/*"
- "/proxy"
- "/proxy/*"
mode: bypass
# Bypass для всех localhost referer'ов (ГЛАВНОЕ ИСПРАВЛЕНИЕ!)
- name: localhost-referer-bypass
referer:
- "http://127.0.0.1:*"
- "https://127.0.0.1:*"
- "http://localhost:*"
- "https://localhost:*"
- "http://172.*:*"
- "https://172.*:*"
mode: bypass
# Docker внутренние сети
- name: docker-networks-bypass
source-ip:
- "172.16.0.0/12" # Docker networks
- "192.168.0.0/16" # Private networks
- "10.0.0.0/8" # Private networks
mode: bypass
# Dokploy специфические пути
- name: dokploy-admin-paths
host: "*"
uri:
- "/api/auth/*"
- "/api/deploy/*"
- "/api/docker/*"
- "/api/projects/*"
- "/api/services/*"
- "/_next/static/*"
- "/favicon.ico"
- "/manifest.json"
mode: bypass
# NPM админка
- name: npm-admin-paths
host: "*"
uri:
- "/api/nginx/*"
- "/api/users/*"
- "/api/settings/*"
- "/api/certificates/*"
- "/assets/*"
- "/js/*"
- "/css/*"
mode: bypass
practices:
- name: webapp-smart-practice
# Веб-атаки с умными настройками для localhost
web-attacks:
max-body-size-kb: 2000000 # Увеличено для админок
max-header-size-bytes: 204800 # Увеличено для сложных запросов
max-object-depth: 50
max-url-size-bytes: 65536
minimum-confidence: medium # Средний уровень для баланса
override-mode: detect
protections:
csrf-protection: detect
error-disclosure: detect
non-valid-http-methods: false # Отключено для REST API
open-redirect: detect
# Продвинутая анти-бот защита с исключениями для localhost
anti-bot:
injected-URIs: []
validated-URIs:
- "/api/auth/login"
- "/api/users/profile"
- "/nginx/proxy"
- "/settings"
override-mode: detect
# Snort сигнатуры с пониженной чувствительностью для localhost
snort-signatures:
configmap:
- sql_injection
- cross_site_scripting
# НЕ включаем remote_code_execution для localhost!
- local_file_inclusion
- path_traversal
override-mode: detect
# Валидация OpenAPI
openapi-schema-validation:
configmap: []
override-mode: detect
# Умное логирование с фильтрацией localhost событий
log-triggers:
- name: appsec-smart-log-trigger
access-control-logging:
allow-events: true
drop-events: true
additional-suspicious-events-logging:
enabled: true
minimum-severity: medium # Средний уровень для уменьшения шума
response-body: false
response-code: true
appsec-logging:
all-web-requests: false # Отключено для производительности
detect-events: true
prevent-events: true
extended-logging:
http-headers: false # Отключено для приватности
request-body: false # Отключено для производительности
url-path: true
url-query: false # Отключено для админок
log-destination:
cloud: false
stdout:
format: json
# Умные ответы
custom-responses:
- name: appsec-smart-response
mode: response-code-only
http-response-code: 403
EOF
log_success "Политика OpenAppSec настроена для игнорирования ложных срабатываний localhost"
# Запускаем сервисы
log_info "Запуск контейнеров OpenAppSec + NPM..."
docker-compose up -d
log_success "OpenAppSec + NPM установлены на портах 80/443"
}
# =============================================================================
# РАЗДЕЛ 12: ОЖИДАНИЕ ЗАПУСКА СЕРВИСОВ
# =============================================================================
# Ждет, пока все сервисы полностью запустятся
wait_for_services() {
log_info "Ожидание запуска сервисов..."
# Ждем готовности NPM
local npm_ready=false
local attempts=0
local max_attempts=30
while [ "$npm_ready" = false ] && [ $attempts -lt $max_attempts ]; do
if curl -s http://localhost:81 > /dev/null 2>&1; then
npm_ready=true
log_success "NPM готов"
else
log_info "Ожидание запуска NPM... (попытка $((attempts + 1))/$max_attempts)"
sleep 10
attempts=$((attempts + 1))
fi
done
if [ "$npm_ready" = false ]; then
log_error "NPM не удалось запустить в ожидаемое время"
exit 1
fi
# Ждем готовности Dokploy
local dokploy_ready=false
attempts=0
while [ "$dokploy_ready" = false ] && [ $attempts -lt $max_attempts ]; do
if curl -s http://localhost:${DOKPLOY_PORT} > /dev/null 2>&1; then
dokploy_ready=true
log_success "Dokploy готов"
else
log_info "Ожидание запуска Dokploy... (попытка $((attempts + 1))/$max_attempts)"
sleep 10
attempts=$((attempts + 1))
fi
done
if [ "$dokploy_ready" = false ]; then
log_error "Dokploy не удалось запустить в ожидаемое время"
exit 1
fi
}
# =============================================================================
# РАЗДЕЛ 13: АВТОМАТИЧЕСКАЯ НАСТРОЙКА NPM
# =============================================================================
# Автоматически настраивает NPM и создает proxy host для Dokploy
configure_npm_automatically() {
log_info "Автоматическая настройка Nginx Proxy Manager..."
# Ждем еще немного для полной готовности сервисов
sleep 15
# Получаем токен авторизации
log_info "Получение токена авторизации NPM..."
local auth_response=$(curl -s -X POST http://localhost:81/api/tokens \
-H "Content-Type: application/json" \
-d '{
"identity": "[email protected]",
"secret": "changeme"
}')
local token=$(echo "$auth_response" | jq -r '.token // empty')
if [ -z "$token" ] || [ "$token" = "null" ]; then
log_warning "Не удалось получить токен авторизации NPM автоматически. Вам потребуется настроить NPM вручную."
return 1
fi
log_success "Получен токен авторизации NPM"
# Изменяем email и пароль администратора
log_info "Обновление учетных данных администратора NPM..."
local user_update=$(curl -s -X PUT http://localhost:81/api/users/1 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d "{
\"email\": \"${ADMIN_EMAIL}\",
\"nickname\": \"Administrator\",
\"is_disabled\": false,
\"roles\": [\"admin\"]
}")
# Устанавливаем новый пароль
local password_set=$(curl -s -X PUT http://localhost:81/api/users/1/auth \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d "{
\"type\": \"password\",
\"current\": \"changeme\",
\"secret\": \"${NPM_ADMIN_PASSWORD}\"
}")
log_success "Учетные данные администратора NPM обновлены"
# Создаем proxy host для Dokploy - пробуем несколько подходов
log_info "Создание proxy host для Dokploy..."
# Метод 1: Пробуем получить реальный IP контейнера сервиса
local dokploy_container_id=$(docker ps --filter "ancestor=dokploy/dokploy:latest" --format "{{.ID}}" | head -1)
local dokploy_ip=""
if [ -n "$dokploy_container_id" ]; then
local network_id=$(docker network inspect dokploy-network --format '{{.Id}}' 2>/dev/null)
if [ -n "$network_id" ]; then
dokploy_ip=$(docker inspect $dokploy_container_id --format "{{range .NetworkSettings.Networks}}{{if eq .NetworkID \"$network_id\"}}{{.IPAddress}}{{end}}{{end}}" 2>/dev/null)
if [ -n "$dokploy_ip" ] && [ "$dokploy_ip" != "<no value>" ] && [ "$dokploy_ip" != ".IPAddress" ]; then
log_info "Найден IP контейнера Dokploy: $dokploy_ip"
else
dokploy_ip=""
fi
fi
fi
# Метод 2: Пробуем инспекцию сервиса
if [ -z "$dokploy_ip" ]; then
local network_id=$(docker network inspect dokploy-network --format '{{.Id}}' 2>/dev/null)
if [ -n "$network_id" ]; then
dokploy_ip=$(docker service inspect dokploy --format "{{range .Endpoint.VirtualIPs}}{{if eq .NetworkID \"$network_id\"}}{{.Addr}}{{end}}{{end}}" 2>/dev/null | cut -d'/' -f1)
fi
if [ -n "$dokploy_ip" ] && [ "$dokploy_ip" != "<no value>" ] && [ "$dokploy_ip" != ".IPAddress" ]; then
log_info "Найден IP сервиса Dokploy: $dokploy_ip"
else
dokploy_ip=""
fi
fi
# Метод 3: Используем имя сервиса как резерв
if [ -z "$dokploy_ip" ]; then
dokploy_ip="dokploy"
log_info "Используем имя сервиса 'dokploy' как резерв"
fi
local dokploy_port=3000
# Тестируем соединение перед созданием proxy host
log_info "Тестирование соединения с Dokploy на $dokploy_ip:$dokploy_port..."
local test_result="200"
# Пробуем тестировать соединение изнутри сети
if docker run --rm --network dokploy-network alpine/curl:latest -s --connect-timeout 5 http://$dokploy_ip:$dokploy_port >/dev/null 2>&1; then
test_result="200"
log_success "Успешно подключились к Dokploy на $dokploy_ip:$dokploy_port"
else
test_result="000"
log_warning "Не удается достичь Dokploy на $dokploy_ip:$dokploy_port, но попробуем создать proxy host в любом случае"
fi
# Ждем еще немного для полной готовности NPM
sleep 5
# Создаем proxy host с улучшенной обработкой ошибок
log_info "Создание proxy host с настройками: Domain=dokploy.local, Target=$dokploy_ip:$dokploy_port"
local proxy_host_response=$(curl -s -w "HTTPSTATUS:%{http_code}" -X POST http://localhost:81/api/nginx/proxy-hosts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d "{
\"domain_names\": [\"dokploy.local\"],
\"forward_scheme\": \"http\",
\"forward_host\": \"$dokploy_ip\",
\"forward_port\": $dokploy_port,
\"access_list_id\": 0,
\"certificate_id\": 0,
\"ssl_forced\": false,
\"caching_enabled\": false,
\"block_exploits\": true,
\"advanced_config\": \"proxy_set_header Host \\$host;\\nproxy_set_header X-Real-IP \\$remote_addr;\\nproxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\\nproxy_set_header X-Forwarded-Proto \\$scheme;\\nproxy_read_timeout 300;\\nproxy_connect_timeout 300;\\nproxy_send_timeout 300;\",
\"meta\": {
\"letsencrypt_agree\": false,
\"dns_challenge\": false
},
\"allow_websocket_upgrade\": true,
\"http2_support\": true,
\"forward_host_header\": true,
\"hsts_enabled\": false,
\"hsts_subdomains\": false
}")
# Извлекаем HTTP статус и тело ответа
local http_status=$(echo "$proxy_host_response" | grep -o "HTTPSTATUS:[0-9]*" | cut -d: -f2)
local proxy_host_body=$(echo "$proxy_host_response" | sed 's/HTTPSTATUS:[0-9]*$//')
log_info "Статус ответа NPM API: $http_status"
if [ "$http_status" = "201" ] || [ "$http_status" = "200" ]; then
if echo "$proxy_host_body" | jq -e '.id' > /dev/null 2>&1; then
local proxy_id=$(echo "$proxy_host_body" | jq -r '.id')
log_success "Proxy host успешно создан для Dokploy (ID: $proxy_id, Target: $dokploy_ip:$dokploy_port)"
else
log_success "Proxy host создан для Dokploy (Target: $dokploy_ip:$dokploy_port)"
fi
else
log_warning "Не удалось создать proxy host автоматически (HTTP $http_status)"
log_info "Ответ: $proxy_host_body"
log_info "Вы можете создать его вручную в NPM с этими настройками:"
log_info " Domain: dokploy.local"
log_info " Forward Host: $dokploy_ip"
log_info " Forward Port: $dokploy_port"
log_info " Scheme: http"
log_info " Advanced config: Добавьте proxy таймауты для лучшей стабильности"
# Пробуем альтернативный подход - создаем без advanced config
log_info "Пробуем создать proxy host без advanced config..."
local simple_proxy_response=$(curl -s -w "HTTPSTATUS:%{http_code}" -X POST http://localhost:81/api/nginx/proxy-hosts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d "{
\"domain_names\": [\"dokploy.local\"],
\"forward_scheme\": \"http\",
\"forward_host\": \"$dokploy_ip\",
\"forward_port\": $dokploy_port,
\"access_list_id\": 0,
\"certificate_id\": 0,
\"ssl_forced\": false,
\"caching_enabled\": false,
\"block_exploits\": false,
\"meta\": {
\"letsencrypt_agree\": false,
\"dns_challenge\": false
},
\"allow_websocket_upgrade\": true,
\"http2_support\": false,
\"forward_host_header\": true,
\"hsts_enabled\": false,
\"hsts_subdomains\": false
}")
local simple_http_status=$(echo "$simple_proxy_response" | grep -o "HTTPSTATUS:[0-9]*" | cut -d: -f2)
local simple_proxy_body=$(echo "$simple_proxy_response" | sed 's/HTTPSTATUS:[0-9]*$//')
if [ "$simple_http_status" = "201" ] || [ "$simple_http_status" = "200" ]; then
if echo "$simple_proxy_body" | jq -e '.id' > /dev/null 2>&1; then
local simple_proxy_id=$(echo "$simple_proxy_body" | jq -r '.id')
log_success "Простой proxy host успешно создан для Dokploy (ID: $simple_proxy_id, Target: $dokploy_ip:$dokploy_port)"
else
log_success "Простой proxy host создан для Dokploy (Target: $dokploy_ip:$dokploy_port)"
fi
else
log_warning "Обе попытки создания proxy host не удались"
log_info "Простой ответ: $simple_proxy_body"
fi
fi
}
# =============================================================================
# РАЗДЕЛ 14: ИСПРАВЛЕНИЕ ПРАВ DOCKER
# =============================================================================
# Исправляет права Docker для текущего пользователя
fix_docker_permissions() {
log_info "Исправление прав Docker для текущего пользователя..."
# Добавляем текущего пользователя в группу docker если еще не добавлен
if [ -n "$SUDO_USER" ]; then
usermod -aG docker "$SUDO_USER" 2>/dev/null || true
log_success "Пользователь $SUDO_USER добавлен в группу docker"
# Пробуем исправить права сокета
chmod 666 /var/run/docker.sock 2>/dev/null || true
log_info "Для использования Docker без sudo, выполните: newgrp docker"
log_info "Или выйдите и войдите снова"
else
log_warning "Не удалось определить текущего пользователя для прав Docker"
fi
}
# =============================================================================
# РАЗДЕЛ 15: ТЕСТИРОВАНИЕ NPM PROXY
# =============================================================================
# Тестирует работу NPM proxy
test_npm_proxy() {
log_info "Тестирование конфигурации NPM proxy..."
# Ждем активации proxy
sleep 10
# Тестируем, работает ли proxy, проверяя, разрешается ли dokploy.local через NPM
local test_result=$(curl -s -H "Host: dokploy.local" http://localhost:80 2>/dev/null | head -c 100)
if echo "$test_result" | grep -q "DOCTYPE\|html\|Dokploy" 2>/dev/null; then
log_success "NPM proxy работает! Вы можете получить доступ к Dokploy через dokploy.local"
else
log_info "Тест NPM proxy неубедителен. Рекомендуется ручная проверка."
fi
}
# =============================================================================
# РАЗДЕЛ 16: ПОЛУЧЕНИЕ ПУБЛИЧНОГО IP
# =============================================================================
# Получает публичный IP адрес сервера
get_public_ip() {
local ip=""
# Сначала пробуем IPv4
ip=$(curl -4s --connect-timeout 5 https://ifconfig.io 2>/dev/null)
if [ -z "$ip" ]; then
ip=$(curl -4s --connect-timeout 5 https://icanhazip.com 2>/dev/null)
fi
if [ -z "$ip" ]; then
ip=$(curl -4s --connect-timeout 5 https://ipecho.net/plain 2>/dev/null)
fi
if [ -z "$ip" ]; then
echo "localhost"
else
echo "$ip"
fi
}
format_ip_for_url() {
local ip="$1"
if echo "$ip" | grep -q ':'; then
# IPv6
echo "[${ip}]"
else
# IPv4
echo "${ip}"
fi
}
# =============================================================================
# РАЗДЕЛ 17: СООБЩЕНИЕ О ЗАВЕРШЕНИИ
# =============================================================================
# Показывает итоговое сообщение с информацией о доступе
show_completion_message() {
local public_ip=$(get_public_ip)
local formatted_addr=$(format_ip_for_url "$public_ip")
echo ""
echo "===================================================================================="
printf "${GREEN}🎉 Установка Dokploy + Nginx Proxy Manager завершена!${NC}\n"
echo "===================================================================================="
echo ""
printf "${BLUE}Развернутая архитектура:${NC}\n"
printf " Internet → Nginx Proxy Manager + OpenAppSec → Dokploy Apps (БЕЗ Traefik)\n"
echo ""
printf "${BLUE}Установленные сервисы:${NC}\n"
printf " ✅ Dokploy (с PostgreSQL, Redis) - БЕЗ Traefik\n"
printf " ✅ OpenAppSec + Nginx Proxy Manager\n"
printf " ✅ Единая Docker сеть: dokploy-network\n"
echo ""
printf "${BLUE}URL для доступа:${NC}\n"
printf " 🌐 Dokploy Dashboard: ${YELLOW}http://${formatted_addr}:${DOKPLOY_PORT}${NC}\n"
printf " 🔒 Nginx Proxy Manager: ${YELLOW}http://${formatted_addr}:81${NC}\n"
echo ""
printf "${BLUE}Поток трафика:${NC}\n"
printf " • Порт 80/443: Публичный трафик → NPM + OpenAppSec (слой безопасности)\n"
printf " • NPM перенаправляет напрямую к: сервису Dokploy (порт 3000)\n"
printf " • Ваши приложения, развернутые в Dokploy, будут доступны через NPM\n"
echo ""
printf "${BLUE}Учетные данные NPM:${NC}\n"
printf " Email: ${YELLOW}${ADMIN_EMAIL}${NC}\n"
printf " Пароль: ${YELLOW}${NPM_ADMIN_PASSWORD}${NC}\n"
echo ""
printf "${BLUE}Конфигурация:${NC}\n"
printf " • Порт Dokploy: ${DOKPLOY_PORT}\n"
printf " • Тег релиза: ${RELEASE_TAG}\n"
printf " • Proxy host настроен для: dokploy.local\n"
printf " • Firewall: nftables настроен (порты 80, 443 открыты)\n"
printf " • Права пользователя Docker: настроены для $SUDO_USER\n"
echo ""
printf "${YELLOW}⚠️ Важные примечания:${NC}\n"
printf " • Traefik НЕ установлен - NPM обрабатывает всю маршрутизацию\n"
printf " • Настройте домены ваших приложений через интерфейс NPM\n"
printf " • Направьте ваши домены на этот IP сервера: ${formatted_addr}\n"
printf " • Для локального тестирования добавьте в /etc/hosts: ${formatted_addr} dokploy.local\n"
printf " • Выполните 'newgrp docker' или выйдите/войдите для использования Docker без sudo\n"
echo ""
printf "${GREEN}Подождите 15 секунд для полного запуска всех сервисов!${NC}\n"
echo "===================================================================================="
}
# =============================================================================
# РАЗДЕЛ 18: ФУНКЦИЯ ОБНОВЛЕНИЯ
# =============================================================================
# Обновляет Dokploy и NPM до последних версий
update_dokploy() {
log_info "Обновление Dokploy..."
# Скачиваем последний образ
docker pull dokploy/dokploy:${RELEASE_TAG}
# Обновляем сервис
docker service update --image dokploy/dokploy:${RELEASE_TAG} dokploy
# Обновляем OpenAppSec + NPM
if [ -d "/opt/openappsec-npm" ]; then
cd /opt/openappsec-npm
docker-compose pull
docker-compose up -d
fi
log_success "Dokploy и NPM успешно обновлены"
}
# =============================================================================
# РАЗДЕЛ 19: ОСНОВНАЯ ФУНКЦИЯ
# =============================================================================
# Главная функция, которая выполняет всю установку
main() {
log_info "Запуск установки Dokploy + Nginx Proxy Manager..."
log_info "Архитектура: Internet → NPM + OpenAppSec → Dokploy Apps (БЕЗ Traefik)"
cleanup_existing
check_requirements
setup_firewall
install_docker
setup_docker_swarm
create_network
install_dokploy_services
create_openappsec_compose
install_openappsec
wait_for_services
configure_npm_automatically
fix_docker_permissions
test_npm_proxy
show_completion_message
}
# =============================================================================
# РАЗДЕЛ 20: ОБРАБОТКА АРГУМЕНТОВ СКРИПТА
# =============================================================================
# Обрабатывает аргументы командной строки
case "${1:-}" in
"update")
update_dokploy
;;
"clean")
log_info "Выполнение полной очистки..."
cleanup_existing
docker system prune -af --volumes
log_success "Полная очистка завершена"
;;
*)
main
;;
esac
Last updated
Was this helpful?