176 lines
12 KiB
Markdown
176 lines
12 KiB
Markdown
# anotherreflections.ru — v2 (Astro)
|
||
|
||
## Проект
|
||
|
||
Главный сайт-портал ролевой группы «Иные Отражения». Переделан со старого WordPress 6.9 (тема `darkness-10`, 23 активных плагина) на статический **Astro 5 + Content Collections + markdown**.
|
||
|
||
## Сервер
|
||
|
||
| Параметр | Значение |
|
||
|---|---|
|
||
| Хост | `web.hhivp.com` (45.10.53.206 / 45.10.53.242) |
|
||
| Deploy path | `/opt/docker/sites/anotherreflections-ru-v2/` (git clone) |
|
||
| Git remote | `git.striker.su/striker/anotherreflections-website-v2` (ветка `main`) |
|
||
| Контейнер | `anotherreflections-ru-v2` (nginx:1.29-alpine) |
|
||
| Внутренний порт | 80 |
|
||
| Внешний порт | 127.0.0.1:**4084** (nginx-хост → proxy_pass) |
|
||
| nginx vhost | `/etc/nginx/conf.d/anotherreflections.ru` |
|
||
| OG-image / лого | `public/og-image.png`, `public/logo*.svg` |
|
||
|
||
## Cutover (2026-05-20)
|
||
|
||
Со старого WordPress (контейнер `anotherreflections-ru` на :4080) — на новый Astro-контейнер (:4084). Старый контейнер и БД оставлены для отката на 1-2 недели.
|
||
|
||
**Откат за 1 минуту:**
|
||
```bash
|
||
ssh striker@web.hhivp.com
|
||
sudo sed -i 's|127.0.0.1:4084|127.0.0.1:4080|g' /etc/nginx/conf.d/anotherreflections.ru
|
||
sudo nginx -t && sudo systemctl reload nginx
|
||
```
|
||
|
||
**Бэкап перед cutover** в `/opt/backup/anotherreflections-pre-v2-cutover-20260521-024027/`:
|
||
- `wp-site-tree.tgz` (349 МБ) — весь `/opt/docker/sites/anotherreflections-ru/`
|
||
- `wp-db-anotherreflctions_ru.sql.gz` (5.6 МБ) — mysqldump БД (на `db.hhivp.com` MySQL)
|
||
- `anotherreflections.ru.nginx` — старый vhost (proxy 4080)
|
||
|
||
Также резерв-копия vhost в `/opt/backup/nginx-bak/20260521-024433/anotherreflections.ru.bak.20260520-wp`.
|
||
|
||
## Стек
|
||
|
||
- **Astro 5** (6.3.6) + Content Collections — каждая `.astro` страница рендерится в `dist/.../index.html` при `npm run build` (SSG, без runtime JS-фреймворка).
|
||
- **@astrojs/rss** — RSS-фиды (общий + per-category) под IPB RSS Importer.
|
||
- **@astrojs/sitemap** — `sitemap-index.xml` автоматически.
|
||
- **sanitize-html** — очистка HTML тела поста для `<content:encoded>` в RSS.
|
||
- **sharp** (devDep) — генерация OG-image PNG из SVG.
|
||
|
||
## Структура
|
||
|
||
```
|
||
src/
|
||
├── content/
|
||
│ ├── posts/*.md (50 постов 2009-2015, мигрированы из WP)
|
||
│ ├── pages/*.md (6 страниц: O нас, Наши друзья, страницы про игры)
|
||
│ └── _categories.json (10 категорий-справочник)
|
||
├── components/
|
||
│ ├── BrandMark.astro (SVG-знак в шапке)
|
||
│ ├── SocialLinks.astro (плавающая правая колонка: Telegram, ВК, ↑)
|
||
│ ├── Analytics.astro (Яндекс.Метрика + GA, скрипты с type=text/plain
|
||
│ │ активируются после consent)
|
||
│ └── CookieConsent.astro (152-ФЗ-баннер, ar-consent в localStorage+cookie)
|
||
├── layouts/BaseLayout.astro
|
||
├── pages/
|
||
│ ├── index.astro (hero + лента всех постов)
|
||
│ ├── [slug].astro (один пост или одна страница, slug совпадает с WP)
|
||
│ ├── 404.astro
|
||
│ ├── miry.astro (8 игровых вселенных карточками)
|
||
│ ├── kontakty.astro (email + Telegram + ВК)
|
||
│ ├── privacy.astro (политика + кнопка «Отозвать согласие»)
|
||
│ ├── feed.xml.ts (общий RSS, фильтр по RSS_CUTOFF)
|
||
│ ├── sitemap.txt.ts (plain-text список всех URL)
|
||
│ └── category/
|
||
│ ├── [slug].astro
|
||
│ └── [slug]/feed.xml.ts (per-category RSS)
|
||
├── lib/rss-helpers.ts (рендер md→HTML для <content:encoded>)
|
||
├── styles/global.css (тёмная тема, decorative ::before/::after звёзды)
|
||
└── consts.ts (SITE_TITLE, WORLDS, ANALYTICS, CATEGORY_COLORS, plural())
|
||
|
||
public/
|
||
├── favicon.svg (брендовый знак на тёмном фоне, оптимизирован под 16-32px)
|
||
├── favicon.ico (32×32 PNG-in-ICO, генерируется из favicon.svg)
|
||
├── apple-touch-icon.png (180×180 для iOS home screen)
|
||
├── logo.svg, logo-mark.svg
|
||
├── og-image.png (1200×630 для расшаривания в TG/VK/Twitter)
|
||
├── og-image.svg (исходник)
|
||
├── robots.txt (allow all, Host для Yandex, AI-crawlers)
|
||
├── ai.txt (Train/Cite/Quote: yes)
|
||
├── llms.txt (формат llmstxt.org)
|
||
└── wp-content/uploads/ (4 файла, перенесены со старого WP: header_bg + NY-baner)
|
||
|
||
scripts/
|
||
├── migrate-wp.mjs (одноразовый: _wp-export.json → src/content/*.md)
|
||
├── build-og-image.mjs (sharp → public/og-image.png из SVG)
|
||
└── build-favicon.mjs (sharp → public/favicon.ico + apple-touch-icon.png из favicon.svg; `npm run build:favicon`)
|
||
|
||
Dockerfile (multi-stage: node:22-alpine builds → nginx:1.29-alpine serves)
|
||
nginx.conf (gzip, кэш _astro/ 1y, MIME application/rss+xml для feed)
|
||
docker-compose.yml (контейнер на 127.0.0.1:4084)
|
||
.gitea/workflows/deploy.yml (push в main → SSH-деплой на web)
|
||
```
|
||
|
||
## RSS под IPB Importer
|
||
|
||
`RSS_CUTOFF` в `src/consts.ts` (по умолчанию `2026-05-20`) — фид отдаёт **только посты с pubDate ≥ cutoff**. Архив 2009-2015 на сайте остаётся, в форумы через RSS Importer не вбрасывается. Чтобы добавить пост в фид — публикуем с датой ≥ cutoff и пересобираем.
|
||
|
||
RSS 2.0 strict: `<pubDate>` в RFC-822, `<guid isPermaLink="true">` = URL поста (IPB дедуплицирует), `<content:encoded>` с CDATA + полным HTML тела, `<lastBuildDate>` обновляется при каждом build.
|
||
|
||
URLs:
|
||
- `/feed.xml` — общий
|
||
- `/category/<slug>/feed.xml` — по категории
|
||
|
||
## Аналитика и cookie consent
|
||
|
||
- **Яндекс.Метрика** 13938862 (с webvisor) и **Google gtag** GT-PH39R3X. ID-шки в `src/consts.ts:ANALYTICS`.
|
||
- Оба скрипта в `Analytics.astro` имеют `type="text/plain" data-cookieconsent="statistics"` — браузер их **не выполняет**, пока пользователь не нажмёт «Принять» в баннере.
|
||
- `CookieConsent.astro` активирует скрипты при согласии, сохраняет выбор в `localStorage` + `cookie ar-consent=accept|deny` (12 мес).
|
||
- На `/privacy/` кнопка «Отозвать согласие» — ставит `ar-consent=deny` одним кликом.
|
||
|
||
## Деплой
|
||
|
||
**Через CI/CD (Gitea Actions)** — push в `main`:
|
||
1. Workflow `.gitea/workflows/deploy.yml` стартует.
|
||
2. SSH на `web.hhivp.com` с ключом из секрета `SSH_PRIVATE_KEY` (base64).
|
||
3. `git fetch` + `git reset --hard origin/main` → `docker compose build --pull` → `docker compose up -d` → `docker image prune -af --filter "until=168h"`.
|
||
4. Verify-шаг: `curl -sf -H "Host: anotherreflections.ru" http://127.0.0.1:4084/`.
|
||
|
||
Секреты в Gitea (`/repos/striker/anotherreflections-website-v2/actions/secrets`): `SSH_PRIVATE_KEY`, `SSH_HOST=web.hhivp.com`, `SSH_USER=striker`, `SSH_PORT=22`. Deploy-ключ — `~/.ssh/anotherreflections-v2-deploy{,.pub}` локально, `.pub` в `~striker/.ssh/authorized_keys` на web.
|
||
|
||
**Вручную** (с локалки):
|
||
```bash
|
||
cd /opt/docker/sites/anotherreflections-ru-v2
|
||
git pull
|
||
docker compose build && docker compose up -d
|
||
```
|
||
|
||
## База данных
|
||
|
||
В новой версии БД нет. Старый WP оставлен с БД `anotherreflctions_ru` (`sic`, с опечаткой) на `db.hhivp.com` (45.10.53.205, MySQL), user `u_anotherreflections`, prefix `anm_`. После 1-2 недель наблюдения за новой версией — старый WP-контейнер можно удалить, БД и snapshot тоже.
|
||
|
||
## SEO-title и description — уникальность
|
||
|
||
Шаблон в `BaseLayout.astro:16-17`: `title` рендерится как `${title} — ${SITE_TITLE}`, `description` falls back на `SITE_DESCRIPTION`. Если страница не передаёт свои `title`/`description`, получает дефолт → дубли в выдаче.
|
||
|
||
**Правила (поддерживать при добавлении страниц):**
|
||
- **Posts** (`src/content/posts/*.md`) — обязателен `description:` в frontmatter (Zod schema: `z.string().default('')`, но пустой даёт fallback). В SEO-title `[slug].astro` подставляет год публикации; если есть другой пост с тем же `title` за тот же год — подставляется полная дата (`DD месяц YYYY`).
|
||
- **Pages** (`src/content/pages/*.md`) — обязателен `description:`. `[slug].astro` берёт его напрямую.
|
||
- **Категории** (`category/[slug].astro`) — description вычисляется автоматически из количества публикаций и диапазона лет.
|
||
- **Главная** (`index.astro`) — свой description прописан явно.
|
||
|
||
## SEO/AI файлы
|
||
|
||
- `public/robots.txt` — статика, разрешено всё; явно перечислены GPTBot/ClaudeBot/Google-Extended/CCBot/PerplexityBot/anthropic-ai; ссылки на sitemap-index.xml и sitemap.txt
|
||
- `public/ai.txt` — Train/Cite/Quote: yes (по спецификации spawning.ai)
|
||
- `public/llms.txt` — формат llmstxt.org с описанием проекта, ключевыми страницами, форумами, RSS-фидами, контактами
|
||
- `src/pages/sitemap.txt.ts` — генерирует plain-text список 68 URL при каждом билде
|
||
- `@astrojs/sitemap` — `sitemap-index.xml` + `sitemap-0.xml` XML-формат
|
||
|
||
## Скриншоты-источники
|
||
|
||
В `E:/Projects/` лежат финальные:
|
||
- `anotherreflections-logo.svg` / `.png` — знак + надпись
|
||
- `anotherreflections-logo-mark.svg` / `.png` — только знак
|
||
|
||
## Локальная разработка
|
||
|
||
```bash
|
||
npm install
|
||
npm run dev # → http://localhost:4321
|
||
npm run build # → dist/ (статика)
|
||
npm run preview # просмотр build
|
||
npm run migrate # одноразовая миграция _wp-export.json → md (уже сделана)
|
||
```
|
||
|
||
## История
|
||
|
||
- **2026-05-20**: v1 (WordPress 6.9 + darkness-10 + 23 плагина, контейнер на :4080) переделан на Astro 5. Новый репо `git.striker.su/striker/anotherreflections-website-v2`. Cutover в production через nginx proxy_pass swap. См. также `memory/project_anotherreflections_main_site.md`.
|
||
- **2026-05-07**: 6 IPB-форумов проекта мигрированы со str-u-01 на web.hhivp.com (порты 4091-4096). См. `memory/project_anotherreflections_forums.md`.
|