# pushkinohistory.ru — Astro v2

Сайт «История города Пушкино». Редизайн с WordPress (v1) на статический Astro 5 + Content Collections + markdown.

**Прод:** https://pushkinohistory.ru
**Репо:** `git.striker.su/striker/pushkinohistory-ru-v2`
**Хост:** `web.hhivp.com` (45.10.53.206 / 45.10.53.242)
**Контейнер:** `pushkinohistory-ru-v2` на `127.0.0.1:4146` (nginx:alpine + Astro SSG)
**Cutover:** 2026-05-21 со старого WP-контейнера `pushkinohistory-ru:4143`

## Стек

- **Astro 5** + Content Collections + markdown
- **nginx:1.29-alpine** в runtime-контейнере (статика + bind-mount для агрегатора новостей)
- **sanitize-html** — очистка тела поста для RSS `<content:encoded>` (с CDATA)
- **fast-xml-parser** — изолированно в `scripts/` (только для cron-агрегатора)
- **sharp** (devDep, опц.) — генерация OG-image PNG из SVG
- **PT Serif** (заголовки/тело статьи) + **IBM Plex Sans** (UI)
- **@astrojs/sitemap** — `sitemap-index.xml` автоматически

## Структура

```
src/
├── content/
│   ├── posts/*.md          (7 постов, мигрированы из WP DB ph_posts)
│   ├── pages/*.md          (4 страницы: Главная-приветствие, История, Фото, Форум)
│   └── _categories.json    (через categorySlugs в frontmatter)
├── components/
│   ├── Header.astro        (фото-шапка + аэрофото Пушкино + sepia overlay)
│   ├── Sidebar.astro       (Транспорт, Рубрики с подсчётом, Партнёры, Объявления)
│   ├── Footer.astro
│   ├── PostCard.astro      (featured / has-thumb / no-thumb варианты)
│   ├── CookieConsent.astro (152-ФЗ баннер, ph-consent в localStorage+cookie)
│   └── Analytics.astro     (Яндекс.Метрика + GA, gating через type=text/plain)
├── layouts/BaseLayout.astro
├── pages/
│   ├── index.astro         (Главная история + Ещё из истории + Хроника)
│   ├── [slug].astro        (один пост или одна страница)
│   ├── cat/[slug].astro    (рубрика)
│   ├── cat/[slug]/feed.xml.ts  (per-category RSS)
│   ├── news.astro          (агрегатор внешних RSS, фетчит /api/news.json)
│   ├── 404.astro
│   ├── privacy.astro       (политика + кнопка «Отозвать согласие»)
│   ├── feed.xml.ts         (общий RSS, с RSS_CUTOFF)
│   └── sitemap.txt.ts      (plain-text карта)
├── lib/
│   ├── extract.ts          (firstImage, plainText, formatDateRu)
│   └── rss-helpers.ts      (buildRss, sanitizeForRss, cdata, plainTextExcerpt)
├── data/                   (внешний контент-конфиг, JSON)
│   ├── transport.json      (ссылки на Yandex.Schedules / mostransport)
│   ├── partners.json
│   ├── ads.json
│   └── feeds.json          (внешние RSS-источники для cron-агрегатора)
├── styles/global.css
└── consts.ts               (SITE_TITLE, MAIN_NAV, RSS_CUTOFF, ANALYTICS, plural)

public/
├── uploads/                (6 картинок, перенесены из WP /wp-content/uploads/)
├── favicon.svg
├── robots.txt
├── ai.txt
└── llms.txt

nginx/pushkinohistory.ru.conf   (vhost для хост-nginx, симлинкуется в /etc/nginx/conf.d/)

scripts/
├── convert_posts.py        (WP DB → src/content/posts.json + pages.json, fix WP-resized URL)
├── convert_to_markdown.py  (posts.json → src/content/posts/*.md с frontmatter)
├── pull-external-rss.mjs   (cron на хосте: feeds.json → data/news.json)
├── install-cron.sh         (установка cron на web.hhivp.com)
└── package.json            (изолированный fast-xml-parser)

Dockerfile          (multi-stage: node:22-alpine build → nginx:1.29-alpine serve)
nginx.conf          (внутри контейнера: gzip, кэш _astro/, MIME для RSS, /api/news.json из bind-mount)
docker-compose.yml  (контейнер на 127.0.0.1:4146, bind-mount data/ → /var/lib/pushkino/data:ro)
.gitea/workflows/deploy.yml  (push в main → SSH-деплой на web.hhivp.com)
```

## Контент

- **7 постов** + **4 страницы** скрейпом из WP DB `pushkinohistory_ru` на `db.hhivp.com`
- **URL-encoded кириллические slug'и WP** → транслитерированы (`/segodnya-nochyu-rossiyane-uvidyat-pervoe/`); старые URL → 301 через nginx map по `$uri`
- **WP-resized URL** (`-1024x768.png`) → оригинал автоматически в `convert_posts.py:RESIZED_RE`
- **Frontmatter-флаги для иерархии главной:**
  - `featured: true` + `featuredImage` — пин на верх как «Главная история» (Воронино, Старое Село)
  - `hideFromList: true` — скрыть с главной (3 «технические работы»), доступ только через рубрику
- **Категория `main`** — псевдо-флаг «попадает на главную»; не показывается в плашках и в сайдбаре

## RSS

### Свой `/feed.xml` (для IPB Importer)

- Полный HTML тела поста в `<content:encoded>` с CDATA (sanitize-html → buildRss)
- Стабильные `<guid isPermaLink="true">` = URL поста (IPB дедуплицирует)
- `<dc:creator>`, `<category>`, корректный `<pubDate>` в RFC-822
- Контент-Type `application/rss+xml; charset=utf-8`
- **`RSS_CUTOFF`** в `src/consts.ts` (default `2010-01-01`) — отрезает старый архив. Чтобы IPB не флудил при следующем рестарте — поменять на новую дату и пересобрать
- Per-category RSS: `/cat/<slug>/feed.xml`

### Агрегатор внешних RSS

- `src/data/feeds.json` — список источников (`enabled: false` по умолчанию)
- `scripts/pull-external-rss.mjs` — cron на web.hhivp.com (каждый час в `:12`)
- Пишет `/opt/docker/sites/pushkinohistory-ru-v2/data/news.json` (bind-mount в контейнер как `/var/lib/pushkino/data:ro`)
- Фронт `/news/` фетчит client-side через `/api/news.json` (отдаёт nginx внутри контейнера alias на bind-mount)
- Логи `/var/log/pushkino-rss-aggregator.log` + logrotate weekly × 4

Чтобы добавить источник:
1. В `src/data/feeds.json` добавить `{name, url, enabled: true, max}`
2. Push → CI → деплой
3. На следующем cron-tick'е появится в `/news/`

## Деплой

### Автоматический (Gitea Actions)

Push в `main` → `.gitea/workflows/deploy.yml`:
1. SSH на `web.hhivp.com` с ключом из секрета `SSH_DEPLOY_KEY`
2. `git fetch + reset --hard origin/main`
3. `docker compose build && up -d`
4. `curl -fsS http://127.0.0.1:4146/` — health check
5. `docker image prune --filter "until=168h"`

Секреты в Gitea (`/repos/striker/pushkinohistory-ru-v2/actions/secrets`):
- `SSH_DEPLOY_KEY` — приватный ключ `~/.ssh/pushkino-v2-deploy` (генерён локально, pubkey в `striker@web:~/.ssh/authorized_keys`)
- `SSH_KNOWN_HOSTS` — fingerprint `web.hhivp.com ssh-ed25519 AAAAC3...`

### Вручную

```bash
cd E:\Projects\pushkinohistory-ru-v2
git add . && git commit -m "..." && git push
# или с локалки на сервер:
ssh striker@web.hhivp.com 'cd /opt/docker/sites/pushkinohistory-ru-v2 && git pull && docker compose up -d --build'
```

### nginx vhost

Симлинк: `/etc/nginx/conf.d/pushkinohistory.ru` → `nginx/pushkinohistory.ru.conf` в репо. После правки nginx-конфига: push → CI заберёт → `sudo nginx -t && sudo systemctl reload nginx`.

301-редиректы со старых WP-URL — через `map $uri $legacy_redirect` (см. файл). Если нужно добавить новый редирект — отредактировать map и pushнуть.

## Откат на WP v1

Старый WP-контейнер `pushkinohistory-ru:4143` + БД `pushkinohistory_ru` на `db.hhivp.com` сохранены. Откат за ~1 минуту:

```bash
ssh striker@web.hhivp.com
echo "Gh_lpx2017!" | sudo -S ln -sfn /opt/docker/sites/pushkinohistory-ru/nginx/pushkinohistory.ru.conf /etc/nginx/conf.d/pushkinohistory.ru
sudo nginx -t && sudo systemctl reload nginx
```

После 1-2 недель стабильной работы v2 — старый WP можно удалить (контейнер + БД + репо).

## Форум

`forum.pushkinohistory.ru` — IPB 4.x в отдельном контейнере `forum-pushkinohistory-ru:4144`. **v2 редизайном не затронут.**

## Аналитика

`ANALYTICS` в `src/consts.ts` (`yandexMetrika`, `googleGtag`) пустые — впишите ID после регистрации счётчиков. Скрипты в `Analytics.astro` имеют `type="text/plain" data-cookieconsent="statistics"` и активируются только после согласия в баннере `CookieConsent.astro`.

Согласие хранится в `localStorage` + cookie `ph-consent` (12 мес). На `/privacy/` есть кнопка «Отозвать согласие».

## Локальная разработка

```bash
npm install
npm run dev      # → http://localhost:4321
npm run build    # → dist/ (статика)
npm run preview
```

Если 4321 занят — Astro сам найдёт следующий свободный (4322 и т.д.).

## История

- **2026-05-08:** v1 (WordPress 6.x) контейнеризован, миграция со str-u-01 на web.hhivp.com
- **2026-05-14:** фикс trust-proxy.conf для Docker bridges (Better WP Security)
- **2026-05-21:** v2 редизайн — Vite+React → Astro 5 (стек как у `anotherreflections-website-v2`). Cutover, бэкап старого WP в репо `pushkinohistory-ru` + БД на `db.hhivp.com` (~2 недели на наблюдение).
