<?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>DevOps on dedinit()</title><link>https://dedinit.ru/categories/devops/</link><description>Recent content in DevOps on dedinit()</description><generator>Hugo -- 0.165.0</generator><language>ru-ru</language><lastBuildDate>Mon, 31 Aug 2026 03:00:00 +0300</lastBuildDate><atom:link href="https://dedinit.ru/categories/devops/index.xml" rel="self" type="application/rss+xml"/><item><title>WEB Proxy для Telegram Desktop: ставим tproxy-server на свой VPS и чиним грабли установщика</title><link>https://dedinit.ru/2026/tproxy-web-proxy-telegram-desktop/</link><pubDate>Mon, 31 Aug 2026 03:00:00 +0300</pubDate><guid>https://dedinit.ru/2026/tproxy-web-proxy-telegram-desktop/</guid><description>Рассказываю, как развернул WEB Proxy для Telegram Desktop на собственном VPS (tproxy-server + Caddy + MTProxy), что нашёл в установщике upstream и как починил падение mtproxy.service с правами на бинарь после сборки.</description><content:encoded><![CDATA[<figure class="entry-cover"><img src="https://dedinit.ru/2026/tproxy-web-proxy-telegram-desktop/hero.svg" alt="WEB Proxy для Telegram Desktop: Caddy, tproxy-server, MTProxy"></figure><h1 id="web-proxy-для-telegram-desktop-на-своём-vps">WEB Proxy для Telegram Desktop на своём VPS</h1>
<p>В моей инфраструктуре давно крутится Telegram-трафик через SOCKS5-туннель на VPS — но у этого подхода есть минусы: SOCKS5 легко режется DPI, а клиент надо настраивать отдельно. Альтернатива от самих разработчиков Telegram Desktop — <strong>WEB Proxy</strong>: прокси, который маскируется под обычный HTTPS-сайт на 443-м порту. Такой трафик не отличить от обычного захода на сайт, а подключение настраивается в два поля в настройках клиента.</p>
<p>В этой статье — как я развернул <a href="https://github.com/telegramdesktop/tproxy-server">tproxy-server</a> на свежем VPS (Debian 13, Hostkey), какие проблемы встретил в установщике и как их обошёл. Полезно всем, кто решит поднять свой WEB Proxy.</p>
<h2 id="зачем-web-proxy-а-не-socks5">Зачем WEB Proxy, а не SOCKS5</h2>
<table>
	<thead>
			<tr>
					<th>Критерий</th>
					<th>SOCKS5-туннель</th>
					<th>WEB Proxy (tproxy-server)</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Внешний вид трафика</td>
					<td>SOCKS5 (легко детектится)</td>
					<td>Обычный HTTPS на 443</td>
			</tr>
			<tr>
					<td>Маскировка под сайт</td>
					<td>нет</td>
					<td>да (свой статический сайт)</td>
			</tr>
			<tr>
					<td>Настройка в Telegram Desktop</td>
					<td>тип прокси + адрес + порт</td>
					<td>hostname + secret</td>
			</tr>
			<tr>
					<td>Держит сам Telegram</td>
					<td>да</td>
					<td>да (через MTProxy на бэкенде)</td>
			</tr>
	</tbody>
</table>
<p>Схема работы tproxy-server:</p>
<pre tabindex="0"><code>Telegram Desktop → HTTPS:443 → Caddy → tproxy-server:8080 → MTProxy:2398 → Telegram
                                        └ admin :8081 (/readyz, /healthz)
</code></pre><p>Caddy отдаёт Let&rsquo;s Encrypt сертификат и проксирует весь трафик на tproxy-server, который сам решает: обычные запросы отдаёт сайт-маскировку, а запросы WEB Proxy — гонит в MTProxy. Снаружи это просто HTTPS-сайт.</p>
<h2 id="установка-что-делает-installsh">Установка: что делает install.sh</h2>
<p>У upstream отличный установщик — <code>deploy/install.sh</code> делает почти всё сам:</p>
<ul>
<li>ставит пакеты (ca-certificates, curl, nftables);</li>
<li>ставит <strong>Caddy 2.11.4</strong> и <strong>Go 1.26.5</strong> (если нет);</li>
<li>собирает <code>tproxy-server</code> из исходников;</li>
<li>собирает <strong>MTProxy</strong> (pinned commit <code>f36d8af</code>) в <code>/opt/MTProxy</code>;</li>
<li>создаёт systemd-сервисы: <code>tproxy-server</code>, <code>mtproxy</code>, <code>tproxy-firewall</code>, <code>caddy</code>;</li>
<li>настраивает таймер <code>refresh-mtproxy-config</code> (обновление списка серверов Telegram раз в сутки);</li>
<li>поднимает nft-правило, закрывающее порты MTProxy (2398, 8888) от внешнего мира.</li>
</ul>
<p>Запуск:</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><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">3
</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-bash" data-lang="bash"><span style="display:flex;"><span>./deploy/install.sh --hostname vps03.nixg.ru <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>  --email me@example.com --site-dir /tmp/tproxy-site <span style="color:#79c0ff">\
</span></span></span><span style="display:flex;"><span>  --secret <span style="color:#a5d6ff">&#34;</span><span style="color:#ff7b72">$(</span>cat /tmp/tproxy-secret.txt<span style="color:#ff7b72">)</span><span style="color:#a5d6ff">&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>--secret</code> — 32 hex-символа (<code>openssl rand -hex 16</code>), <code>--site-dir</code> — каталог со статическим сайтом-маскировкой.</p>
<h2 id="грабли-1-mtproxyservice-падает-с-203exec-readyz-503">Грабли №1: mtproxy.service падает с 203/EXEC, readyz 503</h2>
<p>После первого прогона установщика <code>tproxy-server</code> был active, Caddy слушал 80/443, но прокси не работал:</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></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-text" data-lang="text"><span style="display:flex;"><span>● mtproxy.service - failed (Result: exit-code), status=203/EXEC
</span></span></code></pre></td></tr></table>
</div>
</div><p>Порт 2398 не слушался, а <code>/readyz</code> административного эндпоинта отдавал 503 (readyz — это «вся цепочка готова», healthz при этом был 200).</p>
<p>Причина оказалась в <strong>правах на бинарь после сборки</strong>. Установщик собирает MTProxy в <code>/opt/MTProxy/objs/bin/mtproto-proxy</code>, но из-за umask бинарь и каталоги получают права <code>700 root:root</code>. Юнит <code>mtproxy.service</code> запускается от пользователя <code>mtproxy</code> (с <code>NoNewPrivileges=true</code> и <code>ProtectSystem=strict</code>) — процесс не может прочитать бинарь и умирает с 203/EXEC.</p>
<p>Лечится одной серией команд:</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><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">3
</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">4
</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-bash" data-lang="bash"><span style="display:flex;"><span>chmod <span style="color:#a5d6ff">755</span> /opt/MTProxy /opt/MTProxy/objs /opt/MTProxy/objs/bin
</span></span><span style="display:flex;"><span>chown root:mtproxy /opt/MTProxy/objs/bin/mtproto-proxy
</span></span><span style="display:flex;"><span>chmod <span style="color:#a5d6ff">750</span> /opt/MTProxy/objs/bin/mtproto-proxy
</span></span><span style="display:flex;"><span>systemctl restart mtproxy
</span></span></code></pre></td></tr></table>
</div>
</div><p>После этого <code>mtproxy</code> — active (running), слушает <code>0.0.0.0:2398</code>, readyz → 200.</p>
<h2 id="грабли-2-флейк-go-test-в-установщике">Грабли №2: флейк go test в установщике</h2>
<p>Первый прогон install.sh (холодный кэш Go, сборка всех пакетов параллельно) упал на тестах:</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-text" data-lang="text"><span style="display:flex;"><span>FAIL: internal/config — TestLoadAcceptsSystemdCredentialReadPermissions:
</span></span><span style="display:flex;"><span>group/other-readable profiles file outside a credential directory was accepted
</span></span></code></pre></td></tr></table>
</div>
</div><p>А повторный <code>go test ./...</code> (тёплый кэш) — PASS. Это классическая гонка при холодном параллельном прогоне тестов, а не баг кода. Лечение простое: перезапустить install.sh — дальше идёт нормально.</p>
<h2 id="грабли-3-не-открыты-порты-80443--ложная-тревога">Грабли №3: «не открыты порты 80/443» — ложная тревога</h2>
<p>Снаружи curl на 80/443 не отвечал, и я заподозрил firewall провайдера (Hostkey). Пошёл в панель — а там <strong>вообще нет настроек firewall/прокси</strong>. Полез в документацию, искал API&hellip;</p>
<p>На самом деле всё проще: порты у провайдера открыты по умолчанию, просто на 80/443 <strong>ничего не слушало</strong> — curl получал connection refused. Проверка: поднимаю временный <code>python3 -m http.server 80</code> → TCP connect снаружи OK. После установки Caddy сам слушает 80/443.</p>
<p>Так что если у вас после установки снаружи не открывается сайт — сначала проверьте, что порт слушается (<code>ss -tlnp</code>), а не ищите firewall в панели провайдера.</p>
<h2 id="проверка-после-установки">Проверка после установки</h2>
<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><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">3
</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">4
</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">5
</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">6
</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-bash" data-lang="bash"><span style="display:flex;"><span>systemctl status tproxy-server mtproxy tproxy-firewall caddy   <span style="color:#8b949e;font-style:italic"># все active</span>
</span></span><span style="display:flex;"><span>curl -s -o /dev/null -w <span style="color:#a5d6ff">&#39;%{http_code}&#39;</span> http://127.0.0.1:8081/readyz    <span style="color:#8b949e;font-style:italic"># 200</span>
</span></span><span style="display:flex;"><span>curl -s -o /dev/null -w <span style="color:#a5d6ff">&#39;%{http_code}&#39;</span> https://vps03.nixg.ru/          <span style="color:#8b949e;font-style:italic"># 200 (сайт)</span>
</span></span><span style="display:flex;"><span>openssl s_client -connect vps03.nixg.ru:443 -servername vps03.nixg.ru  <span style="color:#8b949e;font-style:italic"># LE-серт</span>
</span></span><span style="display:flex;"><span>ss -tlnp | grep -E <span style="color:#a5d6ff">&#39;:80 |:443|:2398|:8080|:8081&#39;</span>
</span></span><span style="display:flex;"><span>nft list ruleset | grep -E <span style="color:#a5d6ff">&#39;2398|8888&#39;</span>   <span style="color:#8b949e;font-style:italic"># закрыто от внешнего мира</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Наружу видны только 80/443. MTProxy-бэкенд (2398, 8888) закрыт nft — <code>iifname != &quot;lo&quot; ... drop</code>, то есть бэкенд не светится и не принимает подключения напрямую.</p>
<h2 id="настройка-клиента">Настройка клиента</h2>
<p>В Telegram Desktop: <strong>Settings → Advanced → Connection Type → Use custom proxy → NEW PROXY → WEB Proxy</strong>:</p>
<ul>
<li>hostname: <code>vps03.nixg.ru</code></li>
<li>secret: (32 hex-символа, которые вы сгенерировали)</li>
</ul>
<h2 id="итог">Итог</h2>
<p>WEB Proxy — отличная замена SOCKS5-туннелю, если нужна маскировка под обычный HTTPS: настраивается в два поля, трафик неотличим от сайта, а готовый установщик upstream делает почти всю работу. Главные грабли — права на бинарь MTProxy после сборки (203/EXEC) и ложная тревога с «закрытыми портами». Оба решаются за пару минут, если знать, куда смотреть.</p>
<p>Полный набор файлов (заметки, сайт-маскировку, команду установки) я сложил в репозиторий <a href="https://github.com/telegramdesktop/tproxy-server">tproxy-web</a> — точнее, в свой <a href="https://gitverse.ru/kpa39l/tproxy-web">tproxy-web на gitverse</a>, а INSTALL_NOTES.md с деталями лежит там же.</p>
]]></content:encoded></item><item><title>Как я перевёл aux-задачи Hermes на локальные модели: экономия токенов и отключение режима размышлений у Qwen3</title><link>https://dedinit.ru/2026/local-aux-models-hermes-qwen3/</link><pubDate>Sun, 30 Aug 2026 12:00:00 +0300</pubDate><guid>https://dedinit.ru/2026/local-aux-models-hermes-qwen3/</guid><description>Рассказываю, как выбрал модель для вспомогательных (auxiliary) задач агента Hermes, перевёл vision и текстовые задачи на локальную Ollama, сэкономил токены облачного провайдера и допилил отключение режима размышлений (thinking) у Qwen3.</description><content:encoded><![CDATA[<figure class="entry-cover"><img src="https://dedinit.ru/2026/local-aux-models-hermes-qwen3/hero.svg" alt="Локальные aux-модели Hermes: qwen3-vl и qwen3"></figure><h1 id="как-я-перевёл-aux-задачи-hermes-на-локальные-модели">Как я перевёл aux-задачи Hermes на локальные модели</h1>
<p>У меня на домашнем сервере живёт агент <a href="https://hermes-agent.nousresearch.com">Hermes</a> — он ведёт блог, следит за сертификатами, ходит в мессенджеры и выполняет кучу мелких, но важных задач. Основную модель он берёт в облаке (провайдер polza.ai, 131K контекст), а для вспомогательных задач — генерации заголовков, извлечения текста из веб-страниц, описания картинок (vision) и сжатия контекста — у него есть отдельный механизм <strong>auxiliary</strong>. По умолчанию все aux-задачи тоже резолвятся в облако, а значит — тратят токены и деньги.</p>
<p>На сервере стоит видеокарта <strong>RTX 5060 Ti 16 GB</strong> (да, та самая, про которую я писал в статье про зависание Ubuntu при загрузке). Раз есть GPU — почему бы не увести aux-задачи на локальные модели? Приватность, скорость, ноль затрат на API. Рассказываю, как выбирал модель, как настраивал и какой грабли встретил при отключении режима размышлений (thinking) у Qwen3.</p>
<h2 id="что-такое-auxiliary-задачи-в-hermes">Что такое auxiliary-задачи в Hermes</h2>
<p>Hermes — агент с модульной архитектурой. Кроме основной модели (которая думает и пишет ответы), он вызывает <strong>вспомогательные (auxiliary) задачи</strong> через отдельный клиент — <code>auxiliary_client.py</code>. В моей версии их несколько:</p>
<table>
	<thead>
			<tr>
					<th>Задача</th>
					<th>Что делает</th>
					<th>Порог контекста</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>vision</code></td>
					<td>Описывает картинки, читает скриншоты</td>
					<td>нет</td>
			</tr>
			<tr>
					<td><code>title_generation</code></td>
					<td>Генерирует заголовки диалогов</td>
					<td>нет</td>
			</tr>
			<tr>
					<td><code>web_extract</code></td>
					<td>Извлекает текст из веб-страниц</td>
					<td>нет</td>
			</tr>
			<tr>
					<td><code>compression</code></td>
					<td>Сжимает историю диалога (context compaction)</td>
					<td><strong>64K жёсткий минимум</strong></td>
			</tr>
			<tr>
					<td><code>skills_hub</code>, <code>approval</code>, <code>mcp</code> и др.</td>
					<td>Служебные</td>
					<td>нет</td>
			</tr>
	</tbody>
</table>
<p>Ключевой момент: <strong>только <code>compression</code> требует большого контекста (64K)</strong>. Это жёсткое требование самого Hermes — для сжатия истории ему нужна модель, которая видит весь диалог. Локальные GGUF-модели до 64K не дотягивают (проверил: qwen3-8b упирается в 40960 токенов контекста, qwen2.5-coder-14b — в 32768; теги «64k/65k» в названиях моделей — маркетинг, реальный контекст меньше). Поэтому <strong>compression я оставил в облаке</strong>, а вот все остальные aux-задачи — кандидаты на локализацию.</p>
<h2 id="выбор-модели">Выбор модели</h2>
<p>Критерии были простые:</p>
<ol>
<li>Модель должна уметь <strong>vision</strong> (для <code>vision</code>-задачи).</li>
<li>Должна влезать в 16 GB VRAM вместе с эмбеддинг-моделью <code>bge-m3</code> и основной текст-моделью.</li>
<li>Должна быстро отвечать — aux-задачи вызываются часто, ждать минуту утомительно.</li>
<li>Желательно уметь закрыть и текстовые задачи (генерация заголовков, извлечение текста) — чтобы не гонять облако вообще.</li>
</ol>
<p>Я поставил <a href="https://ollama.com/library/qwen3-vl">qwen3-vl:8b</a> — мультимодальную модель Qwen 8B с поддержкой vision. Она весит ~6.1 GB, контекст 262K (но KV cache на полный контекст — ~309 GB, так что реально держу <code>num_ctx=8192</code>). Проверил через <code>/api/show</code> — у неё есть capability <code>vision</code>, значит Hermes её увидит как vision-модель.</p>
<p><strong>Важный нюанс про qwen3-vl:</strong> она великолепно описывает картинки (проверено E2E — отвечает «red»/«blue» на цветные картинки за 1-10 секунд), но для текстовых задач она&hellip; слишком умная. На простой вопрос «какой сегодня день недели» она генерирует 3778 токенов рассуждений и отвечает через 64 секунды! Это потому, что qwen3-модели по умолчанию включён режим reasoning/thinking. Для заголовков и извлечения текста это неприемлемо.</p>
<p>Поэтому я взял <strong>две модели</strong>:</p>
<ul>
<li><code>qwen3-vl:8b</code> — для vision (картинки);</li>
<li><code>qwen3:8b</code> — для текстовых aux (title_generation, web_extract), с принудительно отключённым thinking.</li>
</ul>
<h2 id="настройка">Настройка</h2>
<p>Hermes поддерживает per-task конфигурацию auxiliary-задач в <code>config.yaml</code>. Формат:</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><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"> 3
</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"> 4
</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"> 5
</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"> 6
</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"> 7
</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"> 8
</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"> 9
</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">10
</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">11
</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">12
</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">13
</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">14
</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">15
</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">16
</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">17
</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">18
</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">19
</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">20
</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-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7ee787">auxiliary</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">vision</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">provider</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">custom</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">model</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">qwen3-vl:8b</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">base_url</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">http://localhost:11434/v1</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">api_key</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">ollama</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">title_generation</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">provider</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">custom</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">model</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">qwen3:8b</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">base_url</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">http://localhost:11434/v1</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">api_key</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">ollama</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">extra_body</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">think</span>:<span style="color:#6e7681"> </span><span style="color:#79c0ff">false</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">web_extract</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">provider</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">custom</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">model</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">qwen3:8b</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">base_url</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">http://localhost:11434/v1</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">api_key</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">ollama</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">extra_body</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">think</span>:<span style="color:#6e7681"> </span><span style="color:#79c0ff">false</span><span style="color:#6e7681">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Настройка через CLI:</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><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">3
</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">4
</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">5
</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-bash" data-lang="bash"><span style="display:flex;"><span>hermes config set auxiliary.vision.provider custom
</span></span><span style="display:flex;"><span>hermes config set auxiliary.vision.model qwen3-vl:8b
</span></span><span style="display:flex;"><span>hermes config set auxiliary.vision.base_url http://localhost:11434/v1
</span></span><span style="display:flex;"><span>hermes config set auxiliary.vision.api_key ollama
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># и так для каждой задачи</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Пара важных граблей:</p>
<ul>
<li><strong><code>api_key</code> обязан быть непустым.</strong> В коде Hermes пустой <code>api_key</code> означает «унаследовать ключ основного провайдера» — и тогда он попытается сходить в polza.ai с локальной моделью. Я поставил заглушку <code>ollama</code> (Ollama её не проверяет).</li>
<li><strong>Ollama надо форматировать через OpenAI-совместимый эндпоинт</strong> <code>/v1/chat/completions</code> — именно так Hermes ходит в локальные модели.</li>
<li><strong>Для qwen3-vl держать <code>num_ctx</code> небольшим</strong> (я использую 8192), иначе Ollama пытается зарезервировать KV cache под весь 262K контекст — это ~309 GB, runner падает с EOF.</li>
</ul>
<h2 id="допиливание-отключения-режима-размышлений-thinking">Допиливание отключения режима размышлений (thinking)</h2>
<p>Самая интересная часть. Qwen3-модели (и qwen3, и qwen3-vl) по умолчанию генерируют <strong>скрытый поток рассуждений (thinking/reasoning)</strong> перед ответом. Для aux-задач это означает:</p>
<ul>
<li>Заголовок генерируется 64 секунды вместо 3-5.</li>
<li>При ограниченном <code>max_tokens</code> ответ вообще <strong>пустой</strong> — весь бюджет съедает thinking, до <code>content</code> дело не доходит.</li>
</ul>
<p>Я перепробовал несколько способов отключить thinking, и вот что выяснилось:</p>
<h3 id="что-не-работает-в-ollama">Что НЕ работает (в Ollama)</h3>
<table>
	<thead>
			<tr>
					<th>Способ</th>
					<th>Результат</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>/no_think</code> в начале сообщения</td>
					<td>Не работает — модель всё равно думает</td>
			</tr>
			<tr>
					<td><code>enable_thinking: false</code> в <code>extra_body</code></td>
					<td>Не работает</td>
			</tr>
			<tr>
					<td><code>thinking: {type: disabled}</code> (как в OpenAI API Qwen)</td>
					<td>Не работает — content пустой, reasoning продолжается</td>
			</tr>
			<tr>
					<td><code>system: &quot;Не думай&quot;</code></td>
					<td>Не работает</td>
			</tr>
	</tbody>
</table>
<p>Я проверил chat template модели — там есть команды <code>/think</code> и <code>/no_think</code>, но они не активируются в Ollama (флаг <code>IsThinkSet</code> не выставляется).</p>
<h3 id="что-работает">Что работает</h3>
<p><strong>Единственный рабочий способ — корневой параметр <code>think: false</code> в теле запроса:</strong></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><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">3
</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">4
</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">5
</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-bash" data-lang="bash"><span style="display:flex;"><span>curl http://localhost:11434/api/chat -d <span style="color:#a5d6ff">&#39;{
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">  &#34;model&#34;: &#34;qwen3:8b&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">  &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Привет!&#34;}],
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">  &#34;think&#34;: false
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">}&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Ответ приходит через <strong>0.7 секунды</strong> (вместо 64!), <code>reasoning=false</code>, <code>content</code> заполнен. Именно эту опцию я и прописал в <code>extra_body</code> конфига Hermes:</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-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7ee787">extra_body</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">think</span>:<span style="color:#6e7681"> </span><span style="color:#79c0ff">false</span><span style="color:#6e7681">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>В коде Hermes <code>extra_body</code> уходит в корень JSON-тела запроса — как раз туда, куда Ollama ждёт параметр <code>think</code>.</p>
<h3 id="проверка-e2e">Проверка E2E</h3>
<p>После настройки прогнал реальные вызовы через вспомогательный клиент Hermes (<code>call_llm</code>):</p>
<table>
	<thead>
			<tr>
					<th>Задача</th>
					<th>Время</th>
					<th>Результат</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>title_generation</code></td>
					<td>11.3 сек</td>
					<td>«Настройка Ollama: локальные модели»</td>
			</tr>
			<tr>
					<td><code>web_extract</code></td>
					<td>7.5 сек</td>
					<td>Извлёк главную мысль</td>
			</tr>
			<tr>
					<td><code>vision</code> (картинка 256x256)</td>
					<td>7.6 сек</td>
					<td>«blue»</td>
			</tr>
	</tbody>
</table>
<h2 id="vram-и-память">VRAM и память</h2>
<p>На RTX 5060 Ti 16 GB в памяти держатся одновременно:</p>
<ul>
<li><code>qwen3-vl:8b</code> — ~7.2 GB (vision)</li>
<li><code>qwen3:8b</code> — ~6.1 GB (текст)</li>
<li><code>bge-m3</code> — ~1.2 GB (эмбеддинги)</li>
</ul>
<p>Итого ~14.5 GB — влезает с запасом, и переключение vision↔текст происходит без выгрузки модели. Проверено через <code>/api/ps</code>: после текстового вызова в памяти остаются обе модели.</p>
<h2 id="итог">Итог</h2>
<ul>
<li><strong>Vision</strong> полностью локальный — картинки описываются на сервере, в облако не уходят.</li>
<li><strong>Текстовые aux</strong> (заголовки, извлечение текста) — локальные, быстрые, бесплатные.</li>
<li><strong>Compression</strong> остался в облаке — ему нужен контекст 64K, который локально не влезает.</li>
<li>Облачные токены тратятся только на основную модель и сжатие контекста — экономия существенная.</li>
</ul>
<p>Самое ценное знание, которым делюсь: <strong>если вы используете Qwen3 в Ollama для вспомогательных задач — не забудьте <code>think: false</code></strong>. Это превращает задумчивую модель, которая час думает над заголовком, в мгновенного исполнителя. И помните: <code>/no_think</code>, <code>enable_thinking</code> и <code>thinking: disabled</code> — в Ollama не работают, работает только корневой <code>think: false</code>.</p>
<hr>
<p><em>Полезные ссылки: <a href="https://hermes-agent.nousresearch.com">Hermes Agent</a> · <a href="https://ollama.com/library/qwen3-vl">Ollama qwen3-vl</a> · <a href="https://ollama.com/library/qwen3">Ollama qwen3</a></em></p>
]]></content:encoded></item><item><title>Факап с конфиге WG привел к потере 6 часов жизни</title><link>https://dedinit.ru/2026/20260503-dns-routing/</link><pubDate>Sun, 03 May 2026 01:26:20 +0300</pubDate><guid>https://dedinit.ru/2026/20260503-dns-routing/</guid><description>&lt;p&gt;Расскажу про то как один параметр в кофиге Wireguard причинил мне опыт по разбору половины сетевых настроек Ubuntu. Один с виду безобидный параметр&amp;hellip; IaC блин нужен обязательно. Я просто забыл что и для чего менял, а потом уже поздно было вспоминать.&lt;/p&gt;
&lt;h2 id="симптомы-тишина-в-эфире-и-гора-dropped-пакетов"&gt;Симптомы: тишина в эфире и гора dropped пакетов&lt;/h2&gt;
&lt;p&gt;Всё началось с того, полсе смерти системного диска в моем домашнем сервере, я перестал крутить его 24/7 и набегами по выходным конифгурировал его, пытаясь вернуть привычный набор сервисом. И вот однажды включаю я сервер, а у меня проблема с системным резолвом DNS. Так-то напрямую &lt;code&gt;nslookup ya.ru 8.8.8.8&lt;/code&gt; выдает всё нормально. но система ни в какую не хочет резолвить адреса. Так-то я забыл, что неделей ранее я настраивал Wireguard со своими VPS, и что именно после настройки Wireguard сервер потерял доступ к локальной сети и интернету. начал ковырять, ИИшка подсказала что на Ubuntu надо netplan копать, начал фигачить yamlики, тут время вышло и я еще на неделю забросил комп. И вот вчера, сейчас далеко за полночь, я его включил и опять начал разбираться, естественно забыл не только то что я две недели назад делал, но и то чем неделю назад занимался. И вот картинка, сервер загрузился, подозрительно долго грузился кстати, но по сети недоступен.&lt;/p&gt;</description><content:encoded><![CDATA[<figure class="entry-cover"><img src="https://dedinit.ru/2026/20260503-dns-routing/hero.svg" alt="Факап с конфиге WG привел к потере 6 часов жизни"></figure><p>Расскажу про то как один параметр в кофиге Wireguard причинил мне опыт по разбору половины сетевых настроек Ubuntu. Один с виду безобидный параметр&hellip; IaC блин нужен обязательно. Я просто забыл что и для чего менял, а потом уже поздно было вспоминать.</p>
<h2 id="симптомы-тишина-в-эфире-и-гора-dropped-пакетов">Симптомы: тишина в эфире и гора dropped пакетов</h2>
<p>Всё началось с того, полсе смерти системного диска в моем домашнем сервере, я перестал крутить его 24/7 и набегами по выходным конифгурировал его, пытаясь вернуть  привычный набор сервисом. И вот однажды включаю я сервер, а у меня проблема с системным резолвом DNS. Так-то напрямую <code>nslookup ya.ru 8.8.8.8</code> выдает всё нормально. но система ни в какую не хочет резолвить адреса. Так-то я забыл, что неделей ранее я настраивал Wireguard со своими VPS, и что именно после настройки Wireguard сервер  потерял доступ к локальной сети и интернету. начал ковырять, ИИшка подсказала что на Ubuntu надо netplan копать, начал фигачить yamlики, тут время вышло и я еще на неделю забросил комп. И вот вчера, сейчас далеко за полночь, я его включил и опять начал разбираться, естественно забыл не только то что я две недели назад делал, но и то чем неделю назад занимался. И вот картинка, сервер загрузился, подозрительно долго грузился кстати, но по сети недоступен.</p>
<p>Первая диагностика через <code>ip -s link show</code> показала тревожную картину на интерфейсе <code>eno1</code>:</p>
<ul>
<li><strong>RX:</strong> 53 000+ пакетов принято.</li>
<li><strong>Dropped:</strong> 21 000+ пакетов отброшено.</li>
<li><strong>TX:</strong> Всего 40 пакетов отправлено.</li>
</ul>
<p>Команда <code>ip route show</code> выводила только маршрут для интерфейса <code>wg0</code>. Маршрута по умолчанию через физический интерфейс не было. Система «не видела» шлюза.</p>
<p>Команда <code>ip a</code> показала, что на всех интерфейсах, кроме loopback, ip-адреса отсутствуют.</p>
<p>Попытки перезапустить службы или применить конфигурацию через <code>sudo netplan apply</code> не давали результата. Интерфейс зависал в состоянии <code>configuring</code>, а адрес не присваивался.</p>
<h2 id="расследование-кто-виноват">Расследование: кто виноват?</h2>
<h3 id="1-конфликт-маршрутизации">1. Конфликт маршрутизации</h3>
<p>Первоначальная гипотеза была в том, что WireGuard перехватывает весь трафик. Однако в конфиге <code>wg0.conf</code> параметр <code>AllowedIPs</code> был ограничен подсетью туннеля (<code>10.8.0.0/24</code>). Он не должен был блокировать локальную сеть.</p>
<p>Проблема оказалась глубже: отсутствие маршрута по умолчанию для <code>eno1</code> означало, что ядро просто не знало, куда девать исходящие пакеты, кроме как в туннель (если бы он был активен) или в никуда.</p>
<h3 id="2-почему-dhcp-молчал">2. Почему DHCP молчал?</h3>
<p>Ubuntu 24.04 использует стек <code>systemd-networkd</code> для управления сетью на серверных сборках. Конфигурация задается через <code>Netplan</code> (YAML-файлы).</p>
<p>Проверка статуса показала:</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-bash" data-lang="bash"><span style="display:flex;"><span>networkctl status eno1
</span></span><span style="display:flex;"><span>State: routable <span style="color:#ff7b72;font-weight:bold">(</span>configuring<span style="color:#ff7b72;font-weight:bold">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Статус <code>configuring</code> в сочетании с <code>routable</code> — это классический признак того, что демон ждет завершения какой-то операции. В логах <code>journalctl -u systemd-networkd</code> не было ошибок получения адреса IPv4, зато постоянно терялась аренда IPv6 (<code>DHCPv6 lease lost</code>).</p>
<p>Оказалось, что <code>systemd-networkd</code> по умолчанию пытается настроить и IPv4, и IPv6. Если сервер DHCPv6 не отвечает или есть проблемы с Router Advertisements (RA), демон может зависнуть в ожидании, блокируя переход интерфейса в полностью рабочее состояние для IPv4.</p>
<h3 id="3-долгая-загрузка">3. Долгая загрузка</h3>
<p>При перезагрузке я заметил, что система висит на этапе <code>Job systemd-networkd-wait-online.service</code>. Однако служба <code>wait-online</code> держала систему, пока сеть не поднимется. Поскольку сеть не могла подняться из-за зависшего DHCP-клиента, загрузка затягивалась.</p>
<h2 id="решение-поэтапный-демонтаж-проблем">Решение: поэтапный демонтаж проблем</h2>
<h3 id="шаг-1-отключение-ожидания-сети">Шаг 1. Отключение ожидания сети</h3>
<p>Чтобы система загружалась быстро, даже если сеть сбоит, отключил службу ожидания:</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl disable systemd-networkd-wait-online.service
</span></span><span style="display:flex;"><span>sudo systemctl mask systemd-networkd-wait-online.service
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="шаг-2-отключение-ipv6">Шаг 2. Отключение IPv6</h3>
<p>Возможно проблема долгой загрузки была в ожидании ответов IPv6, а поскольку в моей инфраструктуре он пока не критичен, то я отключил его на уровне ядра. Это сузило  площадь ошибок в конфигурации <code>systemd-networkd</code>, я сосредоточился только на IPv4.</p>
<p>В <code>/etc/sysctl.conf</code> добавил:</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><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">3
</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-ini" data-lang="ini"><span style="display:flex;"><span>net.ipv6.conf.all.disable_ipv6<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>net.ipv6.conf.default.disable_ipv6<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>net.ipv6.conf.lo.disable_ipv6<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">1</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>И применил изменения: <code>sudo sysctl -p</code>.</p>
<h3 id="шаг-3-переход-на-прямую-конфигурацию-systemd-networkd">Шаг 3. Переход на прямую конфигурацию systemd-networkd</h3>
<p>Файлы Netplan (<code>/etc/netplan/*.yaml</code>) генерируют конфиги для бэкенда. У меня их было два, и они могли конфликтовать или содержать избыточные параметры, тем более я уже и не помнил как и почему я их именно так писал. Изолировал прослойку Netplan, создав файл-заглушку <code>/etc/systemd/network/10-eno1.network</code> с настройками для  <code>systemd-networkd</code>:</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><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">3
</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">4
</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">5
</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">6
</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">7
</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">8
</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-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#ff7b72">[Match]</span>
</span></span><span style="display:flex;"><span>Name<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">eno1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">[Network]</span>
</span></span><span style="display:flex;"><span>DHCP<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">ipv4</span>
</span></span><span style="display:flex;"><span>IPv6AcceptRA<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">no</span>
</span></span><span style="display:flex;"><span>DNS<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">1.1.1.1</span>
</span></span><span style="display:flex;"><span>DNS<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">8.8.8.8</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Ключевой момент здесь — <code>DHCP=ipv4</code>. Тут явно говорим демону: «Используй только IPv4, игнорируй IPv6». Параметр <code>IPv6AcceptRA=no</code> дополнительно страхует от ожидания сообщений роутера.</p>
<p>Удалил старые файлы из <code>/etc/netplan/</code>, чтобы избежать двойного применения настроек, и перезапустил службу:</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></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-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl restart systemd-networkd
</span></span></code></pre></td></tr></table>
</div>
</div><p>Интерфейс сразу получил адрес <code>192.168.1.3</code> и перешел в статус <code>routable (configured)</code>. Системный резолв DNS заработал.</p>
<h3 id="шаг-4-финальный-босс-dns-и-wireguard">Шаг 4. Финальный босс: DNS и WireGuard</h3>
<p>Пинги пошли, но <code>nslookup ya.ru</code> выдавал таймауты на <code>127.0.0.53</code> (локальный stub-resolver <code>systemd-resolved</code>). Вот про эту штуку я не знал, поэтому теперь знаю что не нужно поднимать на каждом сервер unbound, в systemd все есть из коробки.</p>
<p>Проверка <code>resolvectl status</code> показала странность:</p>
<ul>
<li>Link 2 (eno1): DNS Servers: 1.1.1.1, 8.8.8.8</li>
<li>Link 5 (wg0): <strong>DNS Domain: ~.</strong></li>
</ul>
<p>Правда я её не заметил сперва, но консультации с ИИ не всегда являются потерей времени. Символ <code>~.</code> означает «глобальный поиск». WireGuard, увидев в своем конфиге строку <code>DNS = 1.1.1.1</code>, автоматически сообщил системе, что этот DNS-сервер должен обрабатывать <strong>все</strong> запросы, перекрывая настройки физического интерфейса. Но так как туннель до собственных серверов и маршрутов до внешних DNS в нём нет, разрешения имен не работали.</p>
<p><strong>Решение:</strong>
В файле <code>/etc/wireguard/wg0.conf</code> я закомментировал всего лишь одну строку, как потом выяснилось добавленной в конфиг по рекомендаци такой же ИИшечки:</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></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-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># DNS = 1.1.1.1</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Переподнял туннель:</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo wg-quick down wg0
</span></span><span style="display:flex;"><span>sudo wg-quick up wg0
</span></span></code></pre></td></tr></table>
</div>
</div><p>Теперь <code>wg0</code> имеетв выводк <code>networkctl status eno1</code> <code>Current Scopes: none</code>, а все DNS-запросы идут через основной интерфейс <code>eno1</code> на публичные серверы Cloudflare и Google.</p>
<h2 id="итоги-и-выводы">Итоги и выводы</h2>
<ol>
<li><strong>WireGuard и DNS:</strong> Параметр <code>DNS</code> в конфиге WireGuard — это не просто рекомендация, а команда для системы изменить <strong>глобальные</strong> настройки резолвинга. Если ты не хочешь, чтобы туннель перехватывал все DNS-запросы, не указывай этот параметр в конфиге клиента, либо используйте более тонкие настройки <code>Domains</code> (если клиент поддерживает).</li>
<li><strong>Ubuntu 24.04 и IPv6:</strong> По умолчанию включенный IPv6 может вызывать задержки при получении адреса IPv4, если инфраструктура не готова к v6. В домашних лабораториях его часто проще отключить, чем дебажить RA и DHCPv6.</li>
<li><strong>Netplan vs systemd-networkd:</strong> Netplan удобен, но иногда прямая конфигурация <code>systemd-networkd</code> дает больше прозрачности и контроля, особенно при отладке сложных случаев с DHCP.</li>
<li><strong>systemd-networkd-wait-online:</strong> На серверах, где сеть может падать или долго подниматься, эту службу лучше маскировать, иначе она будет тормозить загрузку всей ОС.</li>
</ol>
<p>Эта ошибка в конфиге WG стоила мне нескольких часов, но теперь моя домашняя лаборатория работает стабильно, а конфиги приведены к минимальному и понятному виду, а я узнал еще что-то новенькое про любимый Линукс.</p>
<p>(Ссылка на диалог с Qwen)[https://chat.qwen.ai/s/0cfbc11b-dae3-4dba-bfaf-b0788172d07c?fev=0.2.45]</p>
]]></content:encoded></item><item><title>Ansible в Windows 10 через WSL2: Debian, uv, VHDX и удобный рабочий процесс</title><link>https://dedinit.ru/2026/ansible-controller-on-win10/</link><pubDate>Sun, 26 Apr 2026 04:58:44 -0400</pubDate><guid>https://dedinit.ru/2026/ansible-controller-on-win10/</guid><description>&lt;p&gt;На домашней машине у меня всё еще Winodws 10 стоит(домашние предпочитают удобные цепи закрытого софта вместо свободы), но доставать ноут каждый раз когда нужно управлять инфраструктурой через Ansible бывает сложно, самый практичный путь — поставить Debian в WSL2 и работать уже внутри него. Такой сценарий дает полноценную Linux-среду, не требует отдельной виртуальной машины и удобно бэкапится целиком через wsl &amp;ndash;export в формат VHDX.&lt;/p&gt;
&lt;p&gt;Ниже — полностью рабочая схема без Microsoft Store: ручная установка WSL2, импорт Debian, настройка uv, установка ansible-core, маппинг проекта на D:\ansible и резервное копирование через VHDX.&lt;/p&gt;</description><content:encoded><![CDATA[<figure class="entry-cover"><img src="https://dedinit.ru/2026/ansible-controller-on-win10/hero.svg" alt="Ansible в Windows 10 через WSL2"></figure><p>На домашней машине у меня всё еще Winodws 10 стоит(домашние предпочитают удобные цепи закрытого софта вместо свободы), но доставать ноут каждый раз когда нужно управлять инфраструктурой через Ansible бывает сложно, самый практичный путь — поставить Debian в WSL2 и работать уже внутри него. Такой сценарий дает полноценную Linux-среду, не требует отдельной виртуальной машины и удобно бэкапится целиком через wsl &ndash;export в формат VHDX.</p>
<p>Ниже — полностью рабочая схема без Microsoft Store: ручная установка WSL2, импорт Debian, настройка uv, установка ansible-core, маппинг проекта на D:\ansible и резервное копирование через VHDX.</p>
<h2 id="что-понадобится">Что понадобится</h2>
<p>Для WSL2 на Windows 10 нужна версия 2004 и сборка 19041 или новее, а также включенная аппаратная виртуализация в BIOS/UEFI. Установка WSL и Debian выполняется из PowerShell от имени администратора.</p>
<ul>
<li>Windows 10 с поддержкой WSL2.</li>
<li>PowerShell с правами администратора.</li>
<li>Доступ к интернету для загрузки компонентов и пакетов.</li>
<li>Папка для WSL, например D:\WSL\Debian.</li>
<li>Папка для проектов, например D:\ansible.</li>
</ul>
<h2 id="включаем-wsl2-вручную">Включаем WSL2 вручную</h2>
<p>Если Microsoft Store недоступен или вы не хотите им пользоваться, включите компоненты Windows вручную:</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-powershell" data-lang="powershell"><span style="display:flex;"><span>dism.<span style="color:#79c0ff">exe</span> /online /enable-feature /featurename<span style="color:#f85149">:</span>Microsoft-Windows-Subsystem-Linux /all /norestart
</span></span><span style="display:flex;"><span>dism.<span style="color:#79c0ff">exe</span> /online /enable-feature /featurename<span style="color:#f85149">:</span>VirtualMachinePlatform /all /norestart
</span></span></code></pre></td></tr></table>
</div>
</div><p>После этого перезагрузи ПК. Эти два компонента нужны для работы WSL2, потому что дистрибутив запускается внутри легковесной виртуальной машины.</p>
<p>Затем задаём WSL2 как версию по умолчанию:</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></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-powershell" data-lang="powershell"><span style="display:flex;"><span>wsl --set-default-version <span style="color:#a5d6ff">2</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="устанавливаем-debian-без-store">Устанавливаем Debian без Store</h2>
<p>Для установки Debian без Microsoft Store удобнее использовать импорт rootfs-архива или готового VHDX-образа. Важно не искать случайные архивы по форумам, а брать Debian из официального источника или собирать rootfs на основе официального образа Debian.</p>
<p>На практике удобны два пути:</p>
<p>скачать официальный Debian-образ и подготовить из него rootfs;</p>
<p>либо сразу импортировать уже готовый rootfs-архив, если он у вас есть.</p>
<p>Пример импорта:</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-powershell" data-lang="powershell"><span style="display:flex;"><span>mkdir D:\WSL\Debian
</span></span><span style="display:flex;"><span>wsl --import Debian D:\WSL\Debian D:\Downloads\debian-rootfs.<span style="color:#79c0ff">tar</span> --version <span style="color:#a5d6ff">2</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>После этого запускаем Debian так:</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></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-powershell" data-lang="powershell"><span style="display:flex;"><span>wsl -d Debian
</span></span></code></pre></td></tr></table>
</div>
</div><p>Если Debian уже был установлен из Store, его можно перенести в управляемый каталог через экспорт и повторный импорт, что особенно удобно для бэкапа и миграции.</p>
<h2 id="перенос-debian-из-store">Перенос Debian из Store</h2>
<p>Сценарий переноса простой: экспортируем текущий дистрибутив, затем импортируем его в нужную папку. Microsoft документирует wsl &ndash;export, wsl &ndash;unregister и wsl &ndash;import как штатный путь для переноса и резервного копирования WSL-дистрибутивов.
​</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><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"> 3
</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"> 4
</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"> 5
</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"> 6
</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"> 7
</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"> 8
</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"> 9
</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">10
</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">11
</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">12
</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">13
</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">14
</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">15
</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">16
</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-powershell" data-lang="powershell"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Смотрим имя дистрибутива:</span>
</span></span><span style="display:flex;"><span>wsl -l -v
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#Останавливаем WSL</span>
</span></span><span style="display:flex;"><span>wsl --shutdown
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#Экспортируем Debian в архив</span>
</span></span><span style="display:flex;"><span>wsl --export Debian D:\Backup\Debian.<span style="color:#79c0ff">tar</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#Удаляем старую регистрацию, если нужен чистый перенос</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>wsl --unregister Debian
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#Импортируем в управляемую папку</span>
</span></span><span style="display:flex;"><span>wsl --import Debian D:\WSL\Debian D:\Backup\Debian.<span style="color:#79c0ff">tar</span> --version <span style="color:#a5d6ff">2</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>После этого Debian будет жить в D:\WSL\Debian, а не в Store-профиле пользователя, и им станет проще управлять и делать бэкап.</p>
<h2 id="настраиваем-ansible-через-uv">Настраиваем Ansible через uv</h2>
<p>Для Ansible в WSL2 я рекомендую не системную установку и не pip в системный Python, а отдельное виртуальное окружение, управляемое через uv. Такой подход изолирует зависимости, не трогает системный Python Debian и позволяет легко воспроизводить окружение после восстановления из бэкапа.</p>
<p>ansible-core — это базовый движок Ansible. В отличие от полного пакета ansible, он содержит только ядро: выполнение playbook’ов, inventory-логику, CLI и основную инфраструктуру. Это делает установку легче, прозрачнее и удобнее для проектного workflow.</p>
<p>Минимальный набор для установки:</p>
<ul>
<li>uv;</li>
<li>ansible-core;</li>
<li>openssh-client;</li>
<li>git;</li>
<li>python3.</li>
</ul>
<p>После входа в Debian:</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><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">3
</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt update
</span></span><span style="display:flex;"><span>sudo apt install -y curl git openssh-client python3
</span></span><span style="display:flex;"><span>curl --proto <span style="color:#a5d6ff">&#39;=https&#39;</span> --tlsv1.2 -LsSf https://releases.astral.sh/github/uv/releases/download/0.11.7/uv-installer.sh | sh
</span></span></code></pre></td></tr></table>
</div>
</div><p>После установки uv нужно переоткрытьshell-сессию или добавьте uv в PATH, если установщик это не сделал автоматически. uv умеет создавать виртуальные окружения и ставить пакеты внутрь них, а также работать с выбранным Python-интерпретатором.</p>
<p>Создаем окружение
Если проект лежит в ~/ansible:</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><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">3
</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">4
</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-bash" data-lang="bash"><span style="display:flex;"><span>cd ~/ansible
</span></span><span style="display:flex;"><span>uv venv
</span></span><span style="display:flex;"><span>source .venv/bin/activate
</span></span><span style="display:flex;"><span>uv pip install ansible-core
</span></span></code></pre></td></tr></table>
</div>
</div><p>Если нужен линтер:</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></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-bash" data-lang="bash"><span style="display:flex;"><span>uv pip install ansible-core ansible-lint
</span></span></code></pre></td></tr></table>
</div>
</div><p>Такой workflow хорош тем, что версию Ansible можно менять независимо от системы, а зависимости проекта не смешиваются с Debian-пакетами.</p>
<p>Почему ansible-core удобнее
Для рабочей машины под Ansible ansible-core обычно удобнее, чем полный пакет ansible, по нескольким причинам. Во-первых, он меньше и чище, поэтому окружение проще поддерживать. Во-вторых, вы явно контролируете, какие коллекции и зависимости нужны именно вашему проекту. В-третьих, это снижает зависимость от версии, которую выдает системный репозиторий Debian.</p>
<p>Практически это означает следующее:</p>
<ul>
<li>ansible-core ставится в проектный venv;</li>
<li>коллекции ставятся отдельно через ansible-galaxy или по requirements.yml;</li>
<li>при необходимости окружение можно пересоздать за пару минут.</li>
</ul>
<p>Рекомендуемый workflow</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><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">3
</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">4
</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">5
</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-bash" data-lang="bash"><span style="display:flex;"><span>mkdir -p ~/ansible
</span></span><span style="display:flex;"><span>cd ~/ansible
</span></span><span style="display:flex;"><span>uv venv
</span></span><span style="display:flex;"><span>source .venv/bin/activate
</span></span><span style="display:flex;"><span>uv pip install ansible-core
</span></span></code></pre></td></tr></table>
</div>
</div><p>Дальше в репозитории храним:</p>
<ul>
<li>playbook’и;</li>
<li>inventory;</li>
<li>requirements.yml;</li>
<li>ansible.cfg.
Сами зависимости и .venv в Git не добавляем. Это делает проект переносимым и аккуратным.</li>
</ul>
<h2 id="маппинг-ansible-в-dansible">Маппинг ~/ansible в D:\ansible</h2>
<p>Если хотите хранить проект на диске Windows, используйте стандартный путь WSL: D:\ansible в Linux виден как /mnt/d/ansible. Это штатный механизм WSL, а для обратного преобразования путей существует wslpath.
​
Самый простой вариант:</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></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-bash" data-lang="bash"><span style="display:flex;"><span>cd /mnt/d/ansible
</span></span></code></pre></td></tr></table>
</div>
</div><p>Если хотите, чтобы в Linux путь был именно ~/ansible, создайте символическую ссылку:</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-bash" data-lang="bash"><span style="display:flex;"><span>rm -rf ~/ansible
</span></span><span style="display:flex;"><span>ln -s /mnt/d/ansible ~/ansible
</span></span></code></pre></td></tr></table>
</div>
</div><p>После этого ~/ansible будет указывать на D:\ansible. Это удобно: в Ansible-проектах вы работаете с привычным Linux-путем, а файлы физически лежат на Windows-диске.
​</p>
<h2 id="команды-управления-wsl">Команды управления WSL</h2>
<p>Минимальный набор команд для повседневной работы:</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><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"> 3
</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"> 4
</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"> 5
</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"> 6
</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"> 7
</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"> 8
</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"> 9
</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">10
</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">11
</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">12
</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">13
</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">14
</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">15
</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">16
</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">17
</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-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Показать все дистрибутивы и их версию</span>
</span></span><span style="display:flex;"><span>wsl -l -v
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Запустить Debian</span>
</span></span><span style="display:flex;"><span>wsl -d Debian
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Сделать Debian дистрибутивом по умолчанию</span>
</span></span><span style="display:flex;"><span>wsl --set-default Debian
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#​ Полностью остановить WSL2</span>
</span></span><span style="display:flex;"><span>wsl --shutdown
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Остановить только Debian</span>
</span></span><span style="display:flex;"><span>wsl --terminate Debian
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Перевести Debian в WSL2, если он вдруг оказался в WSL1</span>
</span></span><span style="display:flex;"><span>wsl --set-version Debian <span style="color:#a5d6ff">2</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="бэкап-через-vhdx">Бэкап через VHDX</h2>
<p>Для WSL2 самый удобный резервный формат — VHDX. Microsoft прямо поддерживает экспорт дистрибутива в VHDX через wsl &ndash;export &hellip; &ndash;vhd, а затем импорт обратно через wsl &ndash;import &hellip; &ndash;vhd. Это сохраняет весь дистрибутив целиком, включая Ansible, Python-окружение, ключи, историю shell и рабочие файлы.</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><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">3
</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">4
</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">5
</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-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Перед бэкапом обязательно останавливаем WSL</span>
</span></span><span style="display:flex;"><span>wsl --shutdown
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Затем создаtv бэкап</span>
</span></span><span style="display:flex;"><span>wsl --export Debian D:\Backup\Debian\Debian.<span style="color:#79c0ff">vhdx</span> --vhd
</span></span></code></pre></td></tr></table>
</div>
</div><p>Это удобнее ручного копирования ext4.vhdx, потому что команда сразу создает переносимый снимок дистрибутива в формате VHDX.</p>
<h2 id="восстановление">Восстановление</h2>
<p>Если нужно восстановить среду из такого бэкапа, импортируем VHDX как дистрибутив:</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></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-powershell" data-lang="powershell"><span style="display:flex;"><span>wsl --import DebianRestored D:\WSL\DebianRestored D:\Backup\Debian\Debian.<span style="color:#79c0ff">vhdx</span> --vhd --version <span style="color:#a5d6ff">2</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Если мы храним дистрибутив в отдельной папке и используете import-in-place, можно подключить существующий VHDX без распаковки. Это особенно удобно при переносе среды на другой диск или другой компьютер.</p>
<h2 id="практичная-схема-для-работы">Практичная схема для работы</h2>
<p>Для повседневного использования я бы рекомендовал такую схему:</p>
<ul>
<li>Debian живет в WSL2;</li>
<li>Ansible ставится через uv в .venv;</li>
<li>проекты лежат в D:\ansible и маппятся через /mnt/d/ansible;</li>
<li>резервная копия делается через wsl &ndash;export &hellip; &ndash;vhd;</li>
<li>.venv в Git не хранится, а dependencies фиксируются в документации или requirements.yml.</li>
</ul>
<p>Такой подход дает хорошую изоляцию, быстрый старт после восстановления и удобную миграцию между машинами. Для WSL2 это один из самых практичных способов использовать Ansible в Windows 10 без необходимости перезагрузки.</p>
]]></content:encoded></item><item><title>Как автоматизировать создание постов в Hugo: slugs, архетипы и бандлы</title><link>https://dedinit.ru/2026/hugo-slugs-archetypes-bundles/</link><pubDate>Sat, 18 Apr 2026 09:44:39 +0300</pubDate><guid>https://dedinit.ru/2026/hugo-slugs-archetypes-bundles/</guid><description>Настраиваем Hugo так, чтобы не копировать index.md вручную, не мучиться с транслитерацией и получать красивые URL с датами</description><content:encoded><![CDATA[<figure class="entry-cover"><img src="https://dedinit.ru/2026/hugo-slugs-archetypes-bundles/hero.svg" alt="Автоматизация Hugo"></figure><h2 id="проблема-рутина-при-создании-постов">Проблема: рутина при создании постов</h2>
<p>Когда я только начинал вести блог на Hugo, каждый новый пост создавался через боль и страдания:</p>
<ol>
<li>Создать папку вручную <code>content/posts/название-поста/</code></li>
<li>Скопировать туда <code>index.md</code> из соседней папки</li>
<li>Отредактировать front matter (дату, заголовок, теги)</li>
<li>Придумать slug для красивого URL</li>
<li>&hellip;и не забыть, что папку лучше назвать на латинице</li>
</ol>
<p>На всё это уходило пара минут чисто механической работы. А когда постов 10–20, это начинает реально бесить.</p>
<p>В этой статье я расскажу, как я решил эту проблему с помощью штатных возможностей Hugo: <strong>архетипов (archetypes)</strong>, <strong>пермалинков (permalinks)</strong> и правильной организации <strong>бандлов (bundles)</strong>.</p>
<h2 id="что-такое-бандл-и-зачем-папка-для-каждого-поста">Что такое бандл и зачем папка для каждого поста</h2>
<p>Hugo поддерживает три типа контента:</p>
<p>Три способа хранения контента в Hugo</p>
<ol>
<li>Простой файл .md (без папки)</li>
</ol>
<p>Самый простой способ — просто положить файл мой-пост.md в папку content/posts/:</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><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">3
</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">4
</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">5
</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-text" data-lang="text"><span style="display:flex;"><span>content/
</span></span><span style="display:flex;"><span>└── posts/
</span></span><span style="display:flex;"><span>    ├── первый-пост.md
</span></span><span style="display:flex;"><span>    ├── второй-пост.md
</span></span><span style="display:flex;"><span>    └── третий-пост.md
</span></span></code></pre></td></tr></table>
</div>
</div><p>Плюсы:</p>
<ul>
<li>Максимально просто, ничего создавать не нужно</li>
<li>Подходит для простых постов без изображений</li>
</ul>
<p>Минусы:</p>
<ul>
<li>Все изображения нужно класть в общую папку static/images/</li>
<li>Нельзя прикрепить к посту специфичные файлы (PDF, архивы и т.д.)</li>
<li>Изображения нужно называть уникально, чтобы не пересекались с другими постами</li>
</ul>
<ol start="2">
<li>Leaf bundle (папка + index.md)</li>
</ol>
<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><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">3
</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">4
</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">5
</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">6
</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-text" data-lang="text"><span style="display:flex;"><span>content/
</span></span><span style="display:flex;"><span>└── posts/
</span></span><span style="display:flex;"><span>    └── мой-пост/
</span></span><span style="display:flex;"><span>        ├── index.md
</span></span><span style="display:flex;"><span>        ├── hero.jpg
</span></span><span style="display:flex;"><span>        └── code-example.py
</span></span></code></pre></td></tr></table>
</div>
</div><p>Плюсы:</p>
<ul>
<li>Все ресурсы поста в одном месте</li>
<li>Можно ссылаться на изображения относительно: <img loading="lazy" src="hero.jpg" alt="герой"  />
</li>
<li>Не нужно думать об уникальности имён файлов</li>
</ul>
<p>Минусы:</p>
<ul>
<li>Нужно создавать папку (но мы это автоматизировали)</li>
</ul>
<ol start="3">
<li>Branch bundle (папка + _index.md)</li>
</ol>
<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><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">3
</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">4
</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">5
</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">6
</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-text" data-lang="text"><span style="display:flex;"><span>content/
</span></span><span style="display:flex;"><span>├── posts/
</span></span><span style="display:flex;"><span>│   ├── _index.md          &lt;- описывает секцию /posts/
</span></span><span style="display:flex;"><span>│   ├── первый-пост.md
</span></span><span style="display:flex;"><span>│   └── мой-пост/
</span></span><span style="display:flex;"><span>│       └── index.md
</span></span></code></pre></td></tr></table>
</div>
</div><p>Плюсы:</p>
<ul>
<li>_index.md позволяет задать заголовок, описание для всей секции</li>
<li>Можно настроить отдельный шаблон для списка постов</li>
</ul>
<p>Структура моего блога:</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><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">3
</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">4
</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">5
</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">6
</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">7
</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">8
</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">9
</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-text" data-lang="text"><span style="display:flex;"><span>content/
</span></span><span style="display:flex;"><span>├── posts/
</span></span><span style="display:flex;"><span>│ ├── hugo-slugs-archetypes-bundles/
</span></span><span style="display:flex;"><span>│ │ ├── index.md
</span></span><span style="display:flex;"><span>│ │ ├── images/
</span></span><span style="display:flex;"><span>│ │ │ └── diagram.png
</span></span><span style="display:flex;"><span>│ │ └── code-example.txt
</span></span><span style="display:flex;"><span>│ └── другой-пост/
</span></span><span style="display:flex;"><span>│ └── index.md
</span></span></code></pre></td></tr></table>
</div>
</div><p>Плюсы такого подхода:</p>
<ul>
<li>Все посты хранятся одинаково — папка + index.md. Не нужно думать, какой способ выбрать.</li>
<li>Все файлы поста в одном месте</li>
<li>Можно удобно ссылаться на изображения: <code>![схема](images/diagram.png)</code></li>
<li>Не нужно придумывать уникальные имена для картинок глобально</li>
<li>Если я захочу экспортировать пост в другой блог, достаточно скопировать одну папку со всеми ресурсами.</li>
</ul>
<h2 id="почему-папку-бандла-нужно-называть-на-латинице">Почему папку бандла нужно называть на латинице</h2>
<p>Здесь кроется важный момент. Hugo позволяет использовать любые символы в именах папок, включая кириллицу. Но есть <strong>две причины использовать латиницу</strong>:</p>
<ol>
<li><strong>Чистые URL.</strong> Если папка называется <code>мой-пост</code>, то URL будет <code>/%D0%BC%D0%BE%D0%B9-%D0%BF%D0%BE%D1%81%D1%82/</code>. Браузер это поймёт, но выглядит ужасно.</li>
<li><strong>Slug без транслитерации.</strong> Имя папки удобно использовать как <code>slug</code> — последний сегмент URL. А латиница в URL — это стандарт и хороший тон.</li>
</ol>
<p><strong>Правило:</strong> папку называем на латинице (например, <code>my-awesome-post</code>), а заголовок внутри пишем по-русски.</p>
<h2 id="как-автоматизировать-создание-бандла-через-консоль">Как автоматизировать создание бандла через консоль</h2>
<p>Команда для создания бандла с одной папкой:</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></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-bash" data-lang="bash"><span style="display:flex;"><span>hugo new content posts/название-папки/index.md
</span></span></code></pre></td></tr></table>
</div>
</div><p>Hugo сам создаст папку, сгенерирует index.md с front matter из архетипа.</p>
<p>Важно: эта команда появилась в Hugo 0.112. В старых версиях нужно было сначала создать папку, потом файл.</p>
<h2 id="настройка-архетипа-archetype">Настройка архетипа (archetype)</h2>
<p>Архетип — это шаблон для новых файлов. Он лежит в archetypes/default.md (или в archetypes/post-bundle.md для конкретного типа).</p>
<p>Мой архетип выглядит так (TOML-формат):</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><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"> 3
</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"> 4
</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"> 5
</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"> 6
</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"> 7
</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"> 8
</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"> 9
</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">10
</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">11
</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">12
</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">13
</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">14
</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">15
</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">16
</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">17
</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-toml" data-lang="toml"><span style="display:flex;"><span>---
</span></span><span style="display:flex;"><span>date = <span style="color:#a5d6ff">&#39;{{ .Date }}&#39;</span>
</span></span><span style="display:flex;"><span>lastmod = <span style="color:#a5d6ff">&#39;{{ .Date }}&#39;</span>
</span></span><span style="display:flex;"><span>draft = <span style="color:#79c0ff">true</span>
</span></span><span style="display:flex;"><span>title = <span style="color:#a5d6ff">&#39;{{ replace .File.ContentBaseName &#34;-&#34; &#34; &#34; | title }}&#39;</span>
</span></span><span style="display:flex;"><span>slug = <span style="color:#a5d6ff">&#39;{{ .File.ContentBaseName }}&#39;</span>
</span></span><span style="display:flex;"><span>description = <span style="color:#a5d6ff">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>author = <span style="color:#a5d6ff">&#39;Кразя&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>categories = [
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#39;uncategorized&#39;</span>
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>tags = [
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">&#39;draft&#39;</span>
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>---
</span></span></code></pre></td></tr></table>
</div>
</div><p>Разберём ключевые моменты:
Поле Значение
title Берёт имя папки, заменяет дефисы на пробелы и делает заглавные буквы. my-awesome-post → My Awesome Post
slug Просто берёт имя папки как есть: my-awesome-post
.File.ContentBaseName Встроенная переменная Hugo — имя текущей папки без расширения и пути</p>
<p>После создания поста я вручную меняю title на русский и заполняю description, categories, tags.</p>
<h2 id="настройка-permalinks-для-красивых-url">Настройка permalinks для красивых URL</h2>
<p>Чтобы URL выглядел как 2025/03/my-awesome-post/, а не как posts/my-awesome-post/, добавляем в hugo.toml:</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-toml" data-lang="toml"><span style="display:flex;"><span>[permalinks]
</span></span><span style="display:flex;"><span>  posts = <span style="color:#a5d6ff">&#34;/:year/:month/:slug/&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Теперь при сборке сайта Hugo сам построит нужную структуру. При этом внутри content/ всё остаётся по-прежнему — папка в posts/.</p>
<h2 id="полный-цикл-создания-поста-без-лишних-телодвижений">Полный цикл создания поста (без лишних телодвижений)</h2>
<p>Вот как теперь выглядит создание нового поста в моём блоге:</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><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"> 3
</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"> 4
</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"> 5
</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"> 6
</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"> 7
</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"> 8
</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"> 9
</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">10
</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">11
</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">12
</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-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 1. Создаём бандл с латинским именем папки</span>
</span></span><span style="display:flex;"><span>hugo new content posts/hugo-best-practices/index.md
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 2. Открываем файл и правим русский заголовок, описание, теги</span>
</span></span><span style="display:flex;"><span>vim content/posts/hugo-best-practices/index.md
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 3. Пишем пост в markdown</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 4. Смотрим локально</span>
</span></span><span style="display:flex;"><span>hugo server -D
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 5. Публикуем</span>
</span></span><span style="display:flex;"><span>make deploy
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="что-ещё-можно-добавить-в-front-matter">Что ещё можно добавить в front matter</h2>
<p>В процессе настройки я выяснил, что Hugo поддерживает много полезных полей:</p>
<table>
	<thead>
			<tr>
					<th>Поле</th>
					<th>Назначение</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>publishDate</code></td>
					<td>Отложенная публикация (не рендерится до указанной даты)</td>
			</tr>
			<tr>
					<td><code>expiryDate</code></td>
					<td>Автоматическое снятие с публикации</td>
			</tr>
			<tr>
					<td><code>lastmod</code></td>
					<td>Дата последнего изменения (для SEO)</td>
			</tr>
			<tr>
					<td><code>aliases</code></td>
					<td>Редиректы со старых URL</td>
			</tr>
			<tr>
					<td><code>weight</code></td>
					<td>Ручная сортировка в списке (меньше — выше)</td>
			</tr>
			<tr>
					<td><code>images</code></td>
					<td>Изображение для Open Graph и Twitter Cards</td>
			</tr>
			<tr>
					<td><code>params</code></td>
					<td>Кастомные параметры для темы</td>
			</tr>
	</tbody>
</table>
<h2 id="итог">Итог</h2>
<p>После всех настроек создание нового поста занимает ровно столько времени, сколько нужно на написание контента. Никакой ручной возни с папками и копированием index.md.</p>
<p>Ключевые выводы:</p>
<ul>
<li>
<p>Используйте leaf bundles (папка + index.md) для хранения всех ресурсов поста в одном месте</p>
</li>
<li>
<p>Папки называйте на латинице — это даст чистые URL и автоматический slug</p>
</li>
<li>
<p>Настройте архетип с переменной {{ .File.ContentBaseName }} для автоматической генерации title и slug</p>
</li>
<li>
<p>Добавьте [permalinks] в конфиг для красивых URL с датами</p>
</li>
</ul>
<p>Создавайте новый пост одной командой: <code>bash hugo new content posts/имя-папки/index.md</code></p>
<p>Теперь можно сосредоточиться на том, ради чего всё затевалось — на содержании.</p>
<p>Если у тебя есть свои лайфхаки по Hugo или ты знаешь, как сделать транслитерацию slug прямо из заголовка — пишите мне в Telegram <a href="https://t.me/kpa39l">https://t.me/kpa39l</a>. Обсудим.</p>
]]></content:encoded></item></channel></rss>