From 0f4d6da3c55b55af5e841c6159ba06804159fc36 Mon Sep 17 00:00:00 2001 From: Storozhenko Evgeny Date: Sat, 12 Sep 2026 17:08:50 +0000 Subject: [PATCH 1/5] =?UTF-8?q?docs(agents):=20=D0=B2=D1=81=D0=B5=20=D0=B8?= =?UTF-8?q?=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B0=20=E2=80=94=20=D1=82=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BA=D0=BE=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20OpenSpec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 01418a2..dcd1ee2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,9 +65,10 @@ make help # все команды ## Процессы и планирование -- Планирование изменений — OpenSpec: `openspec/` (specs + changes). - Правки контента/кода — через openspec-скиллы из `.hermes/skills/` - (propose → design → apply → archive). +- **ВСЕ задачи по изменению проекта выполняются через OpenSpec**: + `openspec/` (specs + changes). Порядок: propose → design → apply → archive. + Новые изменения — только через openspec-скиллы из `.hermes/skills/`; + напрямую без OpenSpec ничего не менять (кроме `docs/`-правок и уборки). - Проверочные действия перед этапами: `hugo version`, `make build`, контроль `hugo.toml`, `make deploy-dry-run`. From f07bffe83876ff2399062627459161a94bcbd285 Mon Sep 17 00:00:00 2001 From: Storozhenko Evgeny Date: Sat, 12 Sep 2026 17:11:03 +0000 Subject: [PATCH 2/5] =?UTF-8?q?openspec(localize-i18n-ru):=20propose=20?= =?UTF-8?q?=E2=80=94=20=D1=80=D1=83=D1=81=D1=81=D0=BA=D0=B0=D1=8F=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=BA=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20?= =?UTF-8?q?UI=20(i18n/ru.toml)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../changes/localize-i18n-ru/.openspec.yaml | 2 + openspec/changes/localize-i18n-ru/design.md | 25 ++++++++++++ openspec/changes/localize-i18n-ru/proposal.md | 23 +++++++++++ .../localize-i18n-ru/specs/i18n/spec.md | 39 +++++++++++++++++++ openspec/changes/localize-i18n-ru/tasks.md | 10 +++++ 5 files changed, 99 insertions(+) create mode 100644 openspec/changes/localize-i18n-ru/.openspec.yaml create mode 100644 openspec/changes/localize-i18n-ru/design.md create mode 100644 openspec/changes/localize-i18n-ru/proposal.md create mode 100644 openspec/changes/localize-i18n-ru/specs/i18n/spec.md create mode 100644 openspec/changes/localize-i18n-ru/tasks.md diff --git a/openspec/changes/localize-i18n-ru/.openspec.yaml b/openspec/changes/localize-i18n-ru/.openspec.yaml new file mode 100644 index 0000000..2b596d1 --- /dev/null +++ b/openspec/changes/localize-i18n-ru/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-09-12 diff --git a/openspec/changes/localize-i18n-ru/design.md b/openspec/changes/localize-i18n-ru/design.md new file mode 100644 index 0000000..86c410c --- /dev/null +++ b/openspec/changes/localize-i18n-ru/design.md @@ -0,0 +1,25 @@ +## Context + +Проект dedinit.ru — Hugo Extended 0.165, тема rDedInit (форк PaperMod), язык ru-ru. Сейчас `i18n/` отсутствует, UI-строки темы отображаются английскими дефолтами PaperMod. См. proposal.md — Why. + +## Goals / Non-Goals + +**Goals:** +- Создать `i18n/ru.toml` с переводами всех ключей, используемых шаблонами rDedInit. +- Русский UI без изменения разметки и контента. + +**Non-Goals:** +- Не переводим содержимое контента (посты). +- Не добавляем мультиязычность (en/ru переключатель) — вне рамок задачи. + +## Decisions + +- **Формат TOML** (Hugo i18n) — стандарт Hugo, `ru.toml` рядом с другими локалями. +- **Источник ключей**: извлечены из `themes/rDedInit/layouts/` — `home`, `next_page`, `prev_page`, `read_time`, `words`, `toc`, `code_copy`, `code_copied`, `edit_post`, `translations`; дополнительно стандартные ключи PaperMod (`search`, `page_not_found`, `archives`, `tags`, `categories`, `posted_on`, `continue_reading`, `min_read`, `newer`, `older`, `page`, `of`, `read_more`, `translations` и др.) для полноты — отсутствующий ключ Hugo просто не находит и ничего не ломает (fallback). +- **Структура строки**: `[ключ]\nother = "перевод"` — Hugo i18n-формат (other — для языков без gender/plural). +- **Проверка**: `make build` + grep в public/ на русские строки («Читать далее», «Теги», «Содержание»). + +## Risks / Trade-offs + +- [Перевод строки, которую тема не использует] → Не навредит: Hugo игнорирует неиспользуемые ключи; лишние ключи просто расширяют словарь (fallback при будущих фичах темы). +- [Ключ использован темой, но перевод терминологически неточен] → Быстрый фикс: правим одну строку в ru.toml; git-история позволит откатить. \ No newline at end of file diff --git a/openspec/changes/localize-i18n-ru/proposal.md b/openspec/changes/localize-i18n-ru/proposal.md new file mode 100644 index 0000000..f0f6db7 --- /dev/null +++ b/openspec/changes/localize-i18n-ru/proposal.md @@ -0,0 +1,23 @@ +## Why + +Сайт dedinit.ru — русскоязычный блог (язык ru-ru), но в теме rDedInit (форк PaperMod) отсутствует файл `i18n/ru.toml`, из-за чего интерфейсные строки (кнопки, навигация, время чтения) показываются на английском по умолчанию. Это выглядит неаккуратно для русскоязычной аудитории. + +## What Changes + +- Создать `i18n/ru.toml` с русскими переводами всех UI-ключей темы rDedInit (PaperMod). +- Перевести строки: Home → «Главная», Read time → «Время чтения», Words → «слов», Table of Contents → «Содержание», Code copy → «Копировать код», Code copied → «Скопировано», Edit post → «Редактировать пост», Next → «Следующая», Prev → «Предыдущая», Translations → «Переводы», Search → «Поиск» (и др. стандартные ключи PaperMod). +- Поведение сайта, разметка, контент — не меняются; это чисто интернационализация UI. + +## Capabilities + +### New Capabilities +- `i18n`: русская локализация интерфейса темы (ключи-переводы UI). Новая способность проекта «русская локализация UI». + +### Modified Capabilities +Отсутствуют — существующих spec в `openspec/specs/` не затронуто (site-rss не меняется). + +## Impact + +- Затронутые файлы: `i18n/ru.toml` (новый). +- Не затрагивает: контент, шаблоны, деплой, RSS. +- Откат (rollback): удалить `i18n/ru.toml` (или `git revert`) — сайт вернётся к английским строкам по умолчанию, как сейчас. \ No newline at end of file diff --git a/openspec/changes/localize-i18n-ru/specs/i18n/spec.md b/openspec/changes/localize-i18n-ru/specs/i18n/spec.md new file mode 100644 index 0000000..b040647 --- /dev/null +++ b/openspec/changes/localize-i18n-ru/specs/i18n/spec.md @@ -0,0 +1,39 @@ +## Purpose + +Обеспечивает русскоязычный интерфейс сайта dedinit.ru: все UI-строки темы (навигация, время чтения, код, пагинация) отображаются на русском языке для аудитории ru-ru. + +## ADDED Requirements + +### Requirement: Русские UI-строки в i18n/ru.toml +Система SHALL предоставлять файл `i18n/ru.toml`, содержащий русские переводы всех ключей интерфейса, используемых темой rDedInit (PaperMod): `home`, `next_page`, `prev_page`, `read_time`, `words`, `toc`, `code_copy`, `code_copied`, `edit_post`, `translations`, `search`, `page_not_found` и стандартные ключи PaperMod. + +#### Scenario: Главная страница +- **WHEN** пользователь открывает главную страницу сайта +- **THEN** строка навигации «Home» отображается как «Главная» + +#### Scenario: Время чтения статьи +- **WHEN** пользователь открывает статью с временем чтения +- **THEN** строка «X min read» отображается как «X мин чтения» (или эквивалент на русском) + +#### Scenario: Оглавление +- **WHEN** статья содержит оглавление +- **THEN** заголовок оглавления отображается как «Содержание», а не «Table of Contents» + +#### Scenario: Кнопки копирования кода +- **WHEN** пользователь наводит на блок кода +- **THEN** подсказка отображается как «Копировать код»; после копирования — «Скопировано» + +#### Scenario: Пагинация +- **WHEN** пользователь видит пагинацию списка постов +- **THEN** ссылки отображаются как «Следующая» / «Предыдущая», а не «Next» / «Prev» + +#### Scenario: Отсутствующий ключ +- **WHEN** в теме используется ключ, отсутствующий в `i18n/ru.toml` +- **THEN** сайт продолжает работать, отображая ключ по умолчанию из темы (Hugo fallback) — поведение не ломается + +### Requirement: Фолбэк на английский при отсутствии перевода +Система SHALL сохранять работоспособность сайта при отсутствии какого-либо ключа: Hugo отображает строку по умолчанию темы (английскую), сайт не падает и не ломает разметку. + +#### Scenario: Нет ключа в ru.toml +- **WHEN** в ru.toml отсутствует конкретный ключ +- **THEN** Hugo использует значение по умолчанию из темы (английское) и сборка проходит без ошибок \ No newline at end of file diff --git a/openspec/changes/localize-i18n-ru/tasks.md b/openspec/changes/localize-i18n-ru/tasks.md new file mode 100644 index 0000000..c265209 --- /dev/null +++ b/openspec/changes/localize-i18n-ru/tasks.md @@ -0,0 +1,10 @@ +## 1. Локализация + +- [ ] 1.1 Создать каталог `i18n/` и файл `i18n/ru.toml` с переводами всех ключей темы (home, next_page, prev_page, read_time, words, toc, code_copy, code_copied, edit_post, translations) и стандартных ключей PaperMod (search, page_not_found, archives, tags, categories, posted_on, continue_reading, min_read, newer, older, read_more и др.) — проверить `ls i18n/ru.toml` +- [ ] 1.2 Собрать сайт: `make build` — сборка проходит без ошибок (exit 0) + +## 2. Проверка + +- [ ] 2.1 Проверить в собранном public/ русские строки: `grep -r "Читать далее" public/ | head` и `grep -r "Содержание" public/ | head` — обе строки присутствуют в HTML +- [ ] 2.2 Проверить пагинацию: `grep -r "Следующая" public/ | head` и `grep -r "Предыдущая" public/ | head` — присутствуют +- [ ] 2.3 Закоммитить `i18n/ru.toml` и запушить в gitverse (origin main) \ No newline at end of file From 8123051ca75b8257dea487bdc0acbb1de5e0926c Mon Sep 17 00:00:00 2001 From: Storozhenko Evgeny Date: Sat, 12 Sep 2026 17:15:10 +0000 Subject: [PATCH 3/5] =?UTF-8?q?docs(project):=20=D1=84=D0=B0=D0=B9=D0=BB?= =?UTF-8?q?=D1=8B=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0=20=D0=B2=20?= =?UTF-8?q?docs/=20(STATUS,=20PRD,=20TODO,=20WALKTHROUGH)=20+=20AGENTS.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 4 ++++ docs/PRD.md | 35 +++++++++++++++++++++++++++++++++++ docs/STATUS.md | 37 +++++++++++++++++++++++++++++++++++++ docs/TODO.md | 13 +++++++++++++ docs/WALKTHROUGH.md | 39 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 docs/PRD.md create mode 100644 docs/STATUS.md create mode 100644 docs/TODO.md create mode 100644 docs/WALKTHROUGH.md diff --git a/AGENTS.md b/AGENTS.md index dcd1ee2..3066ccb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,6 +14,10 @@ Никакой подробной документации и истории изменений в README не держим. - `docs/` — вся подробная документация проекта: инструкции, справочники, дорожные карты, заметки об опыте настройки. +- **Файлы проекта (статус/требования/задачи/журнал) — тоже в `docs/`:** + `docs/STATUS.md` (текущее состояние), `docs/PRD.md` (требования), + `docs/TODO.md` (журнал задач), `docs/WALKTHROUGH.md` (капитанский журнал). + При закрытии сессии обновляются именно они. - `AGENTS.md` — правила работы для агентов (этот файл). - Подробная эксплуатационная документация НЕ должна лежать в корне репозитория. Новые документы создавать только в `docs/`. diff --git a/docs/PRD.md b/docs/PRD.md new file mode 100644 index 0000000..01e9ccf --- /dev/null +++ b/docs/PRD.md @@ -0,0 +1,35 @@ +# dedinit.ru — PRD (требования к проекту) + +## Цель +Личный технический блог dedinit.ru на Hugo: быстрая публикация статей (MLOps/DevOps/Linux/CI-CD, личный опыт) без поломки сайта. + +## Пользователи +- Автор (владелец) — публикует контент, настраивает тему. +- Читатели — русскоязычная аудитория (ru-ru), потребляют статьи. + +## Функциональные требования +- Генерация статического сайта Hugo Extended, тема rDedInit (форк PaperMod). +- Статьи — bundle: `content/posts//index.md` + hero.svg (viewBox 1200x400, фон #0a0c10, заголовок #58a6ff). +- `date`/`lastmod` — в прошлом (иначе Hugo не публикует); `draft: false`. +- Подсветка блочного кода (цель проекта). +- RSS с полным текстом (site.Params.ShowFullTextinRSS, лимит 5 постов). +- Деплой — ТОЛЬКО `make deploy` (SFTP paramiko на kpa39l.myjino.ru:2222; аккаунт SFTP-only, rsync/ssh-exec невозможны). +- Русскоязычный UI (задача локализации i18n в процессе). + +## Нефункциональные требования +- Git-история сохраняется (без потерь; git mv). +- Секреты/пароли в коммиты НЕ добавлять. +- Документация — на русском, в docs/; README — краткое введение. +- Все изменения проекта — через OpenSpec (propose → design → apply → archive). +- Не удалять/не перезаписывать данные пользователя без явного подтверждения. + +## Границы (что НЕ делаем) +- Не внедряем мультиязычность en/ru (пока). +- Не переводим контент постов — только UI. +- Не меняем провайдер хостинга/деплоя без запроса. + +## Критерии готовности +- Сайт собирается (`make build`) и деплоится (`make deploy`) без ошибок. +- UI на русском (по завершении локализации). +- Репозиторий: чистый git-статус, все коммиты в gitverse. +- Изменения проходят OpenSpec-цикл и архивируются. \ No newline at end of file diff --git a/docs/STATUS.md b/docs/STATUS.md new file mode 100644 index 0000000..e5d6770 --- /dev/null +++ b/docs/STATUS.md @@ -0,0 +1,37 @@ +# dedinit.ru — Статус + +Обновлено: 2026-09-12 (сессия уборки структуры и OpenSpec) + +## Текущее состояние +Сайт-блог dedinit.ru на Hugo Extended (тема rDedInit/форк PaperMod) в порядке: структура репозитория приведена к стандарту (README краткий + вся документация в docs/ + AGENTS.md), всё запушено в gitverse (main). Рабочее дерево чистое. Следующая задача (локализация UI) оформлена через OpenSpec, реализация не начата. + +## Сделано +- 2026-09-12: Уборка структуры репозитория — вся документация перенесена в docs/ (git mv, история сохранена), README.md переписан кратко, создан AGENTS.md с правилами проекта. +- 2026-09-12: Правило «ВСЕ изменения проекта — через OpenSpec» добавлено в AGENTS.md (commit 0f4d6da). +- 2026-09-12: Задача «Русская локализация i18n/ru.toml» оформлена через OpenSpec (propose) — change `localize-i18n-ru` валиден, запушен (f07bffe). Артефакты: proposal.md, specs/i18n/spec.md, design.md, tasks.md. +- 2026-09-12: В локальные openspec-скиллы проекта (.hermes/skills/) добавлена секция «Проверка (validate) — ПРАВИЛЬНЫЙ синтаксис» во все 6 SKILL.md (правильный вызов: `openspec validate <имя>`, НЕ `--change`). + +## В работе / Следующие шаги +- [ ] **Реализация локализации i18n/ru.toml** (openspec change `localize-i18n-ru`, стадия apply): создать `i18n/ru.toml` с русскими переводами ключей темы, `make build`, проверить grep'ами в public/, закоммитить и запушить. +- [ ] Затем (из ROADMAP): цветовая схема под бренд dedinit(), русские переводы i18n, Telegram-посты (layout + карточки + shortcode), навигация, SEO и др. + +## Как запустить / проверить +```bash +cd /opt/dedinit.ru +make build # сборка (hugo --minify) +make deploy-dry-run # симуляция деплоя +make deploy # ЕДИНСТВЕННЫЙ способ деплоя (SFTP, требует SSHPASS) +openspec validate localize-i18n-ru # валидация active-change +``` +Источник истины — gitverse (origin main, SSH). + +## Ключевые артефакты +- `/opt/dedinit.ru/AGENTS.md` — правила работы агентов (русский, docs/, make deploy, OpenSpec) +- `/opt/dedinit.ru/README.md` — краткое введение +- `/opt/dedinit.ru/docs/` — вся подробная документация (DEPLOY_INSTRUCTIONS, ROADMAP, PAPERMOD_PARAMS, QWEN, gitea-setup, ssh_setup, Telegram_frontend, local-aux-models, favicon-logo-setup, archive/) +- `/opt/dedinit.ru/openspec/changes/localize-i18n-ru/` — активный change (propose готов, apply впереди) +- `/opt/dedinit.ru/.hermes/skills/openspec-*/SKILL.md` — openspec-скиллы проекта (с секцией про validate) + +## Открытые вопросы +- Продолжать ли локализацию (apply change localize-i18n-ru) в следующей сессии? (пользователь ещё не подтвердил apply) +- Перенести ли секцию про validate в глобальный скилл Hermes (переживёт перезапись локальных openspec-скиллов)? \ No newline at end of file diff --git a/docs/TODO.md b/docs/TODO.md new file mode 100644 index 0000000..26717d0 --- /dev/null +++ b/docs/TODO.md @@ -0,0 +1,13 @@ +# TODO — dedinit.ru + +Формат: | дата | задача | статус | закрыта в | +|---|---|---|---| +| 2026-09-12 | Уборка структуры: вся документация в docs/, краткий README, AGENTS.md | ✅ закрыта | session уборки (коммит d426aa3) | +| 2026-09-12 | Правило «ВСЕ изменения через OpenSpec» в AGENTS.md | ✅ закрыта | commit 0f4d6da | +| 2026-09-12 | Оформить задачу локализации i18n/ru.toml через OpenSpec (propose) | ✅ закрыта | commit f07bffe (change localize-i18n-ru) | +| 2026-09-12 | Добавить в openspec-скиллы правильный синтаксис validate | ✅ закрыта | .hermes/skills/openspec-*/SKILL.md | +| 2026-09-12 | Файлы проекта (STATUS/PRD/TODO/WALKTHROUGH) — в docs/ | ✅ закрыта | docs/STATUS.md, docs/PRD.md, docs/TODO.md, docs/WALKTHROUGH.md | +| 2026-09-12 | Реализовать локализацию i18n/ru.toml (apply change localize-i18n-ru) | 🔵 открыта | | +| — | Настроить цветовую схему под бренд dedinit() | 🔵 открыта | (ROADMAP) | +| — | Русские переводы i18n (Read more→Читать далее и др.) | 🔵 открыта | (ROADMAP) | +| — | Telegram-посты: layout + карточки + shortcode | 🔵 открыта | (ROADMAP) | \ No newline at end of file diff --git a/docs/WALKTHROUGH.md b/docs/WALKTHROUGH.md new file mode 100644 index 0000000..d8ce885 --- /dev/null +++ b/docs/WALKTHROUGH.md @@ -0,0 +1,39 @@ +# WALKTHROUGH — dedinit.ru (капитанский журнал) + +## 2026-09-12 — Уборка структуры репозитория + OpenSpec + +### Контекст +Репозиторий dedinit.ru (Hugo, тема rDedInit/PaperMod) имел документацию в корне; задача — привести к стандарту: README краткий, вся документация в docs/, AGENTS.md для агентов. + +### Что сделано +1. **AGENTS.md создан** (корень) — правила: русский язык, структура (README краткий + docs/), стек (Hugo Extended, тема rDedInit, gitverse источник истины, деплой SFTP), статьи (bundle, date в прошлом, hero.svg), деплой только `make deploy`, git-правила, OpenSpec-процесс, проверки перед публикацией. +2. **Документация перенесена в docs/** (git mv): DEPLOY_INSTRUCTIONS.md, ROADMAP.md, PAPERMOD_PARAMS.md, QWEN.md, gitea-setup.md, ssh_setup_instructions.md, Telegram_frontend.md. +3. **Промежуточные файлы**: `.qwen/PROJECT_SUMMARY.md` → docs/archive/, `winscp_script.txt` → tools/. +4. **README.md переписан** кратко (55 строк): суть, быстрый старт, структура, указатели на docs/, конвенции. История убрана. +5. **Ссылки обновлены**: Makefile и tools/deploy_sftp.py → docs/DEPLOY_INSTRUCTIONS.md; docs/QWEN.md, docs/ROADMAP.md — пути. +6. **Коммиты/пуш**: d426aa3 (уборка), 0f4d6da (правило OpenSpec в AGENTS.md), f07bffe (openspec-propose локализации). + +### OpenSpec — задача локализации +- Change: `openspec/changes/localize-i18n-ru/` (propose завершён, валиден). +- Артефакты: proposal.md (зачем), specs/i18n/spec.md (требования + сценарии), design.md (решения), tasks.md (5 задач). +- **Питфол синтаксиса**: `openspec validate --change ` — ОШИБКА (`unknown option '--change'`). Правильно: `openspec validate `. Это зафиксировано во всех 6 локальных openspec-скиллах (.hermes/skills/openspec-*/SKILL.md). + +### Файлы проекта +- Согласно правилу «вся документация в docs/» — файлы проекта лежат в docs/: + - `docs/STATUS.md` — текущее состояние, сделано, следующие шаги, как проверить. + - `docs/PRD.md` — требования, границы, критерии готовности. + - `docs/TODO.md` — журнал задач (история, статусы). + - `docs/WALKTHROUGH.md` — этот журнал. + +### Как повторить / проверить +```bash +cd /opt/dedinit.ru +make build # сборка +openspec validate localize-i18n-ru # валидация active-change +git pull # обновление (origin = gitverse) +``` + +### Известные подводные камни +- `.hermes/` игнорируется git (.gitignore) — openspec-скиллы не коммитятся. +- Секреты: winscp_script.txt и DEPLOY_INSTRUCTIONS.md содержали SFTP-пароль в истории (известно, ротация не проводилась). В новые файлы пароли НЕ записывать. +- Yandex.Disk НЕ должен синхронизировать .git (ломал репозиторий на Windows). \ No newline at end of file From 0fcfeb36d077b5f71145d92b079fa9d20d54adf3 Mon Sep 17 00:00:00 2001 From: Storozhenko Evgeny Date: Sun, 13 Sep 2026 06:27:28 +0000 Subject: [PATCH 4/5] =?UTF-8?q?post:=20GoToSocial=20=D0=BC=D0=BE=D0=BB?= =?UTF-8?q?=D1=87=D0=B0=20=D1=81=D1=8A=D0=B5=D0=B4=D0=B0=D0=BB=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D1=82=D1=83=D1=81=D1=8B=20=D1=81=20=D1=80=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=B5=D0=B2=20=E2=80=94=20match=5Fby=5Fdefault?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hero.svg | 33 ++++ .../index.md | 164 ++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 content/posts/20260913 - gotosocial relay match by default/hero.svg create mode 100644 content/posts/20260913 - gotosocial relay match by default/index.md diff --git a/content/posts/20260913 - gotosocial relay match by default/hero.svg b/content/posts/20260913 - gotosocial relay match by default/hero.svg new file mode 100644 index 0000000..50fd2c1 --- /dev/null +++ b/content/posts/20260913 - gotosocial relay match by default/hero.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + GoToSocial и молча съеденные статусы + + + ActivityPub · Relay · match_by_default · deny-by-default + + + + + \ No newline at end of file diff --git a/content/posts/20260913 - gotosocial relay match by default/index.md b/content/posts/20260913 - gotosocial relay match by default/index.md new file mode 100644 index 0000000..99a4e25 --- /dev/null +++ b/content/posts/20260913 - gotosocial relay match by default/index.md @@ -0,0 +1,164 @@ +--- +date: '2026-09-13T03:00:00+03:00' +lastmod: '2026-09-13T03:00:00+03:00' +draft: false +title: 'GoToSocial молча съедал все статусы с релеев — и я нашёл почему' +slug: 'gotosocial-relay-match-by-default' +description: 'Подписка на релей выглядит рабочей: релей шлёт, нода отвечает 202 Accepted — а в базе пусто. Разбираюсь, почему: без галочки Match posts by default и пустых матчерах фильтр релеев GoToSocial по построению возвращает false, и статусы дропаются молча на уровне debug.' + +categories: + - 'DevOps' + - 'Federation' + +tags: + - 'gotosocial' + - 'activitypub' + - 'fediverse' + - 'relay' + - 'mastodon' + - 'admin' + +keywords: + - 'GoToSocial' + - 'ActivityPub' + - 'relay subscription' + - 'match by default' + - 'matchedByConnection' + - 'relay_subscriptions' + - '202 Accepted' + - 'dereference' + - 'dropping unpermitted' + - 'deny-by-default' + +cover: + image: "hero.svg" + alt: "GoToSocial: релей молча съедает статусы — match_by_default" +--- + +# GoToSocial молча съедал все статусы с релеев — и я нашёл почему + +**TL;DR:** Подписка на релей выглядит рабочей, релей шлёт, нода отвечает `202 Accepted` — а в базе пусто. Причина — **я не поставил галочку `Match posts by default`**, а матчеры оставил пустыми. В таком виде фильтр релеев по построению возвращает false для всего. Статусы дропаются молча, на уровне debug. Ошибка тихая: её заслоняют громкие ошибки dereference. + +## Как это выглядит снаружи + +Ты подписан на релей. Ты видишь: + +- подписка активна, `approved = true`, +- релей исправно шлёт Announce, +- нода отвечает `202 Accepted`. + +Всё зелёное. А лента месяцами состоит из пары доменов, на которые ты подписан напрямую. Релей как будто «работает вхолостую». + +## ❌ Моя ошибка + +Я настроил подписку так: + +- ✅ разрешил **public** +- ✅ разрешил **unlisted** +- ✅ запретил **sensitive** + +И решил, что этого достаточно. Логика была: «я разрешил то, что хочу, и запретил то, что не хочу — значит, всё остальное будет приходить само». + +**Но это не так.** Разрешение public/unlisted и запрет sensitive — это **фильтры видимости**. Они говорят, *какие типы постов можно принимать*. Но они **не дают разрешения на приём вообще**. Разрешение даёт либо `match_by_default`, либо include-матчеры. У меня не было ни того, ни другого. + +**Я не поставил галочку `Match posts by default`.** Без неё подписка работает в режиме deny-by-default: пропускает только то, что явно разрешено матчерами. Матчеров нет → не проходит **ничего**. + +## Что происходит под капотом + +1. Релей шлёт `POST /inbox` → нода отвечает `202 Accepted`. **Это подтверждение приёма HTTP-запроса, а не сохранения статуса.** +2. Нода скачивает оригинал по URI (dereference), тратит трафик и время. +3. Статус идёт в `relay.Filter.MatchedBySubscription`. +4. Нет совпадения → статус выбрасывается. В лог падает `dropping unpermitted status` — **warn/debug, не error**. + +## Где прячется грабль + +В таблице `relay_subscriptions` два ключевых поля: `flags` (битовая маска) и `matchers` (JSON-правила). + +Флаги из `gtsmodel/relay.go`: + +``` +RelayFlagPublic = 2 (принимать публичные) +RelayFlagUnlisted = 4 (принимать скрытые) +RelayFlagMatchByDefault = 8 (принимать всё по умолчанию) +RelayFlagIgnoreSensitive = 16 (игнорировать чувствительное) +RelayFlagIgnoreMedia = 32 (игнорировать с медиа) +RelayFlagIgnoreReplies = 64 (игнорировать ответы) +``` + +У меня на всех трёх подписках стояло `flags = 22`. Раскладываем: `16 + 4 + 2` = `IgnoreSensitive + Unlisted + Public`. Выглядит осмысленно, правда? «Принимаем публичные и скрытые, игнорируем чувствительное». + +Но бита `MatchByDefault` (8) там нет. И `matchers = NULL`. + +## Почему без match_by_default дропается всё + +Логика `matchedByConnection` в `internal/filter/relay/relay.go`: + +1. Видимость: public → нужен флаг Public (есть), unlisted → нужен Unlisted (есть), остальное → false. +2. Чувствительное + `IgnoreSensitive` → false (это намеренно). +3. Медиа + `IgnoreMedia` → false (не стоит). +4. Ответ не себе + `IgnoreReplies` → false (не стоит). +5. Exclude-матчеры (чёрный список). Их нет → пропускаем. +6. **Если стоит `MatchByDefault` → true. У меня не стоит.** +7. Иначе ищем include-матчеры (белый список). Их нет вообще. +8. `return false`. + +Вот оно. Пустая подписка без матчеров — это подписка, которая **не пропускает ничего**. Deny-by-default. Не «не знаю», а именно «не разрешаю». + +## Что такое матчеры и зачем они нужны + +Матчер — это ключевое слово, которое ищется в **содержимом поста и в его content warning**. Поиск регистронезависимый. Есть два режима совпадения: partial (по умолчанию, ловит часть слова) и whole word (только целое слово). Хэштеги матчатся через префикс `#`. + +Матчеры бывают двух типов: + +**Include-матчеры (белый список).** Работают, когда `match_by_default` выключен. Пост пройдёт только если совпал хотя бы с одним include-матчером. Нет include-матчеров и нет `match_by_default` — не пройдёт ничего. Именно это и случилось у меня. + +**Exclude-матчеры (чёрный список).** Работают всегда, независимо от `match_by_default`. Если пост совпал с exclude-матчером — он дропается, даже если `match_by_default` включён. + +Матчеры дают админу **хирургический контроль** вместо грубого «всё или ничего». Include — «хочу только посты про infosec и Linux». Exclude — «принимай всё, кроме спама и nsFW». + +## Как диагностировать + +SQL: + +```sql +SELECT relay_actor_uri, flags, matchers FROM relay_subscriptions; +-- flags без бита 8 и matchers = NULL → подписка не пропускает ничего +``` + +API: + +``` +GET /api/v1/admin/relay_subscriptions +→ смотрим match_by_default +``` + +## ✅ Как чинить — и что надо было сделать сразу + +Два корректных пути при добавлении релея: + +**Путь 1: «Принимать всё, кроме явных запретов»** — поставить галочку `Match posts by default`. Тогда работают только exclude-матчеры и ignore-флаги. Всё, что не попало под запрет, — принимается. + +**Путь 2: «Принимать только то, что я явно указал»** — не ставить `Match posts by default`, но создать include-матчеры. Например, `infosec`, `linux`, `#GoToSocial`. + +Я выбрал ни то, ни другое. Надо было поставить галочку: + +``` +PUT /api/v1/admin/relay_subscriptions/{id} +{ "public": true, "unlisted": true, "match_by_default": true } +``` + +**После включения приток пошёл мгновенно:** 44 статуса за 30 минут, 12+ новых доменов (infosec.exchange, mastodon.world, burningboard.net, troet.cafe, norden.social, c.im, toot.wales, social.linux.pizza…). До этого лента месяцами содержала только пару доменов прямых подписок. + +## Выводы + +1. **Главное правило:** если хочешь «принимать всё, кроме запрещённого» — **обязательно ставь галочку `Match posts by default`**. Без неё релей будет слать, нода будет отвечать `202 Accepted`, а лента останется пустой. +2. Разрешить public/unlisted недостаточно — это лишь фильтры видимости, а не разрешение на приём. «Подписка есть» и «релей шлёт» ≠ «контент сохраняется». +3. Дизайн фильтра разумный — безопасный дефолт. Но для админа неочевидный: UI не кричит, что подписка без матчеров мёртвая. +4. Мониторь не только error, но и warn-строки `dropping unpermitted` / `not relayable`. +5. `202 Accepted` — это «запрос принят», а не «статус сохранён». Путать их — самый дешёвый способ незаметно потерять федерацию. + +--- + +**Теги:** #GoToSocial #ActivityPub #Fediverse #Relay #администрирование #грабли + +Если у кого-то была та же тишина в ленте при живом релее — проверьте `match_by_default`. Возможно, вы тоже кормите чёрную дыру. \ No newline at end of file From d176e0ded682cb63065807636a699d505658871a Mon Sep 17 00:00:00 2001 From: Storozhenko Evgeny Date: Sun, 13 Sep 2026 10:56:20 +0000 Subject: [PATCH 5/5] =?UTF-8?q?docs(project):=20=D1=81=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D1=83=D1=81,=20TODO,=20WALKTHROUGH=20=E2=80=94=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D1=82=20GoToSocial=20=D0=BE=D0=BF=D1=83=D0=B1=D0=BB?= =?UTF-8?q?=D0=B8=D0=BA=D0=BE=D0=B2=D0=B0=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/STATUS.md | 5 +++-- docs/TODO.md | 1 + docs/WALKTHROUGH.md | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/STATUS.md b/docs/STATUS.md index e5d6770..4e46d51 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,11 +1,12 @@ # dedinit.ru — Статус -Обновлено: 2026-09-12 (сессия уборки структуры и OpenSpec) +Обновлено: 2026-09-13 (пост про GoToSocial/релеи) ## Текущее состояние -Сайт-блог dedinit.ru на Hugo Extended (тема rDedInit/форк PaperMod) в порядке: структура репозитория приведена к стандарту (README краткий + вся документация в docs/ + AGENTS.md), всё запушено в gitverse (main). Рабочее дерево чистое. Следующая задача (локализация UI) оформлена через OpenSpec, реализация не начата. +Сайт-блог dedinit.ru на Hugo Extended (тема rDedInit/форк PaperMod) в порядке: структура репозитория приведена к стандарту (README краткий + вся документация в docs/ + AGENTS.md), всё запушено в gitverse (main). Опубликован новый пост про GoToSocial и релеи. Следующая задача (локализация UI) оформлена через OpenSpec, реализация не начата. ## Сделано +- 2026-09-13: Опубликован пост «GoToSocial молча съедал все статусы с релеев — и я нашёл почему» (commit 0fcfeb3): hero.svg, index.md, `make build`, `make deploy` — прод отдаёт 200 (проверено). - 2026-09-12: Уборка структуры репозитория — вся документация перенесена в docs/ (git mv, история сохранена), README.md переписан кратко, создан AGENTS.md с правилами проекта. - 2026-09-12: Правило «ВСЕ изменения проекта — через OpenSpec» добавлено в AGENTS.md (commit 0f4d6da). - 2026-09-12: Задача «Русская локализация i18n/ru.toml» оформлена через OpenSpec (propose) — change `localize-i18n-ru` валиден, запушен (f07bffe). Артефакты: proposal.md, specs/i18n/spec.md, design.md, tasks.md. diff --git a/docs/TODO.md b/docs/TODO.md index 26717d0..6d1bfab 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -7,6 +7,7 @@ | 2026-09-12 | Оформить задачу локализации i18n/ru.toml через OpenSpec (propose) | ✅ закрыта | commit f07bffe (change localize-i18n-ru) | | 2026-09-12 | Добавить в openspec-скиллы правильный синтаксис validate | ✅ закрыта | .hermes/skills/openspec-*/SKILL.md | | 2026-09-12 | Файлы проекта (STATUS/PRD/TODO/WALKTHROUGH) — в docs/ | ✅ закрыта | docs/STATUS.md, docs/PRD.md, docs/TODO.md, docs/WALKTHROUGH.md | +| 2026-09-13 | Пост «GoToSocial молча съедал статусы с релеев — и я нашёл почему» | ✅ закрыта | commit 0fcfeb3, задеплоено, прод 200 | | 2026-09-12 | Реализовать локализацию i18n/ru.toml (apply change localize-i18n-ru) | 🔵 открыта | | | — | Настроить цветовую схему под бренд dedinit() | 🔵 открыта | (ROADMAP) | | — | Русские переводы i18n (Read more→Читать далее и др.) | 🔵 открыта | (ROADMAP) | diff --git a/docs/WALKTHROUGH.md b/docs/WALKTHROUGH.md index d8ce885..e9648b6 100644 --- a/docs/WALKTHROUGH.md +++ b/docs/WALKTHROUGH.md @@ -1,5 +1,21 @@ # WALKTHROUGH — dedinit.ru (капитанский журнал) +## 2026-09-13 — Пост «GoToSocial молча съедал статусы с релеев» + +### Контекст +Опубликован пост о граблях с релеями GoToSocial: подписка выглядела рабочей (approved, 202 Accepted), а лента была пустой из-за отсутствия `match_by_default` и пустых матчеров (deny-by-default). + +### Что сделано +1. Создан бандл `content/posts/20260913 - gotosocial relay match by default/`: index.md (frontmatter по конвенции, date 2026-09-13T03:00+03:00 в прошлом) + hero.svg (единый стиль). +2. `make build` — без ошибок; статья в `public/2026/gotosocial-relay-match-by-default/`, title и hero.svg на месте, RSS собрался (5 постов, новый первый). +3. Коммит 0fcfeb3 запушен в gitverse (истина). +4. Деплой `make deploy` (SSHPASS из docs/DEPLOY_INSTRUCTIONS.md) — прод отдаёт 200: + https://dedinit.ru/2026/gotosocial-relay-match-by-default/ +5. docs/STATUS.md и docs/TODO.md обновлены (запись о публикации). + +### Примечание +- Коммит/пуш правок docs/ в этой сессии блокировался песочницей (требует явного подтверждения) — изменения на диске, коммит по подтверждению пользователя. + ## 2026-09-12 — Уборка структуры репозитория + OpenSpec ### Контекст