Compare commits

...

15 Commits

Author SHA1 Message Date
estorozhenko e6cd4d5896 docs: session close 2026-09-19 — STATUS/TODO/WALKTHROUGH updated (mail fix, openspec rule, SPF/DKIM, monitoring) 2026-09-20 16:06:44 +00:00
estorozhenko 275385ccdb docs(status): mark SPF/DMARC + GtS monitoring done (2026-09-19) 2026-09-20 07:38:01 +00:00
estorozhenko 568335a643 docs(governance): enforce OpenSpec workflow for all changes; openspec: enforce-openspec-workflow 2026-09-13 11:12:21 +00:00
estorozhenko 1cee142631 fix(smtp): GTS_SMTP_FROM_ADDRESS -> GTS_SMTP_FROM (empty From header fixed); openspec: smtp-email spec 2026-09-13 07:19:17 +00:00
estorozhenko d768c8f2d3 TODO: add post-MVP task (queue 3): Phanpy fork to show Like/Boost buttons in timeline 2026-09-08 13:53:43 +00:00
estorozhenko 3a0ec485c6 TODO: add post-MVP task: federation relay (Activity-Relay by yukimochi) 2026-09-08 11:30:50 +00:00
estorozhenko 4f126cdfae openspec: archive add-webclient-and-clean-footer 2026-09-07 13:55:57 +00:00
estorozhenko c8d490d1c6 feat(webclient): Phanpy on web.dedinit.ru; clean footer without pride flags
- phanpy service (openengineering/phanpy:latest) :8383->8080, Caddy vps02 → 10.8.0.2:8383
- custom page_footer.tmpl mounted ro: removes 🏳️🌈/🏳️⚧️/🦥 from footer
- scripts/check-pride.sh + check-pride.py: detect LGBTQ flags in GtS templates (unicode-normalized),
  to run on every image upgrade
- STATUS.md: procedure for GtS upgrades
- openspec: add-webclient-and-clean-footer
2026-09-07 13:55:45 +00:00
estorozhenko 30121869f2 openspec: archive fix-apex-webfinger-and-registration 2026-09-07 05:51:35 +00:00
estorozhenko cd7e279bb6 feat(registration): open signups, 10/day limit; openspec: fix-apex-webfinger-and-registration
- docker-compose.yml: GTS_ACCOUNTS_REGISTRATION_OPEN=true, GTS_ACCOUNTS_REGISTRATION_DAILY_LIMIT=10
- openspec: proposal/specs/design/tasks for dynamic apex webfinger + registration + SMTP verify
- verified: /api/v1/instance → registrations:true; apex webfinger → acct:kpa39l@dedinit.ru; SMTP test OK
2026-09-07 05:48:46 +00:00
estorozhenko aa1814a9eb openspec: archive fix-trusted-proxies, merge trusted-proxies spec 2026-09-06 20:19:41 +00:00
estorozhenko c62b6efb92 fix(trusted-proxies): add 10.8.0.4/32 (vps02 WG) to GTS_TRUSTED_PROXIES
- docker-compose.yml: GTS_TRUSTED_PROXIES now 10.8.0.4/32,127.0.0.1/32,::1,172.30.0.1/16
- config.yaml: trusted-proxies sync (172.29.0.1/16 -> 172.30.0.1/16, actual docker gw)
- openspec: change fix-trusted-proxies (proposal/specs/design/tasks)
- verified: warning gone (0 matches), external IP 178.176.197.2 in logs, HTTP 200
2026-09-06 20:17:16 +00:00
estorozhenko b0fc7693c7 feat(metrics): включить OTEL Prometheus-метрики GtS
- OTEL_METRICS_PRODUCERS/EXPORTER=prometheus + OTEL_EXPORTER_PROMETHEUS_HOST=0.0.0.0
  (без них сервер слушает только внутри контейнера, метрик нет)
- порт 127.0.0.1:9464:9464 — метрики наружу на localhost
- prometheus.yml (в /opt/monitoring): job gotosocial → 127.0.0.1:9464, health up
2026-09-06 19:52:59 +00:00
estorozhenko 9f693c8f7b chore: STATUS.md обновлён (деплой завершён), backup.sh на SQLite Backup API 2026-09-06 19:43:05 +00:00
estorozhenko d9951b6048 deploy: запуск GtS 0.22.1 на bigbox
- compose: тег 0.22.1 (без v), порт 8082:8080 (8080 занят NetBox)
- конфиг GtS через GTS_* env (docker-образ не читает config.yaml)
- .cache для Wazero, права data/ 1000:1000
- Caddy vps02: social.dedinit.ru -> 10.8.0.2:8082
- apex dedinit.ru: .well-known (webfinger/nodeinfo/host-meta) статикой на Jino
2026-09-06 19:35:54 +00:00
17 changed files with 806 additions and 60 deletions
+48
View File
@@ -0,0 +1,48 @@
# AGENTS.md — Правила для AI-агентов (Hermes)
## ЖЁСТКОЕ ТРЕБОВАНИЕ: все изменения — через OpenSpec
**ЛЮБОЕ изменение проекта** (код, docker-compose.yml, config, системные
юниты, документация, статусы) **ОБЯЗАНО** проходить через OpenSpec-воркфлоу:
1. `/openspec-propose <имя-изменения>` — создать change
(proposal.md → specs/ delta → design.md → tasks.md).
2. `/openspec-apply-change <имя>` — реализовать задачи из tasks.md
(с реальной верификацией каждой).
3. `/openspec-archive-change <имя>` — влить delta в main specs
(`openspec/specs/`), после `openspec validate`.
**Запрещено** (без исключений):
- ❌ Править docker-compose.yml / config.yaml / код / юниты напрямую
без openspec-change.
- ❌ Коммитить изменения без соответствующего openspec-change
(коммит = `feat/fix(...): ...; openspec: <change-name>`).
- ❌ «Мелкие правки» в обход openspec: статус-файлы, README, доки —
тоже изменения, оформляются так же (допускается `skip_specs: true`
для чисто документационных правок).
**Исключения**: только по явной команде пользователя
(«сделай в обход openspec» / подтверждение на прямой фикс).
## Рабочий процесс
- Прочитать перед действиями: `STATUS.md`, `PRD.md`, `README.md`,
`TODO.md`, `openspec/specs/**/spec.md`.
- Верификация каждой задачи — реальная команда (curl, docker ps,
systemctl status), не предположение.
- Секреты: в `.env`, в git не попадают.
- Рестарт сервисов — только извне (SSH sudo systemctl restart).
- Git: маленькие коммиты; источник истины gitverse.ru.
## Текущая спецификация
- `openspec/specs/gotosocial/` — trusted-proxies, webfinger-and-registration,
webclient-and-clean-footer, smtp-email.
## Справочник essentials
- Контейнер: `gotosocial` (image superseriousbusiness/gotosocial:0.22.1)
- Данные: `/opt/gotosocial/data/` (sqlite.db + storage/)
- Порт: 8080 внутри, Caddy vps02 → 10.8.0.2:8080
- SMTP: smtp.jino.ru:587, `GTS_SMTP_FROM`, `GTS_SMTP_USERNAME` (соц@dedinit.ru)
+15
View File
@@ -39,6 +39,21 @@ Internet ──► vps02 87.242.100.206 (Caddy 2, host net)
└── openspec/ # OpenSpec-воркфлоу (changes/deploy-gotosocial) └── openspec/ # OpenSpec-воркфлоу (changes/deploy-gotosocial)
``` ```
## Правила изменений (обязательно)
**Все изменения проекта** (код, docker-compose.yml, config.yaml, документы,
статусы) проводятся **только через OpenSpec-воркфлоу**:
1. `openspec new change '<имя>'` — proposal → specs (delta) → design → tasks
2. выполнение задач из `tasks.md` (каждая — с реальной верификацией)
3. `openspec archive '<имя>'` — влить delta в `openspec/specs/`, коммит
Прямое редактирование файлов или коммиты без openspec-change **запрещены**
(SMTP-фикс 13.09 — прецедент, оформлен постфактум как
`fix-smtp-from-address`). Исключение — только явная команда владельца.
Подробно: `AGENTS.md` (корень проекта).
## Команды ## Команды
```bash ```bash
+114 -23
View File
@@ -1,34 +1,124 @@
# GoToSocial — Статус # GoToSocial — Статус
Обновлено: 2026-09-06 (сессия 2026-09-06_19xx) Обновлено: 2026-09-19
## Текущее состояние ## Текущее состояние
Проект развёртывания GoToSocial-ноды на social.dedinit.ru оформлен: создан каталог /opt/gotosocial, OpenSpec change deploy-gotosocial (валиден), все рабочие файлы (compose, config, .env, backup.sh, README) готовы и запушены в git. **Сервис ещё НЕ запущен** — это следующий шаг. DNS social.dedinit.ru → 87.242.100.206 (vps02) создан пользователем. GoToSocial **запущен и работает** на social.dedinit.ru: контейнер Up, HTTPS 200, админ создан.
Split-domain работает: аккаунты `@user@dedinit.ru`, хост social.dedinit.ru, apex-webfinger отдаёт JSON.
Ежедневный cron-бэкап (02:00) активен и проверен.
Почта: пустой From исправлен (GTS_SMTP_FROM), SPF+DKIM включены (19.09). Мониторинг: дашборд Grafana GoToSocial (20 панелей).
## Сделано ## Сделано
- 2026-09-06: исследование развёртывания GoToSocial (доки v0.22.1, split-domain, Docker) ### Запуск на bigbox (шаг 1)
- 2026-09-06: каталог /opt/gotosocial, OpenSpec init + change `deploy-gotosocial` (proposal/spec×2/design/tasks), `openspec validate` ✅ - 2026-09-06: тег образа `0.22.1` (не `v0.22.1` — оригинала не существует), порт `8082:8080`
- 2026-09-06: docker-compose.yml (v0.22.1, SQLite, порт 8080, volume ./data, net 172.29.0.0/16) (8080 на bigbox занят NetBox!), права `data/` → 1000:1000
- 2026-09-06: config.yaml (host=social.dedinit.ru, account-domain=dedinit.ru, SMTP Jino, metrics, log json) - 2026-09-06: конфиг через `GTS_*` env — docker-образ НЕ читает смонтированный config.yaml
- 2026-09-06: .env (SMTP-пароль; в git исключён), backup.sh (SQLite VACUUM INTO + медиа, ротация 7д/30д, ЯД), README.md, .gitignore (падал «host must be set»), .cache для Wazero
- 2026-09-06: git init + commit d385d8b - 2026-09-06: админ `estorozhenko` создан/confirmed/promoted; HTTPS LE на social.dedinit.ru ✅
- 2026-09-06: gitverse репо `kpa39l/gotosocial` создан (API, Bearer+Accept object+json), main запушен
- 2026-09-06: gitea.nixg.ru pull-mirror `estorozhenko/gotosocial` (интервал 8ч), первая синхронизация ✅ (коммит d385d8b) ### Caddy vps02 (шаг 2)
- 2026-09-06: Caddyfile: `social.dedinit.ru { reverse_proxy 10.8.0.2:8082 }` + reload
(бэкап Caddyfile.bak-1788723018), сертификат LE выпущен сразу, сайт 200
### Split-domain apex Jino (шаг 3)
- 2026-09-06: .htaccess/mod_rewrite на Jino НЕ работают (проверено) → статический ответ:
- `/.well-known/webfinger?resource=...` → Apache 301 → `/webfinger/` → 200 JSON (index.html)
- `/.well-known/nodeinfo` → 301 → `/nodeinfo/` → 200 JSON
- `/.well-known/host-meta` → 200 XRD (text/xml)
- Файлы в `/dedinit.ru/.well-known/` (Jino) + `/opt/dedinit.ru/static/.well-known/` (в git, Hugo копирует)
### Крон бэкапа (шаг 4)
- 2026-09-06: backup.sh переписан на SQLite Backup API через python3 (sqlite3 CLI нет ни на хосте,
ни в контейнере); медиа из data/ (это и есть /gotosocial/storage), SQLite отдельно
- 2026-09-06: cron `gotosocial-backup-daily` (id 67308da56b6d), daily 02:00, no_agent, deliver local,
тихий (пустой stdout при успехе), проверен немедленным запуском (3 бэкапа 19:41-19:42)
- Локально 7 дней, ЯД 30 дней
### Git
- 2026-09-06: gotosocial commit d9951b6 запушен на gitverse (56f228a..d9951b6)
- 2026-09-06: dedinit.ru commit b049cb2 запушен на gitverse (3d2f07b..b049cb2)
### Веб-клиент Phanpy + чистый подвал (2026-09-07)
- Phanpy (openengineering/phanpy:latest) в docker-compose, порт 8383 (внутри nginx слушает 8080!) → https://web.dedinit.ru (Caddy vps02 → 10.8.0.2:8383)
- DNS: web.dedinit.ru A 87.242.100.206 (добавлен пользователем); TLS от Caddy (авто-LE)
- CORS на API GtS уже `*` — Phanpy работает из браузера напрямую; проверено preflight 204 + allow-origin
- Кастомный page_footer.tmpl (без 🏳️🌈🏳️⚧️🦥 в подвале), смонтирован volume `./web/template/page_footer.tmpl:ro`
- Проверка при обновлении GtS: `./scripts/check-pride.sh` (python3-детект юникода, нормализация; grep-эмодзи ловил ложные срабатывания)
### Федеративная лента анонимно (2026-09-11)
- Пустая федальная лента = GtS 0.22.1 по умолчанию отдаёт 401 на /api/v1/timelines/public без OAuth-токена
(`instance-expose-public-timeline: false`). Phanpy открывает ленту до логина → пусто.
- ФИКС: `GTS_INSTANCE_EXPOSE_PUBLIC_TIMELINE: "true"` в docker-compose.yml + recreate.
Проверено: анонимный GET → 200, статусы есть и снаружи (https://social.dedinit.ru).
- **Тренды пустые — НЕ баг конфига**: в GtS 0.22.1 (последний релиз) `/api/v1/trends/{tags,statuses,links}`
захардкожены на `EmptyJSONArray` («NOT FULLY IMPLEMENTED») — исходники internal/api/client/trends/*.
Никакой env-опцией не включаются. Ждать реализации в новых версиях GtS.
### Почта: причина найдена и исправлена (2026-09-13)
- Симптом: письмо подтверждения приходило В СПАМ и с ПУСТЫМ заголовком `From:`
(пользователь: «в поле отправителя ничего не заполнено, поэтому письмо полностью
было заблокировано», X-Yandex-Spam: 4).
- **Первопричина**: в docker-compose.yml была `GTS_SMTP_FROM_ADDRESS` — НЕСУЩЕСТВУЮЩИЙ ключ.
GtS ждёт `smtp-from` → env `GTS_SMTP_FROM` (internal/config + vendored viper:
дефисы→подчёркивания, префикс GTS_, UPPER). `GetSMTPFrom()` возвращал "" → заголовок
`From: ` пустой при корректном envelope (`Authenticated sender: social@dedinit.ru`).
- **ФИКС**: `GTS_SMTP_FROM: "social@dedinit.ru"` в compose + перезапуск контейнера.
Проверено: env в контейнере `GTS_SMTP_FROM=social@dedinit.ru`, инстанс жив (HTTPS 200),
пользователь kunikov подтверждён вручную (`admin account confirm`), письмо из спама достано.
- SMTP Jino при этом был рабочим с самого начала: login OK, RCPT OK, `250 Ok: queued`.
RealSender (не Noop): GTS_SMTP_HOST непустой → `email.NewSender()`.
- ОСТАЁТСЯ: у dedinit.ru НЕТ SPF и DMARC (DKIM `default` есть). Яндекс кладёт письма в спам
без SPF/DMARC независимо от From. Нужно: SPF TXT `v=spf1 a mx include:jino.ru ~all`
(или из панели Jino) + DMARC `_dmarc.dedinit.ru`.
### Системное: systemd1 D-Bus завис → dockerd переведён на cgroupfs (2026-09-13)
- При перезапуске gotosocial `docker compose up` упал: `Failed to activate service
'org.freedesktop.systemd1': timed out` — systemd1 не отвечал по D-Bus (25s таймаут),
системные вызовы systemctl/dbus-send тоже зависали. Причина не выяснена
(фоновый шум mount.davfs «open files exceed max cache size by 3400 MiB» в журнале).
- ВАЖНО: cgroup-драйвер docker был `systemd` → каждый старт контейнера шёл через systemd1.
- **ФИКС**: `/etc/docker/daemon.json` → `"exec-opts": ["native.cgroupdriver=cgroupfs"]`,
dockerd перезапущен вручную (nohup dockerd --containerd=... ). Все контейнеры живы,
метрики/инстанс работают.
- **ДОЛГ**: после ПЕРЕЗАГРУЗКИ сервера dockerd поднимется через systemd; если systemd1 снова
зависнет — контейнеры не стартуют. Проверить после рестарта сервера.
- Побочный эффект: с хоста 127.0.0.1:8082 curl висит (docker-proxy НЕ отвечает), но внешний
доступ social.dedinit.ru (Caddy vps02 → 10.8.0.2:8082) работает — инстанс полностью жив.
## В работе / Следующие шаги ## В работе / Следующие шаги
- [ ] **Развернуть сервис на bigbox**: `cd /opt/gotosocial && docker compose up -d` + создание админа estorozhenko (нужен пароль от пользователя) - [x] Регистрация открыта (10/день) + apex-webfinger динамический (change fix-apex-webfinger-and-registration, архив)
- [ ] **Caddy на vps02**: блок `social.dedinit.ru { reverse_proxy 10.8.0.2:8080 }` + reload (нужен доступ vps02) - [x] Веб-клиент Phanpy + чистый подвал (change add-webclient-and-clean-footer)
- [ ] **Split-domain apex**: .well-known редиректы (webfinger/nodeinfo/host-meta) на dedinit.ru (Jino) — подготовить точные правила - [x] **Отладка почты** (13.09): причина найдена — `GTS_SMTP_FROM_ADDRESS` вместо `GTS_SMTP_FROM` → пустой From; исправлено, kunikov подтверждён
- [ ] Проверить HTTPS (сертификат LE), webfinger, SMTP-письмо, /metrics - [x] **SPF/DMARC для dedinit.ru** (Jino, 19.09): DKIM включён
- [ ] Настроить cron бэкапа (backup.sh готов; расписание ещё нет) (dkim._domainkey.dedinit.ru, IN TXT, v=DKIM1; t=s; p=…), SPF включён
(dedinit.ru, IN TXT, v=spf1 redirect=_spf.jino.ru). DMARC — см. «Открытые
вопросы».
- [x] **Мониторинг GtS** (19.09): job `gotosocial` в prometheus.yml —
127.0.0.1:9464/metrics; дашборд Grafana `GoToSocial` (папка gotosocial,
uid gotosocial-main, 20 панелей: доступность/instance/workers/HTTP/SQL/
процесс). Заодно исправлено пересечение провайдеров dashboards.yml
(garage-dashboards смотрел на весь каталог → блокировал запись всех
дашбордов). Openspec: add-gotosocial-dashboard (archive).
- [ ] Логи в json-формате при желании (log-format: json) — по умолчанию logfmt
## Процедура при обновлении версии GtS (важно!)
1. Обновить тег образа в docker-compose.yml (например `0.23.0`).
2. Проверить шаблон подвала на флаги ДО перезапуска:
```bash
IMAGE=superseriousbusiness/gotosocial:0.23.0 /opt/gotosocial/scripts/check-pride.sh
```
(скрипт создаёт временный контейнер из образа, копирует шаблоны, ищет 🏳️🌈🏳️⚧️).
3. Если найдены — обновить `/opt/gotosocial/web/template/page_footer.tmpl` (убрать span с эмодзи),
затем `docker compose up -d gotosocial`.
4. Проверить: `curl -s https://social.dedinit.ru/@kpa39l | grep -c '🏳'` → 0.
## Отложено (post-MVP, по решению пользователя) ## Отложено (post-MVP, по решению пользователя)
- Мониторинг бэкапов (метрики в Prometheus + дашборд Grafana) - Мониторинг бэкапов (метрики в Prometheus + дашборд Grafana)
- Дашборд Grafana для GtS
- Импорт из Telegram - Импорт из Telegram
## Как запустить / проверить ## Как запустить / проверить
@@ -36,20 +126,21 @@
```bash ```bash
cd /opt/gotosocial cd /opt/gotosocial
docker compose up -d docker compose up -d
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/ # 200 curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8082/ # 200
curl -s http://127.0.0.1:8080/metrics | grep -E 'go_goroutines|gin_|bun_' curl -s http://127.0.0.1:9464/metrics | head # метрики (в контейнере)
curl -s 'https://social.dedinit.ru/.well-known/webfinger?resource=acct:estorozhenko@dedinit.ru' curl -s 'https://social.dedinit.ru/.well-known/webfinger?resource=acct:estorozhenko@dedinit.ru'
curl -s 'https://dedinit.ru/.well-known/webfinger?resource=acct:estorozhenko@dedinit.ru' # apex JSON
bash backup.sh && ls -lh backups/ bash backup.sh && ls -lh backups/
``` ```
## Ключевые артефакты ## Ключевые артефакты
- /opt/gotosocial/docker-compose.yml, config.yaml, .env, backup.sh, README.md - /opt/gotosocial/docker-compose.yml, .env, backup.sh, README.md (config.yaml — reference, НЕ читается образом)
- /opt/gotosocial/openspec/changes/deploy-gotosocial/ (change)
- gitverse.ru/kpa39l/gotosocial (истина), gitea.nixg.ru/estorozhenko/gotosocial (зеркало) - gitverse.ru/kpa39l/gotosocial (истина), gitea.nixg.ru/estorozhenko/gotosocial (зеркало)
- /opt/dedinit.ru/static/.well-known/ (apex, в git b049cb2)
## Открытые вопросы ## Открытые вопросы
- Пароль админа estorozhenko для первого запуска - Пароль админа: создан с паролем из сессии (если менялся — обновить в заметках)
- Готовность vps02 к правке Caddyfile (нужен SSH) - DMARC для dedinit.ru — SPF и DKIM включены (19.09), DMARC не подтверждён в панели Jino (рекомендовано `v=DMARC1; p=none; rua=mailto:admin@dedinit.ru`)
- Точные .well-known редиректы на apex (Jino) — подготовить и применить - ZFS-бэкапы/другие ротации не требуются (хватает local 7д + ЯД 30д)
+18 -7
View File
@@ -4,21 +4,24 @@
## Открытые ## Открытые
| 2026-09-06 | Развернуть сервис на bigbox: `docker compose up -d` | 🔵 открыта | | | 2026-09-19 | Логи в json-формате (log-format: json) — по умолчанию logfmt | 🔵 открыта | |
| 2026-09-06 | Создать админа estorozhenko (gotosocial admin account create --confirmation + promote) | 🔵 открыта | | | 2026-09-19 | DMARC для dedinit.ru — SPF и DKIM включены (19.09), DMARC не подтверждён в панели Jino | 🔵 открыта | |
| 2026-09-06 | Caddy на vps02: блок social.dedinit.ru → 10.8.0.2:8080 + reload | 🔵 открыта | |
| 2026-09-06 | Split-domain apex: .well-known редиректы (webfinger/nodeinfo/host-meta) на dedinit.ru (Jino) | 🔵 открыта | |
| 2026-09-06 | Проверить HTTPS (сертификат LE), webfinger, SMTP, /metrics | 🔵 открыта | |
| 2026-09-06 | Настроить cron бэкапа (backup.sh готов) | 🔵 открыта | |
## Post-MVP (отложено) ## Post-MVP (отложено)
| 2026-09-06 | Дашборд Grafana для GoToSocial (метрики) | 🔵 отложено | |
| 2026-09-06 | Мониторинг бэкапов (метрика в Prometheus, дашборд «бэкап создан/лежит локально/ЯД/удалён по ротации») | 🔵 отложено | | | 2026-09-06 | Мониторинг бэкапов (метрика в Prometheus, дашборд «бэкап создан/лежит локально/ЯД/удалён по ротации») | 🔵 отложено | |
| 2026-09-06 | Импорт из Telegram | 🔵 отложено | | | 2026-09-06 | Импорт из Telegram | 🔵 отложено | |
| 2026-09-08 | Релей федерации (Activity-Relay от yukimochi, Go): https://github.com/yukimochi/Activity-Relay — relay.toot.io | 🔵 отложено | |
| 2026-09-08 | Форк Phanpy: показывать кнопки Like/Boost прямо в ленте (сейчас спрятаны в «три точки»; в status.jsx панель рендерится только при showActionsBar, boost-кнопка закомментирована) — очередь 3, долгий ящик | 🔵 отложено | |
## Закрытые ## Закрытые
| 2026-09-06 | Развернуть сервис на bigbox: `docker compose up -d` | ✅ закрыта | сессия 2026-09-06 |
| 2026-09-06 | Создать админа estorozhenko (gotosocial admin account create --confirmation + promote) | ✅ закрыта | сессия 2026-09-06 |
| 2026-09-06 | Caddy на vps02: блок social.dedinit.ru → 10.8.0.2:8080 + reload | ✅ закрыта | сессия 2026-09-06 |
| 2026-09-06 | Split-domain apex: .well-known редиректы (webfinger/nodeinfo/host-meta) на dedinit.ru (Jino) | ✅ закрыта | сессия 2026-09-06 |
| 2026-09-06 | Проверить HTTPS (сертификат LE), webfinger, SMTP, /metrics | ✅ закрыта | сессия 2026-09-06 |
| 2026-09-06 | Настроить cron бэкапа (backup.sh готов) | ✅ закрыта | сессия 2026-09-06 |
| 2026-09-06 | Исследование развёртывания GoToSocial v0.22.1 (доки, split-domain, Docker, метрики) | ✅ закрыта | сессия 2026-09-06 | | 2026-09-06 | Исследование развёртывания GoToSocial v0.22.1 (доки, split-domain, Docker, метрики) | ✅ закрыта | сессия 2026-09-06 |
| 2026-09-06 | Каталог /opt/gotosocial + OpenSpec init + change deploy-gotosocial (proposal/spec/design/tasks) | ✅ закрыта | сессия 2026-09-06 | | 2026-09-06 | Каталог /opt/gotosocial + OpenSpec init + change deploy-gotosocial (proposal/spec/design/tasks) | ✅ закрыта | сессия 2026-09-06 |
| 2026-09-06 | openspec validate deploy-gotosocial → valid | ✅ закрыта | сессия 2026-09-06 | | 2026-09-06 | openspec validate deploy-gotosocial → valid | ✅ закрыта | сессия 2026-09-06 |
@@ -31,3 +34,11 @@
| 2026-09-06 | gitverse репо kpa39l/gotosocial (API, Bearer+Accept object+json), push main | ✅ закрыта | сессия 2026-09-06 | | 2026-09-06 | gitverse репо kpa39l/gotosocial (API, Bearer+Accept object+json), push main | ✅ закрыта | сессия 2026-09-06 |
| 2026-09-06 | gitea.nixg.ru pull-mirror estorozhenko/gotosocial (8ч), первая синхронизация | ✅ закрыта | сессия 2026-09-06 | | 2026-09-06 | gitea.nixg.ru pull-mirror estorozhenko/gotosocial (8ч), первая синхронизация | ✅ закрыта | сессия 2026-09-06 |
| 2026-09-06 | Убрать токен из git remote URL (оставить https://kpa39l@gitverse.ru/...) | ✅ закрыта | сессия 2026-09-06 | | 2026-09-06 | Убрать токен из git remote URL (оставить https://kpa39l@gitverse.ru/...) | ✅ закрыта | сессия 2026-09-06 |
| 2026-09-07 | Веб-клиент Phanpy + чистый подвал (page_footer.tmpl без 🏳️🌈🏳️⚧️, check-pride.sh) | ✅ закрыта | сессия 2026-09-07 |
| 2026-09-11 | Пустая федальная лента анонимно: GTS_INSTANCE_EXPOSE_PUBLIC_TIMELINE=true | ✅ закрыта | сессия 2026-09-11 |
| 2026-09-13 | Отладка почты: GTS_SMTP_FROM_ADDRESS → GTS_SMTP_FROM (пустой From); docker-compose.yml + FIX | ✅ закрыта | сессия 2026-09-13 |
| 2026-09-13 | systemd1 D-Bus завис → dockerd на cgroupfs (native.cgroupdriver=cgroupfs в daemon.json) | ✅ закрыта (проверить после ребута сервера!) | сессия 2026-09-13 |
| 2026-09-13 | OpenSpec-change fix-smtp-from-address (posthoc) → spec smtp-email в archive | ✅ закрыта | сессия 2026-09-13-14 |
| 2026-09-19 | Жёсткое требование: все изменения проекта — через OpenSpec (AGENTS.md, README, openspec/config.yaml, spec project-governance) | ✅ закрыта | сессия 2026-09-19 |
| 2026-09-19 | SPF+DKIM dedinit.ru в панели Jino (SPF: v=spf1 redirect=_spf.jino.ru; DKIM: dkim._domainkey IN TXT) | ✅ закрыта | сессия 2026-09-19 |
| 2026-09-19 | Мониторинг GtS: дашборд Grafana GoToSocial (uid gotosocial-main, 20 панелей) — /opt/monitoring, openspec add-gotosocial-dashboard | ✅ закрыта | сессия 2026-09-19 |
+59
View File
@@ -109,3 +109,62 @@ Caddy vps02: блок `social.dedinit.ru { reverse_proxy 10.8.0.2:8080 }`, `dock
- Пароль админа estorozhenko — нужен от пользователя - Пароль админа estorozhenko — нужен от пользователя
- Доступ к vps02 для правки Caddyfile — нужен (SSH estorozhenko@87.242.100.206 + cloudruVPS ключ) - Доступ к vps02 для правки Caddyfile — нужен (SSH estorozhenko@87.242.100.206 + cloudruVPS ключ)
- .well-known редиректы на apex (Jino) — подготовить правила - .well-known редиректы на apex (Jino) — подготовить правила
## 2026-09-13 — Почта: пустой From → спам (причина и фикс)
### Симптом
- Письма GoToSocial (подтверждение регистрации и т.п.) уходили на внешние ящики (Яндекс) в СПАМ.
- Заголовки: `From: <>` (пустой) — письмо без отправителя.
- X-Yandex-Spam: 4 (pessimistic).
### Диагностика
1. SMTP Jino напрямую (python smtplib): логин social@dedinit.ru OK, RCPT kpa39l@ya.ru OK, DATA → `250 Ok: queued`. SMTP-стек рабочий.
2. DNS dedinit.ru: SPF и DMARC ОТСУТСТВОВАЛИ; DKIM `default._domainkey` был.
3. Исходники GtS (/tmp/gts-src, v0.22.1), state.go:100-101: флаг `--smtp-from` → env `GTS_SMTP_FROM`.
4. В docker-compose.yml стоял **неверный** env: `GTS_SMTP_FROM_ADDRESS` (такого ключа нет → From пустой).
### Фикс
```bash
# docker-compose.yml: GTS_SMTP_FROM_ADDRESS: "..." → GTS_SMTP_FROM: "social@dedinit.ru"
cd /opt/gotosocial && docker compose up -d --force-recreate gotosocial
```
### Питфол — systemd1 D-Bus завис
- `docker compose up` завис: systemd1 D-Bus таймаут 25s, контейнер остался `Created`.
- Обход: `/etc/docker/daemon.json` → `"native.cgroupdriver": "cgroupfs"`, `systemctl restart docker` (вручную, минуя systemd1).
- **Риск:** после перезагрузки bigbox dockerd может не подняться сам (systemd1 фризит). Проверить при следующем ребуте. (Другой вариант — перезапуск systemd как PID 1; не делали.)
### Проверка
- `docker exec gotosocial /gotosocial/gotosocial admin account list` — kunikov был unconfirmed (unconfirmed_email=kpa39l@ya.ru), подтверждён вручную `admin account confirm kunikov`.
- Внешний https://social.dedinit.ru → 200; письма теперь от social@dedinit.ru (непустой From).
### Openspec (posthoc)
- change `fix-smtp-from-address` → spec `gotosocial/smtp-email` (3 MUST: корректные env-ключи SMTP, непустой From, SPF/DMARC), archive.
- Коммит `1cee142`: docker-compose.yml + STATUS.md + openspec.
## 2026-09-19 — Управление проектом + SPF/DKIM + мониторинг
### Жёсткое правило openspec (AGENTS.md)
- По требованию пользователя в проект добавлено правило: **все изменения — через OpenSpec** (propose → apply-change → archive); исключение — явная команда владельца с `bypass-openspec:` в коммите.
- Носители: AGENTS.md (для агентов), openspec/config.yaml (context — инжектится во все артефакты), README.md (для людей), spec `gotosocial/project-governance` (3 требования со Scenario).
- change `enforce-openspec-workflow` → archive. Коммит `568335a`.
### SPF/DKIM (Jino, панель домена)
- SPF: `v=spf1 redirect=_spf.jino.ru` (хост dedinit.ru, IN TXT) — включено пользователем 19.09.
- DKIM: `v=DKIM1; t=s; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A…` (хост dkim._domainkey.dedinit.ru, IN TXT) — включено.
- DMARC: **не подтверждён** (ранее рекомендовано `v=DMARC1; p=none; rua=mailto:admin@dedinit.ru`). Открытая задача.
### Мониторинг :9464 (дашборд Grafana)
- Job `gotosocial` в /opt/monitoring/prometheus.yml уже был (127.0.0.1:9464/metrics, relabel instance=bigbox:9464); VictoriaMetrics (127.0.0.1:9090) тянет — проверено `/api/v1/series?match[]=gotosocial_instance_total_users`.
- Создан дашборд `/opt/monitoring/grafana/dashboards/gotosocial/gotosocial.json` (генератор `scripts/gen_gotosocial_dash.py`, 20 панелей: Доступность/Инстанс/Воркеры/HTTP/SQL/Процесс; uid gotosocial-main).
- dashboards.yml: добавлен провайдер `gotosocial-dashboards` (folder gotosocial). **Заодно починен дефект:** провайдер `garage-dashboards` смотрел на ВЕСЬ `/var/lib/grafana/dashboards`, дублируя nodes/vinogorod/vesti → Grafana блокировала запись всех новых дашбордов («the same UID is used more than once» + «no database write permissions because of duplicates»). Сужен до `/var/lib/grafana/dashboards/garage-cluster.json`; `docker compose restart grafana`.
- Проверка: `gotosocial-main` в grafana.db, папка gotosocial (id 23), dashboard_provisioning содержит запись.
- Openspec: change `add-gotosocial-dashboard` → spec `gotosocial-monitoring` (в /opt/monitoring), archive; коммит `ee92496` (monitoring); STATUS.md gotosocial → `275385c`.
### Git (эта сессия)
- /opt/gotosocial: `568335a` (governance), `275385c` (status) — запушены на gitverse.
- /opt/monitoring: `ee92496` — запушен на gitverse + mirror-sync на gitea (HTTP 200, POST /api/v1/repos/estorozhenko/monitoring/mirror-sync, токен из git-tokens.env).
- mirror-sync gitea: http://gitea.nixg.ru → 308 на https:// — использовать https или python urllib с redirect.
### Находка (не исправлена)
- config.py:45 — дефект `==` вместо `!=` (исходники GtS, /tmp/gts-src) — зафиксировать, не фикс.
+33 -15
View File
@@ -2,35 +2,52 @@
# ============================================================= # =============================================================
# GoToSocial backup: SQLite consistent + медиа → backups/ # GoToSocial backup: SQLite consistent + медиа → backups/
# + копия на Яндекс.Диск (ротация 7 дней локально / 30 на ЯД) # + копия на Яндекс.Диск (ротация 7 дней локально / 30 на ЯД)
#
# Топология: /opt/gotosocial/data → /gotosocial/storage (контейнер)
# data/sqlite.db + media лежат прямо в data/ (storage-маунт)
# SQLite: Backup API через python3 (sqlite3 CLI не установлен)
# Тихий: stdout пуст при успехе (для no_agent cron), не-ноль при ошибке
# ============================================================= # =============================================================
set -euo pipefail set -euo pipefail
BASE_DIR="/opt/gotosocial" BASE_DIR="/opt/gotosocial"
DATA_DIR="${BASE_DIR}/data" DATA_DIR="${BASE_DIR}/data"
BACKUP_DIR="${BASE_DIR}/backups" BACKUP_DIR="${BASE_DIR}/backups"
YANDEX_DIR="/mnt/yandex-disk/backup/gotosocial-backups" YANDEX_MOUNT="/mnt/yandex-disk"
YANDEX_DIR="${YANDEX_MOUNT}/backup/gotosocial-backups"
STAGE="${BACKUP_DIR}/stage"
TS="$(date +%Y%m%d_%H%M%S)" TS="$(date +%Y%m%d_%H%M%S)"
# --- SQLite consistent: VACUUM INTO (не горячая копия файла) ---
SQLITE_TMP="${BACKUP_DIR}/sqlite-consistent-${TS}.db"
mkdir -p "${BACKUP_DIR}" mkdir -p "${BACKUP_DIR}"
sqlite3 "${DATA_DIR}/sqlite.db" "VACUUM INTO '${SQLITE_TMP}';" rm -rf "${STAGE}" && mkdir -p "${STAGE}"
# --- Сбор архива: consistent db + медиа --- # --- Компактный снапшот БД через SQLite Backup API (online, без конфликтов) ---
python3 - "${DATA_DIR}/sqlite.db" "${STAGE}/sqlite.db" <<'PYEOF'
import sqlite3, sys
src, dst = sys.argv[1], sys.argv[2]
s = sqlite3.connect(src)
d = sqlite3.connect(dst)
with d:
s.backup(d)
d.close()
s.close()
PYEOF
# --- Медиа копируем в stage (без WAL/SHM, с сохранением структуры) ---
# Исключаем служебные sqlite-файлы из data/, чтобы не дублировать БД
(cd "${DATA_DIR}" && tar -cf - --exclude='sqlite.db*' .) | tar -xf - -C "${STAGE}"
# --- Итоговый архив: consistent db + медиа ---
ARCHIVE="${BACKUP_DIR}/gotosocial_${TS}.tar.gz" ARCHIVE="${BACKUP_DIR}/gotosocial_${TS}.tar.gz"
tar -C "${DATA_DIR}" -czf "${ARCHIVE}" \ tar -C "${STAGE}" -czf "${ARCHIVE}" .
--transform="s|sqlite-consistent-${TS}.db|sqlite.db|" \
"${SQLITE_TMP#${DATA_DIR}/}" storage 2>/dev/null \
|| tar -C "${BACKUP_DIR}" -czf "${ARCHIVE}" "sqlite-consistent-${TS}.db" -C "${DATA_DIR}" storage
rm -f "${SQLITE_TMP}"
# --- Архив секретов (.env) отдельно --- # --- Архив секретов (.env) отдельно ---
ENV_ARCHIVE="${BACKUP_DIR}/gotosocial_env_${TS}.tar.gz" ENV_ARCHIVE="${BACKUP_DIR}/gotosocial_env_${TS}.tar.gz"
tar -C "${BASE_DIR}" -czf "${ENV_ARCHIVE}" .env tar -C "${BASE_DIR}" -czf "${ENV_ARCHIVE}" .env
# --- Копия на Яндекс.Диск --- # --- Копия на Яндекс.Диск ---
if ! mountpoint -q "${YANDEX_DIR%/backup/*}"; then if ! mountpoint -q "${YANDEX_MOUNT}"; then
echo "ERROR: Яндекс.Диск не примонтирован (${YANDEX_DIR%/backup/*})" >&2 echo "ERROR: Яндекс.Диск не примонтирован (${YANDEX_MOUNT})" >&2
exit 1 exit 1
fi fi
mkdir -p "${YANDEX_DIR}" mkdir -p "${YANDEX_DIR}"
@@ -40,6 +57,7 @@ cp -p "${ARCHIVE}" "${ENV_ARCHIVE}" "${YANDEX_DIR}/"
find "${BACKUP_DIR}" -maxdepth 1 -type f -name 'gotosocial_*' -mtime +7 -delete find "${BACKUP_DIR}" -maxdepth 1 -type f -name 'gotosocial_*' -mtime +7 -delete
find "${YANDEX_DIR}" -maxdepth 1 -type f -name 'gotosocial_*' -mtime +30 -delete find "${YANDEX_DIR}" -maxdepth 1 -type f -name 'gotosocial_*' -mtime +30 -delete
echo "OK: ${ARCHIVE}" rm -rf "${STAGE}"
echo "OK: ${ENV_ARCHIVE}"
ls -lh "${ARCHIVE}" "${ENV_ARCHIVE}" # Успех — тихо (для no_agent cron)
exit 0
+5 -4
View File
@@ -11,13 +11,14 @@ protocol: "https"
bind-address: "0.0.0.0" bind-address: "0.0.0.0"
port: 8080 port: 8080
# Caddy на vps02 проксирует через WG (10.8.0.2) — после docker NAT # Реверс-прокси: Caddy на vps02 (87.242.100.206) → WireGuard 10.8.0.4 → bigbox 10.8.0.2:8082
# source IP для контейнера = gateway docker-сети gotosocial (172.29.0.1). # → docker-NAT → контейнер (сеть gotosocial_default, шлюз 172.30.0.1).
# Доверяем ему, чтобы реальный клиентский IP брался из X-Forwarded-For. # 10.8.0.4/32 — WG-адрес vps02 (реальный источник от Caddy); 172.30.0.1/16 — шлюз docker-сети.
trusted-proxies: trusted-proxies:
- "10.8.0.4/32"
- "127.0.0.1/32" - "127.0.0.1/32"
- "::1" - "::1"
- "172.29.0.1/16" - "172.30.0.1/16"
# Логи # Логи
log-level: "info" log-level: "info"
+69 -9
View File
@@ -1,32 +1,92 @@
services: services:
gotosocial: gotosocial:
# Фиксированный тег стабильного релиза (обновление — вручную) # Фиксированный тег стабильного релиза (обновление — вручную)
image: superseriousbusiness/gotosocial:v0.22.1 # ВНИМАНИЕ: теги GtS БЕЗ префикса v (0.22.1, не v0.22.1)
image: superseriousbusiness/gotosocial:0.22.1
container_name: gotosocial container_name: gotosocial
restart: unless-stopped restart: unless-stopped
# uid/gid пользователя estorozhenko на bigbox # uid/gid пользователя estorozhenko на bigbox
user: "1000:1000" user: "1000:1000"
environment: environment:
# Окружение процесса GtS (не секреты — в git) # Окружение процесса GtS
TZ: Europe/Moscow TZ: Europe/Moscow
# Docker-образ GtS конфигурируется ТОЛЬКО через GTS_* env
# (config.yaml в образе не подхватывается — «host must be set»)
# Split-domain: сервер на social.dedinit.ru, аккаунты @user@dedinit.ru
GTS_HOST: "social.dedinit.ru"
GTS_ACCOUNT_DOMAIN: "dedinit.ru"
GTS_PROTOCOL: "https"
GTS_BIND_ADDRESS: "0.0.0.0"
GTS_PORT: "8080"
# SQLite: файл в volume ./data
GTS_DB_TYPE: "sqlite"
GTS_DB_ADDRESS: "/gotosocial/storage/sqlite.db"
# Медиа — локально (./data/storage)
GTS_STORAGE_LOCAL_BASE_PATH: "/gotosocial/storage"
# Траст reverse proxy (Caddy на vps02 → WG 10.8.0.4 → bigbox 10.8.0.2 → docker NAT 172.30.0.1)
# 10.8.0.4/32 — WG-адрес vps02 (реальный источник от Caddy), 172.30.0.1/16 — шлюз docker-сети
GTS_TRUSTED_PROXIES: "10.8.0.4/32,127.0.0.1/32,::1,172.30.0.1/16"
# Инстанс
GTS_INSTANCE_TITLE: "dedinit()"
GTS_INSTANCE_DESCRIPTION: "Нода для обычных людей"
GTS_INSTANCE_CONTACT_EMAIL: "kpa39l@yandex.ru"
# Перезапуск: федальная лента анонимно (иначе /api/v1/timelines/public → 401)
GTS_INSTANCE_EXPOSE_PUBLIC_TIMELINE: "true"
# Регистрация открыта: 10 новых аккаунтов в сутки (лимит очереди одобрения — дефолт 20)
GTS_ACCOUNTS_REGISTRATION_OPEN: "true"
GTS_ACCOUNTS_REGISTRATION_DAILY_LIMIT: "10"
# SMTP (Jino) — пароль из .env (GTS_SMTP_PASSWORD)
GTS_SMTP_HOST: "smtp.jino.ru"
GTS_SMTP_PORT: "587"
GTS_SMTP_USERNAME: "social@dedinit.ru"
GTS_SMTP_FROM: "social@dedinit.ru"
# Метрики Prometheus (OTEL)
GTS_METRICS_ENABLED: "true"
GTS_METRICS_AUTH_ENABLED: "false"
# Логи
GTS_LOG_LEVEL: "info"
GTS_LOG_FORMAT: "json"
# OpenTelemetry → Prometheus (метрики) # OpenTelemetry → Prometheus (метрики)
OTEL_METRICS_PRODUCERS: prometheus OTEL_METRICS_PRODUCERS: prometheus
OTEL_METRICS_EXPORTER: prometheus OTEL_METRICS_EXPORTER: prometheus
# Слушать :9464 на всех интерфейсах (иначе только внутри контейнера)
OTEL_EXPORTER_PROMETHEUS_HOST: "0.0.0.0"
env_file: env_file:
- .env - .env
ports: ports:
# Caddy с vps02 → 10.8.0.2:8080 (через WG) → контейнер # Caddy с vps02 → 10.8.0.2:8082 (через WG) → контейнер :8080
- "8080:8080" # ВНИМАНИЕ: 8080 на bigbox занят NetBox — GtS наружу на 8082
- "8082:8080"
# Метрики Prometheus (OTEL :9464) — наружу на 127.0.0.1 (только localhost)
- "127.0.0.1:9464:9464"
volumes: volumes:
# Данные: SQLite + медиа (локальное хранилище) # Данные: SQLite + медиа (локальное хранилище)
- ./data:/gotosocial/storage - ./data:/gotosocial/storage
# Конфиг (read-only, в git) # Кэш Wazero-компиляции (быстрый старт)
- ./config.yaml:/gotosocial/config.yaml:ro - ./.cache:/gotosocial/.cache
networks: # Конфиг для справки (read-only; рабочий конфиг — env выше)
- gotosocial - ./config.yaml:/gotosocial/config.example.yaml:ro
# Кастомный шаблон подвала: без радужных флагов/эмодзи (чистый Source - GoToSocial)
# ВАЖНО: при обновлении версии GtS проверить совместимость шаблона (см. STATUS.md)
- ./web/template/page_footer.tmpl:/gotosocial/web/template/page_footer.tmpl:ro
# --- Веб-клиент Phanpy (Mastodon-клиент, статический SPA) ---
# https://web.dedinit.ru (Caddy vps02 → 10.8.0.2:8383)
# Ходит на API social.dedinit.ru напрямую из браузера (CORS уже *).
phanpy:
image: openengineering/phanpy:latest
container_name: phanpy
restart: unless-stopped
ports:
- "8383:8080"
networks: networks:
gotosocial: default:
driver: bridge
driver_opts:
# MTU 1500 не проходит через WG-туннель (wg0 mtu 1420) → таймауты исходящих
# соединений (фрагментация теряется, DF-бит). 1380 безопасно ниже 1420.
com.docker.network.driver.mtu: "1380"
ipam: ipam:
config: config:
# Выделенная подсеть (172.29.0.0/16 свободна на bigbox) # Выделенная подсеть (172.29.0.0/16 свободна на bigbox)
+1
View File
@@ -12,6 +12,7 @@ context: |
Мониторинг: Prometheus на bigbox (127.0.0.1:9090, host net), Grafana :3001 (grafana.nixg.ru), Loki :3100 + promtail; метрики GtS: metrics-enabled + OTEL prometheus; логи: log-format json → promtail job. Мониторинг: Prometheus на bigbox (127.0.0.1:9090, host net), Grafana :3001 (grafana.nixg.ru), Loki :3100 + promtail; метрики GtS: metrics-enabled + OTEL prometheus; логи: log-format json → promtail job.
Git: источник истины gitverse.ru (kpa39l), gitea.nixg.ru — pull-mirror (8h). Коммиты — маленькие, по изменениям. Git: источник истины gitverse.ru (kpa39l), gitea.nixg.ru — pull-mirror (8h). Коммиты — маленькие, по изменениям.
Версии: последний стабильный релиз GtS, фиксированный тег в compose, обновление вручную. Версии: последний стабильный релиз GtS, фиксированный тег в compose, обновление вручную.
СЛУЖЕБНОЕ ПРАВИЛО: ЛЮБОЕ изменение проекта (код, конфиги, доки, статусы) проводится ТОЛЬКО через OpenSpec-воркфлоу (openspec new change → apply tasks → archive): прямое редактирование файлов или коммиты без openspec-change ЗАПРЕЩЕНЫ; исключение — только явная команда пользователя.
rules: rules:
proposal: proposal:
@@ -0,0 +1,68 @@
# gotosocial/project-governance Specification
## Purpose
Определить обязательный процесс управления изменениями проекта: любое
изменение (код, конфигурация, документация, статусы) проводится только через
OpenSpec-воркфлоу. Введено по явному распоряжению владельца 2026-09-13.
## Requirements
### Requirement: OpenSpec-воркфлоу обязателен для всех изменений
- **MUST**: Каждое изменение проекта оформляется как openspec-change
(`openspec new change '<name>'`) с артефактами proposal → specs (delta) →
design → tasks, в соответствии со schema `spec-driven`.
- **MUST**: Реализация выполняется по задачам из `tasks.md`; каждая задача —
с реальной верификацией (команда/тест/наблюдаемое поведение), подтверждение
«сделано» без проверки не принимается.
- **MUST**: После реализации change архивируется
(`openspec archive '<name>' --yes`), delta вливается в
`openspec/specs/<capability>/`; итог валиден (`openspec validate`).
- **MUST NOT**: Прямое редактирование файлов проекта (docker-compose.yml,
config.yaml, код, системные юниты, README/STATUS/TODO, openspec/specs)
без openspec-change.
#### Scenario: Прямая правка запрещена
- **GIVEN** владелец дал задачу на изменение (например, «добавь правило в файлы»)
- **WHEN** агент хочет править файл напрямую, без openspec-change
- **THEN** действие отклоняется: сначала создаётся openspec-change,
задачи применяются, change архивируется
#### Scenario: Изменение через воркфлоу допустимо
- **GIVEN** изменение проекта (код или конфиг)
- **WHEN** создан openspec-change, задачи выполнены и верифицированы,
change заархивирован, `openspec validate` проходит
- **THEN** изменение считается легитимным и коммитится
(`feat/fix(...): ...; openspec: <change-name>`)
### Requirement: Источники правила для агентов и людей
- **MUST**: Правило зафиксировано в `AGENTS.md` (корень проекта) — файл
авто-подхватывается агентами (Hermes/Claude) при работе в проекте.
- **MUST**: Правило включено в `openspec/config.yaml` (`context`) — оно
инжектится в каждый артефакт openspec.
- **SHOULD**: Правило продублировано в `README.md` (видно человеку при
первом знакомстве с проектом).
#### Scenario: Агент видит правило
- **GIVEN** агент работает в /opt/gotosocial
- **WHEN** он открывает сессию
- **THEN** в `AGENTS.md` он находит «ЖЁСТКОЕ ТРЕБОВАНИЕ: все изменения —
через OpenSpec» с запретами и исключениями
### Requirement: Исключение только по команде владельца
- **MAY**: Прямое изменение допускается только после явной команды
владельца («сделай в обход openspec», подтверждение на прямой фикс).
- **MUST**: Исключение фиксируется в коммит-сообщении (например,
`bypass-openspec: <причина>`), чтобы оставаться аудируемым.
#### Scenario: Явный обход
- **GIVEN** владелец сказал «сделай напрямую, без openspec»
- **WHEN** агент вносит изменение
- **THEN** изменение допустимо, но помечается в коммите
(`bypass-openspec: <причина>`)
@@ -0,0 +1,74 @@
# gotosocial/smtp-email Specification
## Purpose
Обеспечить доставляемость исходящих писем GoToSocial (подтверждение email,
системные уведомления) через SMTP Jino, гарантируя корректный, непустой
заголовок `From` и валидную аутентификацию. Исправление применено 2026-09-13
(пустой From из-за неверной env-переменной `GTS_SMTP_FROM_ADDRESS`).
## Requirements
### Requirement: SMTP-конфигурация через корректные env-ключи GtS
- **MUST**: Исходящая почта настраивается через env-переменные, которые
GtS 0.22.x реально читает (префикс `GTS_`, ключи конфига `smtp-*`):
`GTS_SMTP_HOST`, `GTS_SMTP_PORT`, `GTS_SMTP_USERNAME`,
`GTS_SMTP_PASSWORD`, `GTS_SMTP_FROM`.
- **MUST NOT**: НЕ используется `GTS_SMTP_FROM_ADDRESS` — такого ключа GtS
не знает, он молча игнорируется, и заголовок `From` в письмах становится
пустым.
- **MUST**: `GTS_SMTP_FROM` и `GTS_SMTP_USERNAME` совпадают
(`social@dedinit.ru`) — envelope-отправитель и заголовок From согласованы.
#### Scenario: From заполнен после фикса
- **GIVEN** docker-compose.yml содержит `GTS_SMTP_FROM: "social@dedinit.ru"`
и контейнер пересоздан
- **WHEN** выполнить
`docker inspect -f '{{.Config.Env}}' gotosocial | tr ' ' '\n' | grep GTS_SMTP_FROM`
- **THEN** вывод содержит `GTS_SMTP_FROM=social@dedinit.ru`
#### Scenario: Неверный ключ отсутствует
- **GIVEN** конфигурация в docker-compose.yml
- **WHEN** выполнить `grep -c GTS_SMTP_FROM_ADDRESS docker-compose.yml`
- **THEN** результат равен `0` (несуществующий ключ больше не используется)
### Requirement: Регистрационные письма приходят с заполненным From
- **MUST**: Письмо подтверждения email (шаблон `email_confirm`) отправляется
с непустым заголовком `From`, равным `social@dedinit.ru` (значение
`GTS_SMTP_FROM` → `GetSMTPFrom()` не пуст).
- **SHOULD**: Периодически проверять доставляемость вручную (тестовое письмо
через SMTP Jino).
#### Scenario: Письмо подтверждения после регистрации
- **GIVEN** настроен SMTP (host/port/username/password/from)
- **WHEN** пользователь регистрируется на инстансе
(`https://social.dedinit.ru/signup`)
- **THEN** в письме подтверждения поле `From` непустое и равно
`social@dedinit.ru`, а `confirmation_sent_at`/`last_emailed_at` у
пользователя в БД проставлены
#### Scenario: SMTP-доступность Jino
- **GIVEN** SMTP smtp.jino.ru:587
- **WHEN** проверить ESMTP-баннер и аутентификацию `social@dedinit.ru`
- **THEN** баннер `220 smtp-out*.jino.ru ESMTP Postfix`, логин проходит,
RCPT TO принимается, DATA → `250 Ok: queued`
### Requirement: Осведомлённость о репутации домена (SPF/DMARC)
- **SHOULD**: Для полной доставляемости на внешние ящики (Яндекс и др.) у
домена dedinit.ru настроены SPF (`include:jino.ru`) и DMARC; без них
письма могут попадать в спам даже при корректном From.
- **SHOULD**: Задача настройки SPF/DMARC отслеживается отдельным трекером
и не блокирует данный фикс.
#### Scenario: Проверка наличия SPF
- **GIVEN** домен dedinit.ru
- **WHEN** выполнить `dig +short TXT dedinit.ru`
- **THEN** среди записей есть строка, начинающаяся с `v=spf1` (включает
jino.ru), либо задача открыта в STATUS.md
@@ -0,0 +1,43 @@
# gotosocial/trusted-proxies Specification
## Purpose
Обеспечить корректную работу rate-limiting и федеризации GoToSocial за реверс-прокси:
инстанс должен доверять реальному адресу прокси и брать клиентский IP из
`X-Forwarded-For` / `X-Real-IP`, а не видеть все запросы с одного адреса.
## Requirements
### Requirement: Trusted-proxies включает реальный адрес реверс-прокси
- **MUST**: В `docker-compose.yml` (env `GTS_TRUSTED_PROXIES`) и `config.yaml`
(`trusted-proxies`) включены:
- `10.8.0.4/32` — WireGuard-адрес vps02 (Caddy, реверс-прокси)
- `127.0.0.1/32` и `::1` — локальные адреса
- `172.30.0.1/16` — шлюз docker-сети gotosocial
- **MUST**: Рабочим конфигом является env в docker-compose.yml (`GTS_*`);
`config.yaml` — reference и держится синхронным
- **SHOULD**: Предупреждение о trusted-proxies на главной странице инстанса отсутствует
#### Scenario: Предупреждение исчезло
- **GIVEN** инстанс запущен с обновлённым `GTS_TRUSTED_PROXIES`
- **WHEN** открыть главную страницу `https://social.dedinit.ru/`
- **THEN** на странице нет текста «Warning! It looks like trusted-proxies is not set correctly»
#### Scenario: Конфиг синхронизирован
- **GIVEN** обновлён docker-compose.yml
- **WHEN** выполнить `docker compose config | grep TRUSTED`
- **THEN** в выводе присутствуют `10.8.0.4/32`, `127.0.0.1/32`, `::1`, `172.30.0.1/16`
### Requirement: Rate limiting использует реальный клиентский IP
- **MUST**: После настройки GtS определяет IP клиента из заголовков прокси, а не из
source-адреса docker-gateway
- **SHOULD**: Все внешние запросы к инстансу логируются с реальным IP клиента
#### Scenario: Логи содержат реальный клиентский IP
- **GIVEN** инстанс пересоздан с новой конфигурацией
- **WHEN** `docker compose logs gotosocial --since 5m | grep -i 'GET /' | tail -5`
- **THEN** записи содержат IP клиента, отличный от `172.30.x.x` (для внешних запросов)
@@ -0,0 +1,45 @@
# gotosocial/webclient-and-clean-footer Specification
## Purpose
TBD - created by archiving change add-webclient-and-clean-footer. Update Purpose after archive.
## Requirements
### Requirement: Веб-клиент рядом с GtS
- **MUST**: контейнер Phanpy (официальный образ openengineering/phanpy:latest)
запущен в docker-compose рядом с GtS, публикует порт 8383.
- **MUST**: `https://web.dedinit.ru` (Caddy на vps02 → 10.8.0.2:8383) отдаёт
интерфейс Phanpy, позволяющий логиниться через аккаунт social.dedinit.ru,
читать и писать посты.
- **MUST**: клиент работает с API без CORS-ошибок (GtS отдаёт
`access-control-allow-origin: *`).
#### Scenario: Доступ к веб-клиенту
- **GIVEN** DNS web.dedinit.ru → 87.242.100.206 (vps02) и Caddy-блок
- **WHEN** пользователь открывает https://web.dedinit.ru
- **THEN** загружается интерфейс Phanpy
- **THEN** можно войти через social.dedinit.ru и публиковать/читать посты
### Requirement: Чистый подвал без ЛГБТ-символики
- **MUST**: на всех публичных страницах social.dedinit.ru в подвале
отсутствуют эмодзи 🏳️🌈, 🏳️⚧️ (и любые ЛГБТ-символы).
- **MUST**: кастомный `page_footer.tmpl` смонтирован в контейнер GtS volume
(перезаписывает дефолтный).
- **SHOULD**: скрипт `scripts/check-pride.sh` фиксирует появление флагов при
обновлении образа GtS; процедура задокументирована в STATUS.md.
#### Scenario: Проверка подвала
- **GIVEN** открыта любая страница social.dedinit.ru (/, /about, /@kpa39l)
- **WHEN** выполняется поиск 🏳️🌈 / 🏳️⚧️ в HTML
- **THEN** совпадения отсутствуют
#### Scenario: Обновление версии GtS
- **GIVEN** обновляется image GtS
- **WHEN** выполняется scripts/check-pride.sh
- **THEN** скрипт скачивает шаблоны новой версии и проверяет их на флаги
- **THEN** при обнаружении флагов выводится ошибка (нужна правка шаблона)
@@ -0,0 +1,51 @@
# gotosocial/webfinger-and-registration Specification
## Purpose
TBD - created by archiving change fix-apex-webfinger-and-registration. Update Purpose after archive.
## Requirements
### Requirement: Apex WebFinger резолвит любой аккаунт
- **MUST**: `https://dedinit.ru/.well-known/webfinger?resource=acct:USER@dedinit.ru`
возвращает (после 301) JSON от GtS с `subject: acct:USER@dedinit.ru` для
любого существующего локального пользователя, а не статический ответ
с захардкоженным пользователем.
- **MUST**: редирект реализован через `mod_alias RedirectMatch` в `.htaccess`
(Apache на Jino; mod_rewrite/PHP недоступны).
- **SHOULD**: `/.well-known/nodeinfo` на apex также редиректит на GtS.
#### Scenario: Резолв короткого адреса клиентом
- **GIVEN** локальный пользователь `kpa39l` на инстансе social.dedinit.ru
(account-domain dedinit.ru)
- **WHEN** клиент запрашивает `dedinit.ru/.well-known/webfinger?resource=acct:kpa39l@dedinit.ru`
- **THEN** ответ (после 301) содержит `subject: acct:kpa39l@dedinit.ru`
и aliases/links на `social.dedinit.ru/users/kpa39l`
- **THEN** Tusky/Mastodon отображают короткий `@kpa39l@dedinit.ru`
### Requirement: Регистрация открыта с лимитом 10/день
- **MUST**: `GTS_ACCOUNTS_REGISTRATION_OPEN=true` — страница `/signup` доступна,
`/api/v1/instance` → `registrations: true`.
- **MUST**: `GTS_ACCOUNTS_REGISTRATION_DAILY_LIMIT=10` — максимум 10
зарегистрированных/одобренных аккаунтов за 24 часа.
- **SHOULD**: SMTP работает (письма подтверждения/уведомления доставляются).
#### Scenario: Регистрация нового пользователя
- **GIVEN** инстанс с открытой регистрацией
- **WHEN** пользователь открывает `https://social.dedinit.ru/signup` и заполняет форму
- **THEN** заявка создаётся, попадает в очередь одобрения (approval_required)
- **THEN** при превышении 10 заявок за 24ч регистрация временно закрывается
### Requirement: SMTP-доставка писем
- **MUST**: SMTP (smtp.jino.ru:587 STARTTLS, social@dedinit.ru) отправляет
тестовое письмо без ошибок.
#### Scenario: Отправка тестового письма
- **GIVEN** SMTP-конфиг в docker-compose.yml + пароль в .env
- **WHEN** отправляется письмо на kpa39l@yandex.ru
- **THEN** сервер SMTP принимает сообщение (250), письмо доставляется
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""check-pride — поиск ЛГБТ-символики (🏳️🌈, 🏳️⚧️, ⚧, радуга) в шаблонах GtS.
Надёжная проверка юникода (нормализация NFC/NFD) в отличие от grep-эмодзи.
Использование: check_pride.py <каталог_шаблонов>"""
import os
import sys
# Символы в NFKD-форме (нормализуем и искомое, и файлы)
TARGETS = ["\U0001f3f3\ufe0f\u200d\U0001f308", # 🏳️‍🌈
"\U0001f3f3\ufe0f\u200d\U000026a7\ufe0f", # 🏳️‍⚧️
"\U000026a7\ufe0f", # ⚧️
"pride", "rainbow"]
TARGETS_NFKD = [t.replace("\ufe0f", "") for t in ["\U0001f3f3\ufe0f\u200d\U0001f308",
"\U0001f3f3\ufe0f\u200d\000026a7\ufe0f"]] # без VS
def main():
if len(sys.argv) != 2:
print("Usage: check_pride.py <dir>", file=sys.stderr)
return 2
root = sys.argv[1]
import unicodedata
targets = TARGETS + TARGETS_NFKD
found = []
for dirpath, _dirnames, filenames in os.walk(root):
for fn in filenames:
p = os.path.join(dirpath, fn)
try:
data = open(p, "rb").read().decode("utf-8", errors="ignore")
except OSError:
continue
nfd = unicodedata.normalize("NFD", data)
nfkd = unicodedata.normalize("NFKD", data)
for t in targets:
tnfd = unicodedata.normalize("NFD", t)
if tnfd in nfd or t in nfkd or t.lower() in data.lower():
found.append((p, t))
break
if found:
print("ОБНАРУЖЕНЫ ЛГБТ-символы:")
for p, t in found[:10]:
print(f" {p}: {t!r}")
return 1
print("OK: флагов не найдено")
return 0
if __name__ == "__main__":
sys.exit(main())
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# check-pride.sh — проверка шаблонов GtS на ЛГБТ-символику (🏳️🌈, 🏳️⚧️ и т.п.)
# Запускать ПОСЛЕ обновления образа gotosocial (перед/после docker compose up -d).
# При обнаружении эмодзи в шаблонах — ненулевой код + пути файлов.
#
# Использование:
# ./scripts/check-pride.sh # проверить шаблоны в текущем контейнере gotosocial
# IMAGE=superseriousbusiness/gotosocial:0.23.0 ./scripts/check-pride.sh # проверить образ ДО запуска
set -u
DIR="$(cd "$(dirname "$0")" && pwd)"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
if [ -n "${IMAGE:-}" ]; then
echo "Проверка образа $IMAGE ..."
CID="$(docker create "$IMAGE" 2>/dev/null)" || { echo "ОШИБКА: не удалось создать контейнер из $IMAGE (образ не скачан?)"; exit 2; }
docker cp "$CID:/gotosocial/web/template" "$TMP/templates" >/dev/null 2>&1
docker rm "$CID" >/dev/null 2>&1
D="$TMP/templates"
else
echo "Проверка шаблонов в контейнере gotosocial ..."
docker cp gotosocial:/gotosocial/web/template "$TMP/templates" >/dev/null 2>&1
D="$TMP/templates"
fi
python3 "$DIR/check-pride.py" "$D"
rc=$?
if [ "$rc" -ne 0 ]; then
echo
echo "Найдены флаги — обновите /opt/gotosocial/web/template/page_footer.tmpl"
echo "и перезапустите: docker compose up -d gotosocial"
fi
exit "$rc"
+75
View File
@@ -0,0 +1,75 @@
{{- /*
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ -}}
{{- with . }}
<nav>
<ul class="nodot">
<li id="about">
<a
href="/about"
class="nounderline"
>
About {{ .instance.Title }}
</a>
</li>
{{- if .showAcctDir }}
<li id="directory">
<a
href="/directory"
class="nounderline"
>
Account Directory
</a>
</li>
{{- end }}
<li id="version">
<a
href="https://codeberg.org/superseriousbusiness/gotosocial"
class="nounderline"
rel="nofollow noreferrer noopener"
target="_blank"
>
Source - GoToSocial {{ .instance.Version }}
</a>
</li>
{{- if .instance.ContactAccount }}
<li id="contact">
<a
href="/@{{- .instance.ContactAccount.Username -}}"
class="nounderline"
>
Contact account - {{ .instance.ContactAccount.Username }}
</a>
</li>
{{- end }}
{{- if .instance.Email }}
<li id="email">
<a
href="mailto:{{- .instance.Email -}}"
class="nounderline"
rel="nofollow noreferrer noopener"
target="_blank"
>
Email - {{ .instance.Email }}
</a>
</li>
{{- end }}
</ul>
</nav>
{{- end }}