<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>M9K blog: заметки с тегом системное администрирование</title>
<link>https://blog.m9k.space/tags/sistemnoe-administrirovanie/</link>
<description>Заметки обо всём: devops, lifestyle, культура.</description>
<author>M</author>
<language>ru</language>
<generator>E2 (v3877; Aegea)</generator>

<itunes:owner>
<itunes:name>M</itunes:name>
<itunes:email></itunes:email>
</itunes:owner>
<itunes:subtitle>Заметки обо всём: devops, lifestyle, культура.</itunes:subtitle>
<itunes:image href="" />
<itunes:explicit></itunes:explicit>

<item>
<title>Как временно расширить ZFS-пул с помощью zvol из другого пула</title>
<guid isPermaLink="false">228</guid>
<link>https://blog.m9k.space/all/kak-vremenno-rasshirit-zfs-pul-s-pomoschyu-zvol-iz-drugogo-pula/</link>
<pubDate>Tue, 21 Jul 2026 09:36:32 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/kak-vremenno-rasshirit-zfs-pul-s-pomoschyu-zvol-iz-drugogo-pula/</comments>
<description>
&lt;p&gt;Иногда ZFS-пул оказывается заполнен почти полностью, но переносить данные на другой файловый раздел неудобно или слишком долго.&lt;/p&gt;
&lt;p&gt;Было два независимых пула:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zpool list

NAME    SIZE  ALLOC   FREE  FRAG  CAP  HEALTH
db     11.7T  8.99T  2.71T   18%  76%  ONLINE
limon  79.5G  77.0G  2.46G   90%  96%  ONLINE&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Пул limon закончился, а в пуле db оставалось несколько терабайт свободного пространства.&lt;/p&gt;
&lt;p&gt;Требовалось временно дать пулу limon около 10 ГБ, очистить логи и затем вернуть конфигурацию в исходное состояние.&lt;/p&gt;
&lt;h3&gt;Почему нельзя просто передать свободное место?&lt;/h3&gt;
&lt;p&gt;Свободное пространство принадлежит конкретному ZFS-пулу. Датасет одного пула не может непосредственно использовать место из другого пула.&lt;/p&gt;
&lt;h3&gt;Топология исходного пула&lt;/h3&gt;
&lt;p&gt;Пул limon состоял из одного раздела:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zpool status limon

pool: limon
state: ONLINE
config:

    NAME        STATE     READ WRITE CKSUM
    limon       ONLINE       0     0     0
      sdd1      ONLINE       0     0     0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Для последующего удаления временного vdev необходимо проверить поддержку функции device_removal:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zpool get feature@device_removal limon&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В моём случае функция была включена:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;NAME   PROPERTY                VALUE    SOURCE
limon  feature@device_removal  enabled  local&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Также необходимо определить фактическое значение ashift существующего vdev.&lt;/p&gt;
&lt;p&gt;Команда:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zpool get ashift limon&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;может показать:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;limon  ashift  0  default&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Значение 0 означает автоматический выбор для новых устройств и не показывает фактический ashift уже существующего vdev.&lt;/p&gt;
&lt;p&gt;Реальное значение можно получить через zdb:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zdb -C limon | grep -E 'path|ashift'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Результат:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;path: '/dev/sdd1'
phys_path: 'acpi-VMBUS:01-vmbus-3d4511ba2bac43a19298b9d3bddf7b27-lun-2'
ashift: 12&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Таким образом, временный vdev тоже нужно добавлять с ashift=12.&lt;/p&gt;
&lt;h3&gt;Важное предупреждение&lt;/h3&gt;
&lt;p&gt;Описанный способ создаёт зависимость одного ZFS-пула от другого.&lt;/p&gt;
&lt;p&gt;Пока временный zvol входит в пул limon:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;пул db нельзя экспортировать;&lt;/li&gt;
&lt;li&gt;zvol нельзя удалить;&lt;/li&gt;
&lt;li&gt;сервер нельзя загружать без доступного пула db;&lt;/li&gt;
&lt;li&gt;потеря пула db может привести к потере всего пула limon&lt;br /&gt;
Это аварийная временная мера, а не постоянная архитектура хранения.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Создание временного zvol&lt;/h3&gt;
&lt;p&gt;Чтобы не повторять изменяемые значения в командах, зададим переменные:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;POOL_SOURCE=&amp;quot;db&amp;quot;
POOL_TARGET=&amp;quot;limon&amp;quot;
ZVOL_NAME=&amp;quot;limon-temp-vdev&amp;quot;
ZVOL_SIZE=&amp;quot;10G&amp;quot;
ZVOL_DATASET=&amp;quot;${POOL_SOURCE}/${ZVOL_NAME}&amp;quot;
ZVOL_DEVICE=&amp;quot;/dev/zvol/${ZVOL_DATASET}&amp;quot;
ASHIFT=&amp;quot;12&amp;quot;
VOLBLOCKSIZE=&amp;quot;16K&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Создаём обычный, неразреженный zvol:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zfs create -V &amp;quot;${ZVOL_SIZE}&amp;quot; -b &amp;quot;${VOLBLOCKSIZE}&amp;quot; -o compression=off &amp;quot;${ZVOL_DATASET}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Проверяем созданный zvol:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zfs get volsize,volblocksize,refreservation,compression &amp;quot;${ZVOL_DATASET}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Проверяем наличие блочного устройства:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;udevadm settle
ls -l &amp;quot;${ZVOL_DEVICE}&amp;quot;
blockdev --getsize64 &amp;quot;${ZVOL_DEVICE}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;У обычного zvol параметр refreservation должен быть примерно равен его размеру. Это гарантирует, что необходимые 10 ГБ действительно зарезервированы в пуле db.&lt;/p&gt;
&lt;h3&gt;Проверка добавления без изменения пула&lt;/h3&gt;
&lt;p&gt;Сначала выполняем пробный запуск:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zpool add -n -o ashift=&amp;quot;${ASHIFT}&amp;quot; &amp;quot;${POOL_TARGET}&amp;quot; &amp;quot;${ZVOL_DEVICE}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В предполагаемой конфигурации исходный диск и временный zvol должны находиться на одном уровне:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;limon
sdd1
/dev/zvol/db/limon-temp-vdev&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Добавление временного пространства&lt;/h3&gt;
&lt;p&gt;Если пробная проверка не показывает ошибок, добавляем zvol:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zpool add -o ashift=&amp;quot;${ASHIFT}&amp;quot; &amp;quot;${POOL_TARGET}&amp;quot; &amp;quot;${ZVOL_DEVICE}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После этого проверяем состояние:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zpool status -P &amp;quot;${POOL_TARGET}&amp;quot;zpool list &amp;quot;${POOL_TARGET}&amp;quot;
zfs list &amp;quot;${POOL_TARGET}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Дополнительно проверяем ashift обоих vdev:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zdb -C &amp;quot;${POOL_TARGET}&amp;quot; | grep -E 'path|ashift'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Оба top-level vdev должны иметь ashift=12. Разный ashift может помешать последующему удалению временного устройства.&lt;/p&gt;
&lt;p&gt;После добавления zvol пул limon получит немного меньше 10 ГБ полезного пространства из-за внутренних накладных расходов ZFS.&lt;/p&gt;
&lt;h3&gt;Удаление временного vdev&lt;/h3&gt;
&lt;p&gt;Сначала проверяем возможность удаления:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zpool remove -n &amp;quot;${POOL_TARGET}&amp;quot; &amp;quot;${ZVOL_DEVICE}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Параметр -n не выполняет полноценную симуляцию переноса данных. Он используется для оценки параметров операции, включая необходимый объём памяти для таблиц перенаправления.&lt;/p&gt;
&lt;p&gt;Если ошибок нет, запускаем удаление с ожиданием завершения:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zpool remove -w &amp;quot;${POOL_TARGET}&amp;quot; &amp;quot;${ZVOL_DEVICE}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Во время удаления ZFS переносит блоки с временного zvol обратно на исходный диск sdd1.&lt;/p&gt;
&lt;p&gt;За процессом можно наблюдать в другом терминале:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;watch -n 5 &amp;quot;zpool status ${POOL_TARGET}; zpool list ${POOL_TARGET}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;До полного завершения операции нельзя:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;уничтожать zvol;&lt;/li&gt;
&lt;li&gt;экспортировать пул db;&lt;/li&gt;
&lt;li&gt;отключать устройство;&lt;/li&gt;
&lt;li&gt;перезагружать систему без необходимости.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;После успешного удаления проверяем, что в пуле остался только исходный vdev:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zpool status -P &amp;quot;${POOL_TARGET}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В конфигурации должен снова отображаться только sdd1.&lt;/p&gt;
&lt;h3&gt;Удаление временного zvol&lt;/h3&gt;
&lt;p&gt;Только после успешного завершения zpool remove можно уничтожить zvol:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zfs destroy &amp;quot;${ZVOL_DATASET}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Финальная проверка:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;zpool status &amp;quot;${POOL_TARGET}&amp;quot;
zpool list &amp;quot;${POOL_TARGET}&amp;quot;
zfs list &amp;quot;${POOL_SOURCE}&amp;quot; &amp;quot;${POOL_TARGET}&amp;quot;&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>В терминале Linux пропал текст? Верните echo</title>
<guid isPermaLink="false">227</guid>
<link>https://blog.m9k.space/all/v-terminale-linux-propal-tekst-vernite-echo/</link>
<pubDate>Tue, 21 Jul 2026 08:40:59 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/v-terminale-linux-propal-tekst-vernite-echo/</comments>
<description>
&lt;p&gt;Иногда терминал продолжает принимать команды, но перестаёт показывать вводимый текст. Печатаешь вслепую, нажимаешь Enter — команда выполняется. Выглядит как поломка терминала, но чаще всего просто отключён режим отображения ввода.&lt;/p&gt;
&lt;p&gt;Вернуть его можно одной командой:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;stty echo&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После этого символы снова будут отображаться при вводе.&lt;/p&gt;
&lt;p&gt;Обычно echo отключается после некорректно завершившейся программы, которая временно скрывала ввод — например, при запросе пароля. Если терминал выглядит совсем странно, можно выполнить более радикальный сброс:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;stty sane&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Скучная банальность, но именно такие мелочи особенно приятно случайно узнать после нескольких минут печати в пустоту.&lt;/p&gt;
</description>
</item>

<item>
<title>Clash Verge Rev Local Tunnel</title>
<guid isPermaLink="false">224</guid>
<link>https://blog.m9k.space/all/clash-verge-rev-local-tunnel/</link>
<pubDate>Wed, 10 Jun 2026 17:27:20 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/clash-verge-rev-local-tunnel/</comments>
<description>
&lt;p&gt;Получив намедни на локальной станции отвал сразу нескольких утилит, которые умеют пробрасывать трафик через TUN-режим, я пошёл искать альтернативы. В итоге нашлась Clash Verge:&lt;br /&gt;
&lt;a href="https://github.com/clash-verge-rev/clash-verge-rev"&gt;https://github.com/clash-verge-rev/clash-verge-rev&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Задача простая: завернуть трафик через TUN в локальный прокси.&lt;/p&gt;
&lt;p&gt;Идём по пути:&lt;/p&gt;
&lt;p&gt;Профили -&gt; Новый -&gt; Local -&gt; Выбираем файл&lt;/p&gt;
&lt;p&gt;Дальше скармливаем Clash Verge YAML-конфиг:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mixed-port: 7890
allow-lan: false
mode: global
log-level: info
ipv6: false

tun:
  enable: true
  stack: mixed
  auto-route: true
  auto-detect-interface: true
  strict-route: true
  dns-hijack:
    - any:53
    - tcp://any:53

dns:
  enable: true
  ipv6: false
  enhanced-mode: fake-ip
  listen: 127.0.0.1:1053
  default-nameserver:
    - 1.1.1.1
    - 8.8.8.8
  nameserver:
    - 1.1.1.1
    - 8.8.8.8
  fake-ip-filter:
    - &amp;quot;*.lan&amp;quot;
    - &amp;quot;*.local&amp;quot;
    - &amp;quot;localhost&amp;quot;

proxies:
  - name: lan-socks5
    type: socks5
    server: %IP прокси%
    port: %порт прокси%
    udp: true

proxy-groups:
  - name: PROXY
    type: select
    proxies:
      - lan-socks5
      - DIRECT

rules:
  - IP-CIDR,%IP прокси%/32,DIRECT,no-resolve
  - IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
  - IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
  - IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
  - MATCH,PROXY&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После этого можно на главной включать Tun.&lt;br /&gt;
В результате весь трафик, кроме локальных сетей и самого прокси-сервера, будет заворачиваться через указанный SOCKS5-прокси. Локальные диапазоны оставлены в DIRECT, чтобы не словить прекрасный цирк с недоступными локальными ресурсами, роутерами, NAS и прочими домашними зверушками.&lt;/p&gt;
</description>
</item>

<item>
<title>ID Docker контейнеров — максимальных потребителей SWAP в Linux</title>
<guid isPermaLink="false">220</guid>
<link>https://blog.m9k.space/all/id-docker-konteynerov-maksimalnyh-potrebiteley-swap-v-linux/</link>
<pubDate>Mon, 27 Apr 2026 02:48:52 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/id-docker-konteynerov-maksimalnyh-potrebiteley-swap-v-linux/</comments>
<description>
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;for pid in $(ls /proc | grep -E '^[0-9]+$'); do     swap=$(grep VmSwap /proc/$pid/status 2&amp;gt;/dev/null | awk '{print $2}');          if [[ -n &amp;quot;$swap&amp;quot; &amp;amp;&amp;amp; &amp;quot;$swap&amp;quot; -gt 0 ]]; then         cmd=$(tr '\0' ' ' &amp;lt; /proc/$pid/cmdline);          cgroup=$(cat /proc/$pid/cgroup 2&amp;gt;/dev/null);
        container_id=$(echo &amp;quot;$cgroup&amp;quot; | grep -Eo '[a-f0-9]{64}' | head -n1);
        if [[ -z &amp;quot;$container_id&amp;quot; ]]; then             container_id=$(echo &amp;quot;$cgroup&amp;quot; | grep -Eo 'docker-[a-f0-9]+' | sed 's/docker-//' | head -n1);         fi;          echo -e &amp;quot;$swap KB\tPID: $pid\tCID: ${container_id:-host}\tCMD: $cmd&amp;quot;;     fi; done | sort -nr | head&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Proxmox 7 + Debian 12|13 LXC Template</title>
<guid isPermaLink="false">210</guid>
<link>https://blog.m9k.space/all/proxmox-7-debian-12-13-lxc-template/</link>
<pubDate>Sat, 07 Feb 2026 03:27:43 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/proxmox-7-debian-12-13-lxc-template/</comments>
<description>
&lt;p&gt;У Proxmox есть неплохая обратная совместимость с более свежими версиями LXC-контейнеров по сравнению с той версией Debian, на которой основан текущий релиз. В нашем случае используется Proxmox 7 (на базе Debian 11).&lt;/p&gt;
&lt;p&gt;При попытке создать контейнер получаем ошибку вида:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;TASK ERROR: unable to create CT 100 - unsupported debian version '1X'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Для обхода проблемы правим файл&lt;br /&gt;
/usr/share/perl5/PVE/LXC/Setup/Debian.pm в двух местах.&lt;/p&gt;
&lt;p&gt;Обновляем карту версий:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;my $version_map = { 'stretch/sid' =&amp;gt; 9.1, 'buster/sid' =&amp;gt; 10, 'bullseye/sid'=&amp;gt; 11, 'bookworm/sid'=&amp;gt; 12, 'trixie/sid' =&amp;gt; 13, 'kali-rolling'=&amp;gt; 11, };&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Расширяем допустимый диапазон версий:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;if !($version &amp;gt;= 4 &amp;amp;&amp;amp; $version &amp;lt;= 14);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После этого перезапускаем связанные демоны:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;systemctl restart pvedaemon 
systemctl restart pveproxy&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Готово, контейнер успешно создаётся.&lt;/p&gt;
&lt;p&gt;Разумеется, это грязный хак: при обновлениях Proxmox изменения будут перезаписаны, так что решение подходит только как временное.&lt;/p&gt;
</description>
</item>

<item>
<title>Windows перемещение файлов в директории по совпадению части имени</title>
<guid isPermaLink="false">203</guid>
<link>https://blog.m9k.space/all/windows-peremeschenie-faylov-v-direktorii-po-sovpadeniyu-chasti/</link>
<pubDate>Fri, 10 Oct 2025 02:38:06 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/windows-peremeschenie-faylov-v-direktorii-po-sovpadeniyu-chasti/</comments>
<description>
&lt;p&gt;В указанной директории ищем файлы с «_45» или «_16», перемещаем во внутреннюю создаваемую директорию.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;$dir = &amp;quot;C:\work\pics&amp;quot;
$target = Join-Path $dir 'substrateInstagram'
New-Item -ItemType Directory -Path $target -Force | Out-Null
Get-ChildItem -Path $dir -Filter *.jpg -File |
  Where-Object { $_.BaseName -match '(_45|_16)' } |
  Move-Item -Destination $target&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Linux Passwordless oneliner</title>
<guid isPermaLink="false">202</guid>
<link>https://blog.m9k.space/all/linux-passwordless-oneliner/</link>
<pubDate>Mon, 25 Aug 2025 10:44:17 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/linux-passwordless-oneliner/</comments>
<description>
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;TARGET=user@host; PORT=22; [[ -r ~/.ssh/id_ed25519.pub ]] || ssh-keygen -t ed25519 -N &amp;quot;&amp;quot; -f ~/.ssh/id_ed25519; ssh -p &amp;quot;$PORT&amp;quot; &amp;quot;$TARGET&amp;quot; 'umask 077; mkdir -p ~/.ssh &amp;amp;&amp;amp; cat &amp;gt; ~/.ssh/authorized_keys' &amp;lt; ~/.ssh/id_ed25519.pub &amp;amp;&amp;amp; ssh -p &amp;quot;$PORT&amp;quot; &amp;quot;$TARGET&amp;quot; 'echo &amp;quot;&amp;gt;&amp;gt;&amp;gt; Root доступ подтверждён&amp;quot;'&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>CouchDB, Debian, AstraLinux, Docker и все-все-все</title>
<guid isPermaLink="false">201</guid>
<link>https://blog.m9k.space/all/skreschivaem-uzha-i-ezha/</link>
<pubDate>Thu, 14 Aug 2025 10:40:09 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/skreschivaem-uzha-i-ezha/</comments>
<description>
&lt;p&gt;Возникла необходимость запустить Couchdb в контейнере, где базовая ось (и базовые же пакеты) должны быть очень-очень свежими, в противной случай контейнер не проходит встроенную проверку на безопасность.&lt;br /&gt;
Стандартный официальный образ собран на базе Debian 12.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;FROM debian:bookworm-slim&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Простая замена базового образа на&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;FROM debian:trixie-slim&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;приводит к тому, что контейнер не собирается по зависимостям.&lt;br /&gt;
Смотрим официальную репу в части зависимостей и видим прекрасное — libmozjs-78-0 (&gt;= 78.15.0). Пробуем ставить свежую версию, которая есть в репах — не помогает, пакет couchdb принципиально требует libmozjs-78-0.&lt;/p&gt;
&lt;p&gt;Ок, скрещиваем ежа и ужа.&lt;/p&gt;
&lt;p&gt;У debian есть механизм apt-pinning. Это механизм, позволяющий задать приоритеты для версий пакетов из разных источников (репозиториев, release-веток, PPA), чтобы фиксировать версию, запрещать обновление или предпочитать конкретный источник.&lt;/p&gt;
&lt;p&gt;Коротко приоритеты делятся на:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt; 0 — никогда не устанавливать.&lt;/li&gt;
&lt;li&gt;1..99 — ставить только при явном указании версии.&lt;/li&gt;
&lt;li&gt;100 — приоритет уже установленной версии.&lt;/li&gt;
&lt;li&gt;500 — дефолт для неприоритетных источников.&lt;/li&gt;
&lt;li&gt;990 — приоритет для целевого релиза (-t), либо если явно указан release.&lt;/li&gt;
&lt;li&gt;&gt;1000 — разрешает понижение версии (downgrade) к закреплённой.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Добавляет в наш Dockerfile:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;RUN echo &amp;quot;deb http://deb.debian.org/debian bookworm main&amp;quot; &amp;gt; /etc/apt/sources.list.d/bookworm.list &amp;amp;&amp;amp; \
    echo &amp;quot;Package: *\nPin: release n=bookworm\nPin-Priority: 500&amp;quot; &amp;gt; /etc/apt/preferences.d/bookworm &amp;amp;&amp;amp; \
    echo &amp;quot;Package: *\nPin: release n=trixie\nPin-Priority: 600&amp;quot; &amp;gt; /etc/apt/preferences.d/trixie &amp;amp;&amp;amp; \
    echo &amp;quot;deb [signed-by=/usr/share/keyrings/couchdb-archive-keyring.gpg] https://apache.jfrog.io/artifactory/couchdb-deb/ bookworm main&amp;quot; \
            &amp;gt; /etc/apt/sources.list.d/couchdb.list &amp;amp;&amp;amp; \
    apt-get update&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Основная часть пакетов ставится из свежей репы, часть, которых в ней нет — из старых.&lt;/p&gt;
</description>
</item>

<item>
<title>Curl over ssh-tls-tunnel</title>
<guid isPermaLink="false">200</guid>
<link>https://blog.m9k.space/all/curl-over-ssh-tunnel/</link>
<pubDate>Thu, 14 Aug 2025 10:21:23 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/curl-over-ssh-tunnel/</comments>
<description>
&lt;p&gt;Есть ресурс resource.name, на который нужно ходить curl-ом с засылкой данных через JSON. Специфика ресурса такова, что он принимает соединения через TLS 1.3.&lt;br /&gt;
Есть сервер A server.name, на котором нет выхода на нужный ресурс, но есть Nginx, и есть ssh-доступ до сервера B&lt;br /&gt;
Есть сервер B, на котором есть выход на нужный ресурс, но сам сервер очень старый, и TLS на нём не проходит по требования resource.name.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;printf '' | openssl s_client -connect 127.0.0.1:8443 -servername resource.name -tls1_3 -brief&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Костылим:&lt;br /&gt;
На сервере A поднимаем туннель на внутренний порт:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;ssh -fN -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -L 8443:resource.name:443 user@serverB&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;На Nginx рисуем конфиг:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;server {
        listen 9090;
        server_name         server.name;

        access_log  /site/logs/nginx/port_9090.log  main;
        error_log   /site/logs/nginx/port_9090.error.log;

location / {
        proxy_pass https://127.0.0.1:8443;

        proxy_set_header Host resource.name;
        proxy_ssl_server_name on;
        proxy_ssl_name resource.name;
        proxy_ssl_protocols TLSv1.3;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_connect_timeout 10s;
        proxy_read_timeout 60s;
        proxy_ssl_verify off;
}
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Кидаемся тестовым запросом:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;curl -v -X POST 'http://server.name:9090/resource' -H 'Content-Type: application/json' --data '{&amp;quot;login&amp;quot;:&amp;quot;$LOGIN&amp;quot;,&amp;quot;password&amp;quot;:&amp;quot;$PASSWORD&amp;quot;}'&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Unzip без unzip</title>
<guid isPermaLink="false">199</guid>
<link>https://blog.m9k.space/all/unzip-bez-unzip/</link>
<pubDate>Thu, 14 Aug 2025 10:07:44 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/unzip-bez-unzip/</comments>
<description>
&lt;p&gt;Есть сервер без выхода в сеть с древней ОС и мёртвыми репами. Нужно распаковать zip-архив — но утилиты нет.&lt;br /&gt;
Решаем вопрос python-ом:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;python - &amp;lt;&amp;lt;'EOF'
import zipfile, os
archive = 'archive.zip'
target_dir = 'archive.zip'

if not os.path.exists(target_dir):
    os.makedirs(target_dir)

with zipfile.ZipFile(archive, 'r') as z:
    for name in z.namelist():
        z.extract(name, target_dir)
EOF&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Multistage Docker — как пересобирать без кэша не весь Dockefile</title>
<guid isPermaLink="false">197</guid>
<link>https://blog.m9k.space/all/multistage-docker-kak-peresobirat-bez-kesha-ne-ves-dockefile/</link>
<pubDate>Tue, 05 Aug 2025 08:47:54 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/multistage-docker-kak-peresobirat-bez-kesha-ne-ves-dockefile/</comments>
<description>
&lt;p&gt;Возникла необходимость пересобрать отдельный stage-контейнер из Dockerfile. Первый собранный контейнер — это контейнер сборки OpenSSL, который выполняется очень, очень долго. Для того, чтоб сэкономить время, во вторую часть добавляем:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;# Stage 1: OpenSSL-сборка (пусть останется закешированной)
FROM debian:bookworm AS openssl-build
# ... 
# Stage 2: основная
FROM debian:bookworm-slim
# Аргумент, чтобы инвалидировать кэш
ARG CACHE_BREAKER=default
# Используем его в ENV, чтобы Docker счёл слой новым
ENV CACHE_BREAKER=${CACHE_BREAKER}&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Перечитывание топологии диска в ВМ после расширения его в гипервизоре</title>
<guid isPermaLink="false">184</guid>
<link>https://blog.m9k.space/all/perechityvanie-topologii-diska-v-vm-posle-rasshireniya-ego-v-gip/</link>
<pubDate>Tue, 08 Apr 2025 08:04:53 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/perechityvanie-topologii-diska-v-vm-posle-rasshireniya-ego-v-gip/</comments>
<description>
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;echo 1 &amp;gt; /sys/class/scsi_device/0:0:0:0/device/rescan&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Chain из цепочки сертификатов</title>
<guid isPermaLink="false">182</guid>
<link>https://blog.m9k.space/all/chain-iz-cepochki-sertifikatov/</link>
<pubDate>Wed, 19 Mar 2025 03:47:53 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/chain-iz-cepochki-sertifikatov/</comments>
<description>
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;cat domain.crt intermediate.crt root.crt &amp;gt; chain.crt&lt;/code&gt;&lt;/pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>ZFS на Oracle Linux 8.10</title>
<guid isPermaLink="false">181</guid>
<link>https://blog.m9k.space/all/zfs-na-oracle-linux-8-10/</link>
<pubDate>Thu, 06 Mar 2025 10:29:29 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/zfs-na-oracle-linux-8-10/</comments>
<description>
&lt;p&gt;При установке ZFS на Oracle Linux 8.10 по официальной &lt;a href="https://openzfs.github.io/openzfs-docs/Getting%20Started/RHEL-based%20distro/index.html"&gt;доке&lt;/a&gt; столкнулся с неочевидной ошибкой:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;checking for kernel config option compatibility... done&lt;br /&gt;
checking whether CONFIG_MODULES is defined... no&lt;br /&gt;
configure: error:&lt;br /&gt;
		*** This kernel does not include the required loadable module&lt;br /&gt;
		*** support!&lt;br /&gt;
		***&lt;br /&gt;
		*** To build OpenZFS as a loadable Linux kernel module&lt;br /&gt;
		*** enable loadable module support by setting&lt;br /&gt;
		*** `CONFIG_MODULES=y` in the kernel configuration and run&lt;br /&gt;
		*** `make modules_prepare` in the Linux source tree.&lt;br /&gt;
		***&lt;br /&gt;
		*** If you don’t intend to enable loadable kernel module&lt;br /&gt;
		*** support, please compile OpenZFS as a Linux kernel built-in.&lt;br /&gt;
		***&lt;br /&gt;
		*** Prepare the Linux source tree by running `make prepare`,&lt;br /&gt;
		*** use the OpenZFS `—enable-linux-builtin` configure option,&lt;br /&gt;
		*** copy the OpenZFS sources into the Linux source tree using&lt;br /&gt;
		*** `./copy-builtin &lt;linux source directory&gt;`,&lt;br /&gt;
		*** set `CONFIG_ZFS=y` in the kernel configuration and compile&lt;br /&gt;
		*** kernel as usual.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Объяснение и решение проблемы нашёл &lt;a href="https://run.tournament.org.il/build-zfs-on-oel-with-uek7/"&gt;тут&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The root of the problem is with the GCC toolset used to build UEK7 kernel, which is different from the one used to build Redhat compatible version. RH kernel is built (using the default) GCC.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Решение:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;dnf install -y gcc-toolset-11-annobin-plugin-gcc
. /opt/rh/gcc-toolset-11/enable
dkms install zfs/$VERSION&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Python 2.7 на Debian 12</title>
<guid isPermaLink="false">180</guid>
<link>https://blog.m9k.space/all/python2-7-na-debian-12/</link>
<pubDate>Thu, 06 Mar 2025 10:22:59 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/python2-7-na-debian-12/</comments>
<description>
&lt;p&gt;Потребовалось старым Ansible выполнить скрипты на свежайщем Debian 12. Столкнулся с тем, что в дистрибутиве окончательно выпилили поддержку Python 2.7. Пришлось сотворить Франкенштейна:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Добавляем в /etc/apt/sources.list:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;deb http://archive.debian.org/debian/ stretch contrib main non-free&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
&lt;li&gt;Обновляемся&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;sudo apt-get update&lt;/code&gt;&lt;/pre&gt;&lt;ol start="3"&gt;
&lt;li&gt;Делаем установку&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;sudo apt-get install python2.7&lt;/code&gt;&lt;/pre&gt;&lt;ol start="4"&gt;
&lt;li&gt;Делаем симлинк на бинарь&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;cd /usr/bin &amp;amp;&amp;amp;  ln -s python2.7 python&lt;/code&gt;&lt;/pre&gt;&lt;ol start="5"&gt;
&lt;li&gt;Удаляем добавленное в п.1 и повторно делаем пункт 2.&lt;/li&gt;
&lt;/ol&gt;
</description>
</item>

<item>
<title>Основной потребитель swap на Linux-серверах</title>
<guid isPermaLink="false">179</guid>
<link>https://blog.m9k.space/all/osnovnoy-potrebitel-swap-na-linux-serverah/</link>
<pubDate>Tue, 04 Mar 2025 02:45:41 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/osnovnoy-potrebitel-swap-na-linux-serverah/</comments>
<description>
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;for file in /proc/*/status ; do awk '/VmSwap|Name/{printf $2 &amp;quot; &amp;quot; $3}END{ print &amp;quot;&amp;quot;}' $file; done | sort -k 2 -n -r | less&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Взято &lt;a href="https://blog.programs74.ru/linux-which-process-is-using-swap/"&gt;отсюда&lt;/a&gt;&lt;/p&gt;
</description>
</item>

<item>
<title>SED удалить комментарии из XML</title>
<guid isPermaLink="false">178</guid>
<link>https://blog.m9k.space/all/sed-udalit-kommentarii-iz-xml/</link>
<pubDate>Mon, 24 Feb 2025 04:28:21 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/sed-udalit-kommentarii-iz-xml/</comments>
<description>
&lt;p&gt;Возникла задача сравнить два XML-файла конфигурации Clickhouse для выяснения, что же в ней не так. Беда конфигурации в том, что она содержит прям какое-то совершенное безумное количество закомментированных параметров, и сравнение «в лоб» превращается в муку. Почистим файлы SED-ом:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;cat config.xml | sed '/&amp;lt;!--.*--&amp;gt;/d' | sed '/&amp;lt;!--/,/--&amp;gt;/d' | sed '/^[[:space:]]*$/d' &amp;gt; config_without_comments.xml&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Apache Kafka Чтение из топиков</title>
<guid isPermaLink="false">177</guid>
<link>https://blog.m9k.space/all/apache-kafka-chtenie-iz-topikov/</link>
<pubDate>Fri, 14 Feb 2025 09:19:41 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/apache-kafka-chtenie-iz-topikov/</comments>
<description>
&lt;p&gt;Простое чтение топика с нулевой позиции:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;./kafka-console-consumer.sh --bootstrap-server $HOST:$PORT --topic $TOPIC_NAME --from-beginning&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Для чтения с определённой позиции нужно знать максимальный оффсет:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;./kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list $HOST:$PORT --topic $TOPIC_NAME&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Цифра, которую выдаст команда, это максимальное смещение.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;./kafka-console-consumer.sh --bootstrap-server $HOST:$PORT --topic $TOPIC_NAME --offset $OFFSET_START --partition $PARTITION_NUMBER&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;$OFFSET_START — это максимальное смещение минус некая величина до позиции, с которой мы хотим вычитать топик.&lt;/p&gt;
</description>
</item>

<item>
<title>Hairpin NAT Mikrotik</title>
<guid isPermaLink="false">176</guid>
<link>https://blog.m9k.space/all/hairpin-nat-mikrotik/</link>
<pubDate>Sat, 08 Feb 2025 08:26:00 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/hairpin-nat-mikrotik/</comments>
<description>
&lt;p&gt;Потребовалось настроить хождение на сайты в локальной сети не через интернет, и тут неожиданно выяснилось, что инструкция на официальной Вики не очень рабочая. Беглый гуглинг привёл вот &lt;a href="сюда"&gt;&lt;a href="https://blog.mark99.ru/pravilnyj-nat-loopback-hairpin-nat-v-mikrotik/"&gt;https://blog.mark99.ru/pravilnyj-nat-loopback-hairpin-nat-v-mikrotik/&lt;/a&gt;&lt;/a&gt;. Копипаст самого основного:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;/ip firewall mangle
add chain=prerouting comment=&amp;quot;NAT Loopback detect&amp;quot; dst-address=%EXTERNAL_IP% in-interface=LAN connection-state=new action=mark-packet new-packet-mark=nat-loopback passthrough=yes
/ip firewall nat
add chain=srcnat packet-mark=nat-loopback action=masquerade comment=&amp;quot;NAT Loopback replace address&amp;quot; 
/ip firewall nat
add chain=dstnat dst-address=%EXTERNAL_IP% protocol=tcp dst-port=80,443 action=dst-nat to-addresses=%SITE_IP_INTERNAL%&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>gallery-dl</title>
<guid isPermaLink="false">175</guid>
<link>https://blog.m9k.space/all/gallery-dl-2/</link>
<pubDate>Thu, 30 Jan 2025 13:41:39 +0000</pubDate>
<author>M</author>
<comments>https://blog.m9k.space/all/gallery-dl-2/</comments>
<description>
&lt;p&gt;Минималистичная консольная утилита, которая позволяет в автоматическом режиме тягать контент с нескольких десятков сайтов. Мне потребовалось, чтоб в автоматическом режиме вытянуть контент со страницы, на которую я был подписан в Boosty.&lt;br /&gt;
&lt;a href="https://github.com/mikf/gallery-dl"&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;gallery-dl --cookies-from-browser firefox &amp;quot;URL&amp;quot;&lt;/code&gt;&lt;/pre&gt;</description>
</item>


</channel>
</rss>