diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ee8d48e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,91 @@ +# pushkinohistory.ru — Vite+React v2 + +Сайт «История города Пушкино». Редизайн с WordPress (v1, контейнер `pushkinohistory-ru:4143`) на статический Vite+React+Tailwind (контейнер `pushkinohistory-ru-v2:4146`). + +## Стек + +- Vite 6 + React 18 + Tailwind 3 +- PT Serif (заголовки/основной текст) + IBM Plex Sans (UI) +- Express 4 + compression (runtime сервер, отдаёт prerendered HTML + `/api/news.json` + `/feed/`) +- Puppeteer для prerender (chromium в build-стадии) +- fast-xml-parser для агрегатора внешних RSS + +## Структура + +``` +src/ + App.jsx # клиентский роутер (window.history + popstate), 301-карта oldSlug → newSlug + components/ # Header, Sidebar, Footer, PostCard + content/ # JSON-контент: posts, pages, partners, ads, transport, feeds + pages/ # Home, Post, Page, Category, News, NotFound +server/index.js # Express: /api/health, /api/news.json, /uploads/, /feed/, статика +scripts/ + convert_posts.py # WP DB → src/content/{posts,pages}.json + build-rss.js # генератор IPB-совместимого RSS (full content в CDATA) + build-sitemap.js # sitemap.xml + robots.txt + build-slugs.js # routes.json для prerender + prerender.js # SPA → статичные HTML по маршрутам через puppeteer + pull-external-rss.js # cron: внешние RSS → data/news.json (агрегатор) +public/uploads/ # картинки, перенесены из WP /wp-content/uploads/ +nginx/ # vhost для прода (симлинк из /etc/nginx/conf.d/) +``` + +## Контент + +Скрейп из WP DB (`pushkinohistory_ru` на `db.hhivp.com`): +- 7 постов + 4 страницы +- 6 картинок в `public/uploads/` +- URL-encoded кириллические slugs WP → транслитерированы (`/segodnya-nochyu-rossiyane-uvidyat-pervoe/`); старые URL → 301 через `nginx/map` + +## RSS + +- **Свой `/feed/`** — IPB-совместимый RSS 2.0 с полным HTML в ``, стабильными ``, ``, категориями. Для импорта в `forum.pushkinohistory.ru`. +- **Внешние фиды** — `src/content/feeds.json` (список URL), парсятся cron-скриптом `scripts/pull-external-rss.js` → `data/news.json` (bind-mount), фронт читает client-side через `/api/news.json`. Каждое добавление источника = редактирование `feeds.json` + push. + +## Деплой + +```bash +# Локально: +cd E:\Projects\pushkinohistory-ru-v2 +git add . && git commit -m "..." && git push + +# На web.hhivp.com: +ssh striker@web.hhivp.com +cd /opt/docker/sites/pushkinohistory-ru-v2 +git pull && docker compose up -d --build +``` + +CI/CD автоматизация — Gitea Actions с SSH-deploy (см. `.gitea/workflows/deploy.yml`). + +## Контейнер + +- Image: `pushkinohistory-ru-v2:latest` (мультистейдж: builder с puppeteer/chromium → runtime node:22-alpine + express) +- Порт: `127.0.0.1:4146` +- Bind-mounts: + - `/opt/www/pushkinohistory.ru/uploads-v2 → /app/public/uploads (ro)` — динамические uploads + - `/opt/docker/sites/pushkinohistory-ru-v2/data → /app/data (ro)` — `news.json` от cron +- nginx vhost: `/etc/nginx/conf.d/pushkinohistory.ru` → симлинк на `nginx/pushkinohistory.ru.conf` в этом репо + +## Откат на WP v1 + +Старый WP в `/opt/docker/sites/pushkinohistory-ru/` (контейнер `pushkinohistory-ru:4143`) сохранён. Чтобы откатиться: + +```bash +ssh striker@web.hhivp.com +sudo ln -sfn /opt/docker/sites/pushkinohistory-ru/nginx/pushkinohistory.ru.conf /etc/nginx/conf.d/pushkinohistory.ru +sudo nginx -t && sudo systemctl reload nginx +``` + +## БД (WP, v1) + +DB на `db.hhivp.com` (45.10.53.205), `pushkinohistory_ru`/`u_pushhist`, prefix `ph_`. **Не удалять** — нужна для отката и как источник для повторного скрейпа. + +## Форум + +`forum.pushkinohistory.ru` — IPB 4.x в отдельном контейнере `forum-pushkinohistory-ru:4144`. **v2 редизайном не затронут.** + +## История + +- 2026-05-08: v1 контейнеризован, миграция со str-u-01 на web.hhivp.com +- 2026-05-14: фикс trust-proxy.conf для Docker bridges (Better WP Security) +- 2026-05-21: v2 редизайн — Vite+React+Tailwind, отказ от WP, RSS-агрегатор внешних фидов + свой RSS для IPB diff --git a/nginx/pushkinohistory.ru.conf b/nginx/pushkinohistory.ru.conf new file mode 100644 index 0000000..087d657 --- /dev/null +++ b/nginx/pushkinohistory.ru.conf @@ -0,0 +1,100 @@ +# pushkinohistory.ru — Vite+React (v2) +# Container: pushkinohistory-ru-v2 on 127.0.0.1:4146 +# v2 cutover: 2026-05-21 (старый WP на :4143 оставлен в /opt/docker/sites/pushkinohistory-ru как backup) + +# 301-редиректы со старых URL-encoded WP slugs (cyrillic) на новые транслитерированные. +# nginx уже декодирует URI до cyrillic'а, поэтому в ключах map'а — кириллица в UTF-8. +map $request_uri $legacy_redirect { + default ""; + ~^/добро-пожаловать/?$ /dobro-pozhalovat/; + ~^/фото/?$ /foto/; + ~^/сегодня-ночью-россияне-увидят-первое-суперлуние-года-волчью-луну/?$ /segodnya-nochyu-rossiyane-uvidyat-pervoe/; + ~^/первые-20-градусные-морозы/?$ /pervye-20-gradusnye-morozy/; +} + +server { + listen 80; + listen [::]:80; + server_name pushkinohistory.ru www.pushkinohistory.ru; + include /etc/nginx/templates/letsencrypt.conf; + location / { return 301 https://pushkinohistory.ru$request_uri; } +} + +server { + listen 443 ssl; + listen 443 quic; + listen [::]:443 ssl; + listen [::]:443 quic; + http2 on; + server_name www.pushkinohistory.ru; + + ssl_certificate /etc/letsencrypt/live/pushkinohistory.ru/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/pushkinohistory.ru/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/pushkinohistory.ru/chain.pem; + include /etc/nginx/templates/ssl.conf; + include /etc/nginx/templates/security-headers.conf; + include /etc/nginx/templates/rugov-block.conf; + + return 301 https://pushkinohistory.ru$request_uri; +} + +server { + listen 443 ssl; + listen 443 quic; + listen [::]:443 ssl; + listen [::]:443 quic; + http2 on; + server_name pushkinohistory.ru; + + ssl_certificate /etc/letsencrypt/live/pushkinohistory.ru/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/pushkinohistory.ru/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/pushkinohistory.ru/chain.pem; + include /etc/nginx/templates/ssl.conf; + include /etc/nginx/templates/security-headers.conf; + include /etc/nginx/templates/rugov-block.conf; + include /etc/nginx/templates/letsencrypt.conf; + + access_log /var/log/nginx/access/pushkinohistory.ru.access.log; + error_log /var/log/nginx/errors/pushkinohistory.ru.error.log warn; + + client_max_body_size 4M; + + # 301-редиректы с легаси WP-slugs на новые транслитерированные пути + if ($legacy_redirect != "") { + return 301 $legacy_redirect; + } + + # WP-эндпоинты — больше не существуют, отдаём 410 Gone (помогает поисковикам пометить как удалённые) + location ~* ^/(wp-admin|wp-login\.php|wp-content|wp-includes|xmlrpc\.php|wp-cron\.php|wp-config\.php|readme\.html)$ { + return 410; + } + + # RSS-фид (статичный файл, отдаётся из dist) + location = /feed/ { + proxy_pass http://127.0.0.1:4146; + include /etc/nginx/templates/proxy.conf; + add_header Content-Type "application/rss+xml; charset=utf-8" always; + add_header Cache-Control "public, max-age=600" always; + } + + # Агрегатор новостей: апдейтится по cron, кешируем коротко + location = /api/news.json { + proxy_pass http://127.0.0.1:4146; + include /etc/nginx/templates/proxy.conf; + add_header Cache-Control "public, max-age=120" always; + } + + # Картинки/статические ассеты — кешируем подольше + location ~* ^/(uploads|assets)/ { + proxy_pass http://127.0.0.1:4146; + include /etc/nginx/templates/proxy.conf; + add_header Cache-Control "public, max-age=604800, immutable" always; + proxy_cache_valid 200 7d; + } + + location / { + proxy_pass http://127.0.0.1:4146; + include /etc/nginx/templates/proxy.conf; + proxy_read_timeout 30s; + } +}