post: GoToSocial молча съедал статусы с релеев — match_by_default
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 400" width="100%" height="auto">
|
||||
<rect width="1200" height="400" fill="#0a0c10"/>
|
||||
|
||||
<!-- Hexagon pattern -->
|
||||
<g stroke="#1a2332" stroke-width="1" fill="none" opacity="0.5">
|
||||
<polygon points="100,100 130,80 160,100 160,140 130,160 100,140"/>
|
||||
<polygon points="250,100 280,80 310,100 310,140 280,160 250,140"/>
|
||||
<polygon points="400,100 430,80 460,100 460,140 430,160 400,140"/>
|
||||
<polygon points="550,100 580,80 610,100 610,140 580,160 550,140"/>
|
||||
<polygon points="700,100 730,80 760,100 760,140 730,160 700,140"/>
|
||||
<polygon points="850,100 880,80 910,100 910,140 880,160 850,140"/>
|
||||
<polygon points="1000,100 1030,80 1060,100 1060,140 1030,160 1000,140"/>
|
||||
|
||||
<polygon points="175,160 205,140 235,160 235,200 205,220 175,200"/>
|
||||
<polygon points="325,160 355,140 385,160 385,200 355,220 325,200"/>
|
||||
<polygon points="475,160 505,140 535,160 535,200 505,220 475,200"/>
|
||||
<polygon points="625,160 655,140 685,160 685,200 655,220 625,200"/>
|
||||
<polygon points="775,160 805,140 835,160 835,200 805,220 775,200"/>
|
||||
<polygon points="925,160 955,140 985,160 985,200 955,220 925,200"/>
|
||||
<polygon points="1075,160 1105,140 1135,160 1135,200 1105,220 1075,200"/>
|
||||
</g>
|
||||
|
||||
<!-- Center text -->
|
||||
<text x="600" y="175" font-family="monospace" font-size="36" fill="#58a6ff" text-anchor="middle" font-weight="bold">
|
||||
GoToSocial и молча съеденные статусы
|
||||
</text>
|
||||
<text x="600" y="215" font-family="monospace" font-size="20" fill="#8b949e" text-anchor="middle">
|
||||
ActivityPub · Relay · match_by_default · deny-by-default
|
||||
</text>
|
||||
|
||||
<!-- Decor line -->
|
||||
<line x1="200" y1="250" x2="1000" y2="250" stroke="#30363d" stroke-width="1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -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`. Возможно, вы тоже кормите чёрную дыру.
|
||||
Reference in New Issue
Block a user