<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Admin on dedinit()</title><link>https://dedinit.ru/tags/admin/</link><description>Recent content in Admin on dedinit()</description><generator>Hugo -- 0.165.0</generator><language>ru-ru</language><lastBuildDate>Sun, 13 Sep 2026 15:55:00 +0300</lastBuildDate><atom:link href="https://dedinit.ru/tags/admin/index.xml" rel="self" type="application/rss+xml"/><item><title>Завис systemd1: почему без перезагрузки не обойтись</title><link>https://dedinit.ru/2026/systemd1-hang-systemctl-reboot-force/</link><pubDate>Sun, 13 Sep 2026 15:55:00 +0300</pubDate><guid>https://dedinit.ru/2026/systemd1-hang-systemctl-reboot-force/</guid><description>systemd1 висит: systemctl молчит, bus-активация org.freedesktop.systemd1 таймаутит, docker жив, а PID 1 превратился в один поток. Разбор: почему daemon-reexec не помог, почему Яндекс-Диск был лишь триггером и как перезагрузка с --force --force разрулила ситуацию.</description><content:encoded><![CDATA[<figure class="entry-cover"><img src="https://dedinit.ru/2026/systemd1-hang-systemctl-reboot-force/hero.svg" alt="Завис systemd1 — systemctl молчит, лечится перезагрузкой"></figure><h1 id="завис-systemd1-почему-без-перезагрузки-не-обойтись">Завис systemd1: почему без перезагрузки не обойтись</h1>
<p><strong>TL;DR:</strong> systemd1 (D-Bus-сервис <code>org.freedesktop.systemd1</code>, через который работает <code>systemctl</code>) завис. <code>systemctl</code> молчит, активация сервиса упирается в 25-секундный таймаут, а процесс systemd (PID 1) остаётся с одним-единственным потоком — мёртвый менеджер, который не обслуживает шину. <code>daemon-reexec</code> не помогает. Перезагрузка — единственный выход, и <code>systemctl --force --force reboot</code> справляется даже когда обычный reboot падает с <code>Connection timed out</code>.</p>
<h2 id="как-это-выглядело">Как это выглядело</h2>
<p>Всё началось с того, что у меня завис FUSE-модуль с Яндекс-Диском (<code>/mnt/yandex-disk</code>, davfs2). Пока я разбирался, я заметил, что команды <code>systemctl</code> перестали отвечать вообще.</p>
<p>Когда я попробовал перезагрузиться обычным способом:</p>
<pre tabindex="0"><code>$ sudo systemctl reboot

Broadcast message from root@bigbox on pts/31 (Sun 2026-09-13 13:08:39 UTC):
The system will reboot now!

Call to Reboot failed: Connection timed out
Failed to start reboot.target: Connection timed out
See system logs and &#39;systemctl status reboot.target&#39; for details.
It is possible to perform action directly, see discussion of --force --force in man:systemctl(1).
</code></pre><p>Беда в том, что <code>systemctl reboot</code> сам идёт через systemd1. Если systemd1 висит, то и перезагрузиться через него нельзя — получается «замкнутый круг».</p>
<p>При этом интересная деталь: <code>docker ps</code> и <code>docker info</code> работали нормально, контейнеры — живые, <em>мои</em> агенты продолжали работать. Падал только сам systemd-интерфейс.</p>
<h2 id="что-показала-диагностика">Что показала диагностика</h2>
<p><strong>1. systemd1 не активируется на D-Bus.</strong></p>
<pre tabindex="0"><code>$ busctl | grep systemd1
org.freedesktop.systemd1   -   -   -   (activatable)   -   -   -
</code></pre><p><code>(activatable)</code> значит, что сервис зарегистрирован, но не запущен. При попытке его запустить:</p>
<pre tabindex="0"><code>$ systemctl is-system-running
[tаймаут через 25 секунд]

$ journalctl | grep systemd1
dbus-daemon[1859]: Failed to activate service &#39;org.freedesktop.systemd1&#39;:
timed out (service_start_timeout=25000ms)
</code></pre><p><strong>2. PID 1 — systemd — жив, но как будто мёртв внутри.</strong></p>
<pre tabindex="0"><code>$ ps -o pid,nlwp,stat,etime -p 1
  1   1  Ss  3-06:26:57
</code></pre><p>Ключевое — <code>nlwp = 1</code>. Нормальный systemd держит десятки потоков (они обслуживают шину, юниты, таймеры&hellip;). Один поток означает, что менеджер фактически не работает, а просто висит. Именно поэтому <code>daemon-reexec</code> не реагирует толком — перезапускать некому.</p>
<p><strong>3. Docker тут ни при чём.</strong></p>
<ul>
<li><code>dockerd</code> жил, <code>docker ps</code> отвечал мгновенно;</li>
<li>все контейнеры — <code>Up</code>, с restart-политиками <code>unless-stopped</code> / <code>always</code>;</li>
<li>даже <code>systemctl restart docker</code> упал бы в тот же таймаут systemd1, потому что этот вызов тоже идёт через шину.</li>
</ul>
<p><strong>4. Даже перезагрузка через systemctl не сработала:</strong> <code>Call to Reboot failed: Connection timed out</code>.</p>
<h2 id="почему-fuse-яндекс-диск-виноват-лишь-отчасти">Почему FUSE-Яндекс-Диск виноват лишь отчасти</h2>
<p>Я сначала грешил на зависший davfs2: он висел, и systemd вполне мог залипнуть, пытаясь обратиться к .mount-юниту, который упирается в молчащую FUSE-точку. <strong>Это правдоподобный триггер.</strong> Но уже к моменту диагностики я перемонтировал Яндекс-Диск заново, и он заработал — а systemd1 так и остался висящим.</p>
<p>Проверка это подтвердила:</p>
<ul>
<li><code>ls /mnt/yandex-disk/</code> отвечает мгновенно;</li>
<li>D-state (непрерываемые) процессы отсутствуют;</li>
<li>демон davfs2 жив.</li>
</ul>
<p>То есть: точка Yandex больше не блокирует ничего, а systemd1 всё равно не оживает. Вывод — <strong>проблема уже в самом systemd</strong>, а не в FUSE. FUSE был спусковым крючком, но чинить нужно было не монтирование.</p>
<h2 id="что-мы-перепробовали-перед-ребутом">Что мы перепробовали перед ребутом</h2>
<p><strong>Попытка 1: <code>systemctl daemon-reexec</code></strong> — перезапуск systemd «на месте», самая мягкая реанимация.</p>
<pre tabindex="0"><code>$ sudo systemctl daemon-reexec
# вернуло 0
</code></pre><p>Вернуло успех, но шину не оживило. Потому что systemd с одним потоком не смог толком выполнить re-exec — команда «прошла», но менеджер остался в том же состоянии. Проверка после:</p>
<pre tabindex="0"><code>$ busctl | grep systemd1
org.freedesktop.systemd1   -   -   -   (activatable)   ...
$ systemctl is-system-running
[tаймаут]
</code></pre><p>Ничего не изменилось.</p>
<p><strong>Попытка 2: обычный reboot.</strong> Упал с <code>Connection timed out</code> (см. выше). По понятной причине: reboot через systemctl — это тоже запрос к systemd1.</p>
<p><strong>Попытка 3: <code>systemctl --force --force reboot</code></strong> — двойной форсированный перезапуск. Именно он сработал:</p>
<pre tabindex="0"><code>$ sudo systemctl --force --force reboot
Rebooting.
</code></pre><h2 id="как-это-работает-зачем-два-force">Как это работает: зачем два &ndash;force</h2>
<p><code>systemctl reboot</code> без флагов просит systemd корректно завершить все юниты. Если systemd1 виснет, запрос не проходит. Два <code>--force</code> — это «мне плевать на зависшие службы, перезагружайся»:</p>
<ul>
<li>первый <code>--force</code> — пропустить остановку юнитов (не ждать, пока всё аккуратно завершится),</li>
<li>второй <code>--force</code> (для reboot) — выполнить системный вызов перезагрузки напрямую, <strong>в обход systemd как менеджера</strong>.</li>
</ul>
<p>То есть это фактически «аварийная» перезагрузка через системный механизм ядра (reboot(2)), минуя зависший менеджер. Риск — грязное завершение: незаписанные данные могут потеряться, но при зависшей шине это часто единственный вариант.</p>
<h2 id="после-перезагрузки">После перезагрузки</h2>
<p>Система поднялась за ~11 минут:</p>
<ul>
<li><code>systemctl is-system-running</code> → <code>degraded</code> (отвечает мгновенно — шина жива);</li>
<li><code>org.freedesktop.systemd1</code> на busctl теперь <strong>активирован</strong> (PID systemd, <code>:1.5</code>), а не <code>(activatable)</code>;</li>
<li>docker и containerd — <code>active</code>;</li>
<li><strong>все 27 контейнеров</strong> поднялись сами (restart-политики <code>unless-stopped</code>/<code>always</code> сработали): gitea, netbox, grafana, prometheus, loki, gotosocial, garage, ollama, open-webui, searxng и остальные;</li>
<li><code>/mnt/yandex-disk</code> смонтирован и отвечает; <code>/opt/hermes/obsidian-vault</code> (s3fs) тоже на месте.</li>
</ul>
<p>Единственный failed-юнит — <code>mdmonitor.service</code> (мониторинг MD-массивов). Падает с <code>mdadm: No mail address or alert command - not monitoring</code> — это не поломка, а отсутствие настроенного алерта. На рабочие массивы не влияет, можно замаскировать.</p>
<h2 id="выводы">Выводы</h2>
<ol>
<li><strong>Если systemctl молчит, а docker работает — значит, дело в шине, а не в docker.</strong> Проверяй <code>busctl | grep systemd1</code> и <code>nlwp</code> у PID 1.</li>
<li><strong><code>daemon-reexec</code> не всегда реанимирует</strong> — если у systemd остался один поток, перезапускать себя фактически некому.</li>
<li><strong>Обычный <code>systemctl reboot</code> тоже идёт через systemd1</strong> — при висящей шине он не сработает. Нужен <code>--force --force</code>.</li>
<li><strong>FUSE-зависание может быть триггером</strong>, но не обязательно причиной. Если точка перемонтирована и отвечает, а systemd1 всё равно молчит — лечится только перезагрузкой.</li>
<li><strong>Restart-политики контейнеров себя отрабатывают</strong>: <code>unless-stopped</code>/<code>always</code> подняли всё после ребута, ничего не пришлось поднимать руками.</li>
</ol>
<p>Полный разбор с командами, как это диагностировать, — в WALKTHROUGH проекта.</p>
<hr>
<p><strong>Теги:</strong> #systemd #systemctl #dbus #fuse #davfs2 #reboot #администрирование #грабли</p>
<p>Если у вас <code>systemctl</code> молчит, а контейнеры живут — проверьте <code>busctl | grep systemd1</code> и <code>ps -o nlwp -p 1</code>. И не тратьте время на daemon-reexec: при пульсе «один поток» перезагрузка всё равно неизбежна.</p>
]]></content:encoded></item><item><title>GoToSocial молча съедал все статусы с релеев — и я нашёл почему</title><link>https://dedinit.ru/2026/gotosocial-relay-match-by-default/</link><pubDate>Sun, 13 Sep 2026 03:00:00 +0300</pubDate><guid>https://dedinit.ru/2026/gotosocial-relay-match-by-default/</guid><description>Подписка на релей выглядит рабочей: релей шлёт, нода отвечает 202 Accepted — а в базе пусто. Разбираюсь, почему: без галочки Match posts by default и пустых матчерах фильтр релеев GoToSocial по построению возвращает false, и статусы дропаются молча на уровне debug.</description><content:encoded><![CDATA[<figure class="entry-cover"><img src="https://dedinit.ru/2026/gotosocial-relay-match-by-default/hero.svg" alt="GoToSocial: релей молча съедает статусы — match_by_default"></figure><h1 id="gotosocial-молча-съедал-все-статусы-с-релеев--и-я-нашёл-почему">GoToSocial молча съедал все статусы с релеев — и я нашёл почему</h1>
<p><strong>TL;DR:</strong> Подписка на релей выглядит рабочей, релей шлёт, нода отвечает <code>202 Accepted</code> — а в базе пусто. Причина — <strong>я не поставил галочку <code>Match posts by default</code></strong>, а матчеры оставил пустыми. В таком виде фильтр релеев по построению возвращает false для всего. Статусы дропаются молча, на уровне debug. Ошибка тихая: её заслоняют громкие ошибки dereference.</p>
<h2 id="как-это-выглядит-снаружи">Как это выглядит снаружи</h2>
<p>Ты подписан на релей. Ты видишь:</p>
<ul>
<li>подписка активна, <code>approved = true</code>,</li>
<li>релей исправно шлёт Announce,</li>
<li>нода отвечает <code>202 Accepted</code>.</li>
</ul>
<p>Всё зелёное. А лента месяцами состоит из пары доменов, на которые ты подписан напрямую. Релей как будто «работает вхолостую».</p>
<h2 id="-моя-ошибка">❌ Моя ошибка</h2>
<p>Я настроил подписку так:</p>
<ul>
<li>✅ разрешил <strong>public</strong></li>
<li>✅ разрешил <strong>unlisted</strong></li>
<li>✅ запретил <strong>sensitive</strong></li>
</ul>
<p>И решил, что этого достаточно. Логика была: «я разрешил то, что хочу, и запретил то, что не хочу — значит, всё остальное будет приходить само».</p>
<p><strong>Но это не так.</strong> Разрешение public/unlisted и запрет sensitive — это <strong>фильтры видимости</strong>. Они говорят, <em>какие типы постов можно принимать</em>. Но они <strong>не дают разрешения на приём вообще</strong>. Разрешение даёт либо <code>match_by_default</code>, либо include-матчеры. У меня не было ни того, ни другого.</p>
<p><strong>Я не поставил галочку <code>Match posts by default</code>.</strong> Без неё подписка работает в режиме deny-by-default: пропускает только то, что явно разрешено матчерами. Матчеров нет → не проходит <strong>ничего</strong>.</p>
<h2 id="что-происходит-под-капотом">Что происходит под капотом</h2>
<ol>
<li>Релей шлёт <code>POST /inbox</code> → нода отвечает <code>202 Accepted</code>. <strong>Это подтверждение приёма HTTP-запроса, а не сохранения статуса.</strong></li>
<li>Нода скачивает оригинал по URI (dereference), тратит трафик и время.</li>
<li>Статус идёт в <code>relay.Filter.MatchedBySubscription</code>.</li>
<li>Нет совпадения → статус выбрасывается. В лог падает <code>dropping unpermitted status</code> — <strong>warn/debug, не error</strong>.</li>
</ol>
<h2 id="где-прячется-грабль">Где прячется грабль</h2>
<p>В таблице <code>relay_subscriptions</code> два ключевых поля: <code>flags</code> (битовая маска) и <code>matchers</code> (JSON-правила).</p>
<p>Флаги из <code>gtsmodel/relay.go</code>:</p>
<pre tabindex="0"><code>RelayFlagPublic          = 2   (принимать публичные)
RelayFlagUnlisted        = 4   (принимать скрытые)
RelayFlagMatchByDefault  = 8   (принимать всё по умолчанию)
RelayFlagIgnoreSensitive = 16  (игнорировать чувствительное)
RelayFlagIgnoreMedia     = 32  (игнорировать с медиа)
RelayFlagIgnoreReplies   = 64  (игнорировать ответы)
</code></pre><p>У меня на всех трёх подписках стояло <code>flags = 22</code>. Раскладываем: <code>16 + 4 + 2</code> = <code>IgnoreSensitive + Unlisted + Public</code>. Выглядит осмысленно, правда? «Принимаем публичные и скрытые, игнорируем чувствительное».</p>
<p>Но бита <code>MatchByDefault</code> (8) там нет. И <code>matchers = NULL</code>.</p>
<h2 id="почему-без-match_by_default-дропается-всё">Почему без match_by_default дропается всё</h2>
<p>Логика <code>matchedByConnection</code> в <code>internal/filter/relay/relay.go</code>:</p>
<ol>
<li>Видимость: public → нужен флаг Public (есть), unlisted → нужен Unlisted (есть), остальное → false.</li>
<li>Чувствительное + <code>IgnoreSensitive</code> → false (это намеренно).</li>
<li>Медиа + <code>IgnoreMedia</code> → false (не стоит).</li>
<li>Ответ не себе + <code>IgnoreReplies</code> → false (не стоит).</li>
<li>Exclude-матчеры (чёрный список). Их нет → пропускаем.</li>
<li><strong>Если стоит <code>MatchByDefault</code> → true. У меня не стоит.</strong></li>
<li>Иначе ищем include-матчеры (белый список). Их нет вообще.</li>
<li><code>return false</code>.</li>
</ol>
<p>Вот оно. Пустая подписка без матчеров — это подписка, которая <strong>не пропускает ничего</strong>. Deny-by-default. Не «не знаю», а именно «не разрешаю».</p>
<h2 id="что-такое-матчеры-и-зачем-они-нужны">Что такое матчеры и зачем они нужны</h2>
<p>Матчер — это ключевое слово, которое ищется в <strong>содержимом поста и в его content warning</strong>. Поиск регистронезависимый. Есть два режима совпадения: partial (по умолчанию, ловит часть слова) и whole word (только целое слово). Хэштеги матчатся через префикс <code>#</code>.</p>
<p>Матчеры бывают двух типов:</p>
<p><strong>Include-матчеры (белый список).</strong> Работают, когда <code>match_by_default</code> выключен. Пост пройдёт только если совпал хотя бы с одним include-матчером. Нет include-матчеров и нет <code>match_by_default</code> — не пройдёт ничего. Именно это и случилось у меня.</p>
<p><strong>Exclude-матчеры (чёрный список).</strong> Работают всегда, независимо от <code>match_by_default</code>. Если пост совпал с exclude-матчером — он дропается, даже если <code>match_by_default</code> включён.</p>
<p>Матчеры дают админу <strong>хирургический контроль</strong> вместо грубого «всё или ничего». Include — «хочу только посты про infosec и Linux». Exclude — «принимай всё, кроме спама и nsFW».</p>
<h2 id="как-диагностировать">Как диагностировать</h2>
<p>SQL:</p>
<div class="highlight"><div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">SELECT</span><span style="color:#6e7681"> </span>relay_actor_uri,<span style="color:#6e7681"> </span>flags,<span style="color:#6e7681"> </span>matchers<span style="color:#6e7681"> </span><span style="color:#ff7b72">FROM</span><span style="color:#6e7681"> </span>relay_subscriptions;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">-- flags без бита 8 и matchers = NULL → подписка не пропускает ничего
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>API:</p>
<pre tabindex="0"><code>GET /api/v1/admin/relay_subscriptions
→ смотрим match_by_default
</code></pre><h2 id="-как-чинить--и-что-надо-было-сделать-сразу">✅ Как чинить — и что надо было сделать сразу</h2>
<p>Два корректных пути при добавлении релея:</p>
<p><strong>Путь 1: «Принимать всё, кроме явных запретов»</strong> — поставить галочку <code>Match posts by default</code>. Тогда работают только exclude-матчеры и ignore-флаги. Всё, что не попало под запрет, — принимается.</p>
<p><strong>Путь 2: «Принимать только то, что я явно указал»</strong> — не ставить <code>Match posts by default</code>, но создать include-матчеры. Например, <code>infosec</code>, <code>linux</code>, <code>#GoToSocial</code>.</p>
<p>Я выбрал ни то, ни другое. Надо было поставить галочку:</p>
<pre tabindex="0"><code>PUT /api/v1/admin/relay_subscriptions/{id}
{ &#34;public&#34;: true, &#34;unlisted&#34;: true, &#34;match_by_default&#34;: true }
</code></pre><p><strong>После включения приток пошёл мгновенно:</strong> 44 статуса за 30 минут, 12+ новых доменов (infosec.exchange, mastodon.world, burningboard.net, troet.cafe, norden.social, c.im, toot.wales, social.linux.pizza…). До этого лента месяцами содержала только пару доменов прямых подписок.</p>
<h2 id="выводы">Выводы</h2>
<ol>
<li><strong>Главное правило:</strong> если хочешь «принимать всё, кроме запрещённого» — <strong>обязательно ставь галочку <code>Match posts by default</code></strong>. Без неё релей будет слать, нода будет отвечать <code>202 Accepted</code>, а лента останется пустой.</li>
<li>Разрешить public/unlisted недостаточно — это лишь фильтры видимости, а не разрешение на приём. «Подписка есть» и «релей шлёт» ≠ «контент сохраняется».</li>
<li>Дизайн фильтра разумный — безопасный дефолт. Но для админа неочевидный: UI не кричит, что подписка без матчеров мёртвая.</li>
<li>Мониторь не только error, но и warn-строки <code>dropping unpermitted</code> / <code>not relayable</code>.</li>
<li><code>202 Accepted</code> — это «запрос принят», а не «статус сохранён». Путать их — самый дешёвый способ незаметно потерять федерацию.</li>
</ol>
<hr>
<p><strong>Теги:</strong> #GoToSocial #ActivityPub #Fediverse #Relay #администрирование #грабли</p>
<p>Если у кого-то была та же тишина в ленте при живом релее — проверьте <code>match_by_default</code>. Возможно, вы тоже кормите чёрную дыру.</p>
]]></content:encoded></item></channel></rss>