upd nginx with tls
This commit is contained in:
parent
c307f48ebb
commit
90977abda7
7 changed files with 167 additions and 17 deletions
12
README.md
12
README.md
|
|
@ -78,6 +78,18 @@ docker compose down
|
|||
- В образ копируется весь проект, а runtime-данные исключены через `.dockerignore`.
|
||||
- Если меняешь код или картинки, достаточно пересобрать: `docker compose up --build -d`.
|
||||
|
||||
## HTTPS / Let's Encrypt
|
||||
|
||||
- Фронтендовый `nginx` теперь слушает `80` и `443`, отдает ACME challenge и сам запускает `certbot` внутри контейнера.
|
||||
- Для автоматической выдачи сертификата задай в `.env`:
|
||||
- `APP_DOMAIN=bot.example.com`
|
||||
- `APP_WWW_DOMAIN=www.bot.example.com` (опционально)
|
||||
- `ACME_EMAIL=admin@example.com`
|
||||
- Домен должен уже смотреть на сервер, а порты `80` и `443` должны быть доступны снаружи.
|
||||
- Для безопасной первой проверки можно временно включить `CERTBOT_STAGING=1`, потом вернуть `0`.
|
||||
- Сертификаты и webroot challenge хранятся в docker volumes `certbot_etc` и `certbot_www`, обновление запускается по кругу с интервалом `CERTBOT_RENEW_INTERVAL` (по умолчанию `12h`).
|
||||
- Если нужен старый локальный маппинг портов, можно переопределить `WEBAPP_HTTP_PORT` и `WEBAPP_HTTPS_PORT`, но для реального Let's Encrypt внешний `80` должен оставаться доступным.
|
||||
|
||||
## Кратко о логике
|
||||
|
||||
- `/maket` подбирает размер текста, чтобы уместить его в область макета.
|
||||
|
|
|
|||
|
|
@ -36,7 +36,21 @@ services:
|
|||
build:
|
||||
context: ./webapp/frontend
|
||||
ports:
|
||||
- "3000:80"
|
||||
- "${WEBAPP_HTTP_PORT:-80}:80"
|
||||
- "${WEBAPP_HTTPS_PORT:-443}:443"
|
||||
environment:
|
||||
APP_DOMAIN: ${APP_DOMAIN:-}
|
||||
APP_WWW_DOMAIN: ${APP_WWW_DOMAIN:-}
|
||||
ACME_EMAIL: ${ACME_EMAIL:-}
|
||||
CERTBOT_STAGING: ${CERTBOT_STAGING:-0}
|
||||
CERTBOT_RENEW_INTERVAL: ${CERTBOT_RENEW_INTERVAL:-12h}
|
||||
volumes:
|
||||
- certbot_etc:/etc/letsencrypt
|
||||
- certbot_www:/var/www/certbot
|
||||
depends_on:
|
||||
- webapp-api
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
certbot_etc:
|
||||
certbot_www:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,18 @@
|
|||
FROM nginx:alpine
|
||||
COPY . /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
EXPOSE 80
|
||||
|
||||
RUN apk add --no-cache certbot openssl
|
||||
|
||||
COPY index.html /usr/share/nginx/html/index.html
|
||||
COPY css /usr/share/nginx/html/css
|
||||
COPY js /usr/share/nginx/html/js
|
||||
COPY nginx.conf /etc/nginx/default.conf.template
|
||||
COPY docker-entrypoint.d/01-render-nginx-conf.sh /docker-entrypoint.d/01-render-nginx-conf.sh
|
||||
COPY docker-entrypoint.d/05-init-self-signed.sh /docker-entrypoint.d/05-init-self-signed.sh
|
||||
COPY docker-entrypoint.d/10-certbot-loop.sh /docker-entrypoint.d/10-certbot-loop.sh
|
||||
|
||||
RUN chmod +x /docker-entrypoint.d/01-render-nginx-conf.sh \
|
||||
/docker-entrypoint.d/05-init-self-signed.sh \
|
||||
/docker-entrypoint.d/10-certbot-loop.sh \
|
||||
&& rm -f /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80 443
|
||||
|
|
|
|||
20
webapp/frontend/docker-entrypoint.d/01-render-nginx-conf.sh
Normal file
20
webapp/frontend/docker-entrypoint.d/01-render-nginx-conf.sh
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
APP_DOMAIN="${APP_DOMAIN:-}"
|
||||
APP_WWW_DOMAIN="${APP_WWW_DOMAIN:-}"
|
||||
|
||||
PRIMARY_DOMAIN="${APP_DOMAIN:-localhost}"
|
||||
SERVER_NAMES="_"
|
||||
|
||||
if [ -n "$APP_DOMAIN" ]; then
|
||||
SERVER_NAMES="$APP_DOMAIN"
|
||||
if [ -n "$APP_WWW_DOMAIN" ]; then
|
||||
SERVER_NAMES="$SERVER_NAMES $APP_WWW_DOMAIN"
|
||||
fi
|
||||
fi
|
||||
|
||||
sed \
|
||||
-e "s|__SERVER_NAMES__|$SERVER_NAMES|g" \
|
||||
-e "s|__PRIMARY_DOMAIN__|$PRIMARY_DOMAIN|g" \
|
||||
/etc/nginx/default.conf.template > /etc/nginx/conf.d/default.conf
|
||||
16
webapp/frontend/docker-entrypoint.d/05-init-self-signed.sh
Normal file
16
webapp/frontend/docker-entrypoint.d/05-init-self-signed.sh
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PRIMARY_DOMAIN="${APP_DOMAIN:-localhost}"
|
||||
CERT_DIR="/etc/letsencrypt/live/$PRIMARY_DOMAIN"
|
||||
FULLCHAIN="$CERT_DIR/fullchain.pem"
|
||||
PRIVKEY="$CERT_DIR/privkey.pem"
|
||||
|
||||
if [ ! -s "$FULLCHAIN" ] || [ ! -s "$PRIVKEY" ]; then
|
||||
echo "No TLS certificate for $PRIMARY_DOMAIN. Generating temporary self-signed cert."
|
||||
mkdir -p "$CERT_DIR"
|
||||
openssl req -x509 -nodes -newkey rsa:2048 -days 1 \
|
||||
-keyout "$PRIVKEY" \
|
||||
-out "$FULLCHAIN" \
|
||||
-subj "/CN=$PRIMARY_DOMAIN" >/dev/null 2>&1
|
||||
fi
|
||||
47
webapp/frontend/docker-entrypoint.d/10-certbot-loop.sh
Normal file
47
webapp/frontend/docker-entrypoint.d/10-certbot-loop.sh
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
APP_DOMAIN="${APP_DOMAIN:-}"
|
||||
APP_WWW_DOMAIN="${APP_WWW_DOMAIN:-}"
|
||||
ACME_EMAIL="${ACME_EMAIL:-}"
|
||||
CERTBOT_STAGING="${CERTBOT_STAGING:-0}"
|
||||
CERTBOT_RENEW_INTERVAL="${CERTBOT_RENEW_INTERVAL:-12h}"
|
||||
|
||||
if [ -z "$APP_DOMAIN" ] || [ -z "$ACME_EMAIL" ]; then
|
||||
echo "Skipping certbot: APP_DOMAIN or ACME_EMAIL is empty."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
(
|
||||
sleep 20
|
||||
|
||||
if [ ! -f "/etc/letsencrypt/renewal/$APP_DOMAIN.conf" ]; then
|
||||
rm -rf "/etc/letsencrypt/live/$APP_DOMAIN" "/etc/letsencrypt/archive/$APP_DOMAIN"
|
||||
fi
|
||||
|
||||
while :; do
|
||||
DOMAINS="-d $APP_DOMAIN"
|
||||
if [ -n "$APP_WWW_DOMAIN" ]; then
|
||||
DOMAINS="$DOMAINS -d $APP_WWW_DOMAIN"
|
||||
fi
|
||||
|
||||
STAGING_FLAG=""
|
||||
if [ "$CERTBOT_STAGING" = "1" ]; then
|
||||
STAGING_FLAG="--staging"
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
certbot certonly \
|
||||
--webroot \
|
||||
-w /var/www/certbot \
|
||||
--agree-tos \
|
||||
--non-interactive \
|
||||
--email "$ACME_EMAIL" \
|
||||
--keep-until-expiring \
|
||||
$STAGING_FLAG \
|
||||
$DOMAINS || true
|
||||
|
||||
nginx -s reload || true
|
||||
sleep "$CERTBOT_RENEW_INTERVAL"
|
||||
done
|
||||
) &
|
||||
|
|
@ -1,25 +1,52 @@
|
|||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
listen [::]:80;
|
||||
server_name __SERVER_NAMES__;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name __SERVER_NAMES__;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/__PRIMARY_DOMAIN__/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/__PRIMARY_DOMAIN__/privkey.pem;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# SPA fallback
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://webapp-api:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
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_set_header X-Telegram-Init-Data $http_x_telegram_init_data;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Proxy API requests to backend
|
||||
location /api/ {
|
||||
proxy_pass http://webapp-api:8080;
|
||||
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-Telegram-Init-Data $http_x_telegram_init_data;
|
||||
}
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(css|js|png|jpg|svg|woff2?)$ {
|
||||
location ~* \.(css|js|png|jpg|jpeg|svg|woff2?)$ {
|
||||
expires 7d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue