deploy / deploy (push) Successful in 4s
- Главная: тихая кнопка «Помахать» (web/js/wave.js), общий счётчик для всех посетителей - server/server.mjs: мини-API (/api/waves, /api/wave), счётчик хранится вне репозитория (/var/lib/vizitka-site/waves.json), лимит раз в секунду с IP, проверка Origin - deploy: systemd-сервис vizitka-api, проксирование /api в nginx, setup-server.sh ставит nodejs - CI: после git pull перезапускает API - Домен https://me.ibrash.duckdns.org вместо заглушки, исправлен пробел в шапке - README и заметки обновлены Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
68 lines
3.2 KiB
Plaintext
68 lines
3.2 KiB
Plaintext
# nginx: статический сайт-визитка. Репозиторий клонирован в /opt/vizitka-site, раздаём его папку web/
|
|
server {
|
|
listen 80 default_server;
|
|
listen [::]:80 default_server;
|
|
server_name _;
|
|
|
|
root /opt/vizitka-site/web;
|
|
index index.html;
|
|
|
|
# реальный IP посетителя из X-Forwarded-For, если запрос пришёл от прокси из локальной сети (для лимита в API)
|
|
set_real_ip_from 10.0.0.0/8;
|
|
set_real_ip_from 172.16.0.0/12;
|
|
set_real_ip_from 192.168.0.0/16;
|
|
set_real_ip_from 127.0.0.0/8;
|
|
real_ip_header X-Forwarded-For;
|
|
real_ip_recursive on;
|
|
|
|
# сжатие
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_min_length 512;
|
|
gzip_types text/css application/javascript application/json image/svg+xml text/plain application/xml;
|
|
|
|
# заголовки безопасности (строгий CSP: на сайте нет inline-скриптов и стилей, внешних ресурсов нет)
|
|
server_tokens off;
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
|
|
|
|
# API кнопки «Помахать» (Node, systemd-сервис vizitka-api), счётчик хранится вне репозитория
|
|
location /api/ {
|
|
proxy_pass http://127.0.0.1:8787;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Client-IP $remote_addr;
|
|
proxy_read_timeout 10s;
|
|
client_max_body_size 1k;
|
|
}
|
|
|
|
location / {
|
|
try_files $uri $uri/ =404;
|
|
}
|
|
|
|
# кэш: картинки и шрифты надолго, стили и скрипты на час, данные без кэша
|
|
location ~* \.(woff2|webp|png|jpe?g|svg|ico)$ {
|
|
expires 30d;
|
|
add_header Cache-Control "public, max-age=2592000" always;
|
|
# add_header в location сбрасывает наследуемые, поэтому дублируем безопасные заголовки
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
}
|
|
location ~* \.(css|js)$ {
|
|
add_header Cache-Control "public, max-age=3600" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
}
|
|
location = /config.json {
|
|
add_header Cache-Control "no-cache" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
}
|
|
|
|
# скрытые файлы наружу не отдаём
|
|
location ~ /\. { deny all; }
|
|
|
|
error_page 404 /404.html;
|
|
location = /404.html { internal; }
|
|
}
|