{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "M9K blog: заметки с тегом системное администрирование",
    "_rss_description": "Заметки обо всём: devops, lifestyle, культура.",
    "_rss_language": "ru",
    "_itunes_email": "",
    "_itunes_categories_xml": "",
    "_itunes_image": "",
    "_itunes_explicit": "",
    "home_page_url": "https:\/\/blog.m9k.space\/tags\/sistemnoe-administrirovanie\/",
    "feed_url": "https:\/\/blog.m9k.space\/tags\/sistemnoe-administrirovanie\/json\/",
    "icon": "https:\/\/blog.m9k.space\/pictures\/userpic\/userpic@2x.jpg?1769095194",
    "authors": [
        {
            "name": "M",
            "url": "https:\/\/blog.m9k.space\/",
            "avatar": "https:\/\/blog.m9k.space\/pictures\/userpic\/userpic@2x.jpg?1769095194"
        }
    ],
    "items": [
        {
            "id": "256",
            "url": "https:\/\/blog.m9k.space\/all\/vecherniy-selfhosted-2-5d-parallaks-v-brauzere\/",
            "title": "Вечерний selfhosted. 2.5D — параллакс в браузере.",
            "content_html": "<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.m9k.space\/pictures\/izobrazhenie-2.png\" width=\"655\" height=\"931\" alt=\"\" \/>\n<\/div>\n<p>Владение несколькими IG-аккаунтами, из которых только один реально активный-активный, позволяет выйти из информационного алгоритмического пузыря. Наткнулся на <a href=\"https:\/\/bio.fojcikdominik.com\">Web-дизайнера<\/a>, делающего прикольные сайты — визитки. С точки зрения прикладного использования <s>человека, познавшего grep и cat<\/s>, это понятное дело, специфический изюм — сайт должен быть про быстроту поиска информации — в этом отношение условный tsukumo, вывалившийся прямиком из нулевых отправит в ноудаун все нынешние модные-и-красивые дизайнерские сайты, но иногда можно поиграться и в красивости.<\/p>\n<p>Внимание привлёк <a href=\"https:\/\/lab.revelium.studio\/milkmaid\">Milkmaid<\/a><br \/>\nЦитирую:<\/p>\n<p class=\"loud\">Эффект строится из фотографии и карты глубины — изображения в оттенках серого, где яркость задаёт условное расстояние до камеры. Для новых фотографий карту оценивает нейросеть по одному кадру.<\/p>\n<p class=\"loud\">Фотография преобразуется в облако цветных точек. Каждая точка получает цвет исходного пикселя, положение по горизонтали и вертикали и смещение в глубину. При необходимости создаются дополнительные слои с небольшим отступом. Они добавляют плотность облаку, но не восстанавливают скрытые части объектов.<\/p>\n<p class=\"loud\">При повороте сцены ближние и дальние точки смещаются на экране по-разному. Этот параллакс создаёт ощущение объёма. У краёв изображения прозрачность точек постепенно уменьшается, поэтому фотография мягко растворяется в фоне.<\/p>\n<p class=\"loud\">Движение рассчитывается как система частиц: у каждой точки есть исходное положение и скорость. Виртуальная пружина возвращает её на место, затухание гасит колебания, а вихревое поле и воздействие курсора отклоняют от исходной позиции. Кнопка сборки убирает рассеивание, после чего облако восстанавливает изображение.<\/p>\n<p class=\"loud\">Python заранее готовит фотографию и карту глубины. Отрисовка и движение частиц выполняются на видеочипе устройства через WebGL 2. Получается интерактивная 2.5D-сцена: небольшой поворот выглядит объёмно, но посмотреть за предметы или увидеть их обратную сторону нельзя.<\/p>\n<p><a href=\"https:\/\/particle.m9k.space\/\">Selfhost demo в стилистике<\/a><\/p>\n",
            "summary": "Владение несколькими IG-аккаунтами, из которых только один реально активный-активный, позволяет выйти из информационного алгоритмического пузыря",
            "date_published": "2026-10-14T23:27:01+07:00",
            "date_modified": "2026-10-14T23:27:17+07:00",
            "tags": [
                "llm",
                "selfhosted",
                "нейрогенерация",
                "системное администрирование"
            ],
            "image": "https:\/\/blog.m9k.space\/pictures\/izobrazhenie-2.png",
            "_date_published_rfc2822": "Wed, 14 Oct 2026 23:27:01 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "256",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/blog.m9k.space\/pictures\/izobrazhenie-2.png"
                ]
            }
        },
        {
            "id": "251",
            "url": "https:\/\/blog.m9k.space\/all\/monitoring-relizov-github\/",
            "title": "Мониторинг релизов GitHub",
            "content_html": "<p>Очередной небольшой эксперимент с вайбкодингом — Bash-скрипт для отслеживания новых релизов в GitHub-репозиториях.<\/p>\n<p>Раз в сутки проверяет заданный список проектов через GitHub API и отправляет уведомления в Telegram: версия, ссылка и краткий список изменений.<\/p>\n<p>Поддерживает SOCKS5-прокси, хранит историю обработанных релизов, умеет работать в режиме dry-run. Конфигурация — JSON и .env. Зависимости — Bash, curl и jq.<\/p>\n<p>Сейчас используется для мониторинга Lychee, Firefly III и Memos.<\/p>\n<p>Никаких постоянно работающих сервисов: обычный скрипт, запускаемый через cron.<\/p>\n<p><a href=\"https:\/\/egit.m9k.space\/m9k\/github-release-monitor\">Репа<\/a><\/p>\n",
            "summary": "Очередной небольшой эксперимент с вайбкодингом — Bash-скрипт для отслеживания новых релизов в GitHub-репозиториях...",
            "date_published": "2026-10-11T21:58:38+07:00",
            "date_modified": "2026-10-11T21:58:35+07:00",
            "tags": [
                "github",
                "linux",
                "opensource",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Sun, 11 Oct 2026 21:58:38 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "251",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "250",
            "url": "https:\/\/blog.m9k.space\/all\/migraciya-lychee-6-7-v-docker-lxc\/",
            "title": "Миграция Lychee 6 -> 7 в Docker (LXC)",
            "content_html": "<p>Развлекался намедни обновлением Lychee с миграцией с базового LXC-контейнера Debian 12 -> Debian 13<br \/>\nИсходно: <b>Lychee 6.10.3<\/b> в Debian 12 LXC. База — MariaDB 10.<br \/>\nДанные приложения находились в:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">\/lychee\/conf\n\/lychee\/uploads\n\/lychee\/sym\n\/lychee\/pictures\n\/lychee\/db<\/code><\/pre><p>Цель: обновить приложение и MariaDB  10->11<br \/>\nВ Lychee 7 изменилась структура каталогов внутри контейнера (см. <a href=\"https:\/\/lycheeorg.dev\/docs\/administration\/upgrade\/\">Update Guide<\/a>))<\/p>\n<p>Новая схема:<\/p>\n<pre class=\"e2-text-code\"><code class=\"yaml\">volumes:\n  - .\/lychee\/uploads:\/app\/public\/uploads\n  - .\/lychee\/logs:\/app\/storage\/logs\n  - .\/lychee\/tmp:\/app\/storage\/tmp\n  - .\/lychee\/storage\/app:\/app\/storage\/app<\/code><\/pre><p>База смонтирована в локальную директорию, но т. к. хочу поднять версию базы, делаем миграцию через дамп.<\/p>\n<pre class=\"e2-text-code\"><code class=\"bash\">docker exec lychee_db \\\n  mysqldump \\\n  -u root \\\n  -p&#039;PASSWORD&#039; \\\n  --single-transaction \\\n  lychee &gt; \/lychee\/lychee.sql<\/code><\/pre><p>Переносим всё другой сервер, восстанавливаем.<\/p>\n<h2>APP_KEY<\/h2>\n<p>В Lychee 7 обязателен `APP_KEY`. Без него контейнер завершается с ошибкой:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">ERROR: APP_KEY is not set<\/code><\/pre><p>Ключ можно сгенерировать:<\/p>\n<pre class=\"e2-text-code\"><code class=\"bash\">echo &quot;base64:$(openssl rand -base64 32)&quot;<\/code><\/pre><p>и передать контейнеру:<\/p>\n<pre class=\"e2-text-code\"><code class=\"yaml\">environment:\n  APP_KEY: &quot;base64:...&quot;<\/code><\/pre><h2>Восстановление базы<\/h2>\n<pre class=\"e2-text-code\"><code class=\"bash\">docker compose up -d lychee_db<\/code><\/pre><p>Восстанавливаем дамм.<\/p>\n<pre class=\"e2-text-code\"><code class=\"bash\">docker exec -i lychee_db \\\n  mariadb \\\n  -u root \\\n  -p&#039;PASSWORD&#039; \\\n  lychee &lt;lychee.sql<\/code><\/pre><p>В MariaDB 11 используется клиент `mariadb`, а не старое имя `mysql`.<\/p>\n<h2>Пересчёт статистики<\/h2>\n<p>После успешного запуска Lychee 7 стоит пересчитать размеры альбомов и thumbnails<\/p>\n<pre class=\"e2-text-code\"><code class=\"bash\">docker exec -it lychee php artisan lychee:recompute-album-sizes<\/code><\/pre><p>и статистику альбомов:<\/p>\n<pre class=\"e2-text-code\"><code class=\"bash\">docker exec -it lychee php artisan lychee:recompute-album-stats<\/code><\/pre><h2>Reverse proxy<\/h2>\n<p>После миграции при работе через Nginx возникала плавающая 502, причём выглядела она интересно — на одном и том же ПК веб-морда могла открывать в одном браузере, и не открывать в другом. В логах:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">upstream sent too big header while reading response header from upstream<\/code><\/pre><p>Проблема решилась увеличением proxy buffers:<\/p>\n<!-- Lychee Photo Album Embed v1.0.0 --><link rel=\"stylesheet\" href=\"https:\/\/lychee.m9k.space\/embed\/lychee-embed.css?v=1.0.0\">\n<script src=\"https:\/\/lychee.m9k.space\/embed\/lychee-embed.js?v=1.0.0\"><\/script>\n\n<div\n    data-lychee-embed\n    data-api-url=\"https:\/\/lychee.m9k.space\"\n    data-mode=\"album\"\n    data-album-id=\"Nym-osIiRs6lKn48wlXW9-zt\"\n    data-layout=\"square\"\n    data-spacing=\"8\"\n    data-target-row-height=\"200\"\n    data-target-column-width=\"200\"\n    data-max-photos=\"12\"\n    data-sort-order=\"asc\"\n    data-header-placement=\"none\"\n><\/div>",
            "summary": "Развлекался намедни обновлением Lychee с миграцией с базового LXC-контейнера Debian 12 -> Debian 13",
            "date_published": "2026-10-04T11:49:58+07:00",
            "date_modified": "2026-10-04T11:53:24+07:00",
            "tags": [
                "linux",
                "Lychee",
                "системное администрирование",
                "фотография"
            ],
            "_date_published_rfc2822": "Sun, 04 Oct 2026 11:49:58 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "250",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "248",
            "url": "https:\/\/blog.m9k.space\/all\/egeya-hroniki-obnovleniya\/",
            "title": "Эгея. Хроники обновления.",
            "content_html": "<p>Когда-то, в далёкие времена ковида и окончательного огосударствления ВК, посетила меня идея поднять свой бложик на Aegea. Поднимал его я на актуальном тогда Proxmox 5, ещё ого-го как живом CentOS 7 в LXC. Внутри была, страшно сказать, MariaDB 5 и несвежий апачик. Утекло с тех лет много воды: контейнер несколько раз менял хостовый Proxmox, но внутрянку я не трогал.<\/p>\n<p>И тут зачесались руки привести историческую инфраструктуру в CI-ный вид: плейбук там, плейбук тут.<\/p>\n<p>База — Debian 13, обновление с миграцией на свежую версию движка, потому что с 2020 года Aegea я обновлял всего один раз.<\/p>\n<p>Вообще, это заметка должна была быть одой LLM и <s>нытьём<\/s> на тему того, что нейросети заменят админодевопсов нафиг.<\/p>\n<p>И, в общем и целом, я был в восторге. Написав некоторую длинную портянку текста о том, что, как и почему, я получил длинный плейбук, который, без шуток, выполнился с первого захода. И это с учётом того, что исходный сервер настраивал админ с сильно меньшим пониманием жизни и профессии.<\/p>\n<p>Отрефлексировать я это отрефлексировал тем, что ТЗ было составлено с кучей ограничений, которые человек без опыта ковыряния во всём этом просто не сформулировал бы. Машине скормили хороший запрос — машина выдала хороший результат. Плейбук был запущен, и через пару минут я получил рабочий блог.<\/p>\n<p>Но рефлексия не удалась.<\/p>\n<p>При попытке загрузить картинку в новый пост ловлю: «не удалось зарегистрировать загруженный файл».<br \/>\nВ nginx access log:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">POST \/@ajax\/file-upload\/?entity=note&amp;entity-id=247 HTTP\/1.1&quot; 200<\/code><\/pre><p>При этом в \/var\/www\/blog\/pictures появлялись временные файлы:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">-rw------- 1 www-data www-data 0 upload-XXXXXX<\/code><\/pre><p>Очевидное: место, права, лимиты на аплоад php, просто загрузку — проверил. PHP корректно получал файл:<br \/>\nДвижок в фришной версии обфусцирован, разбираться в коде — неблагодарное дело — мы холопоадмины, наше дело логи читать. Достаём BFG.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">strace -ff $(ps aux | awk &#039;\/php-fpm: pool www\/ &amp;&amp; !\/awk\/ {printf &quot;-p %s &quot;, $2}&#039;) -o \/tmp\/egeya-upload.strace<\/code><\/pre><p>Проблема раз:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">rename(&quot;\/tmp\/phpXXXX&quot;, &quot;\/var\/www\/blog\/pictures\/upload-XXXX&quot;) = -1 EXDEV<\/code><\/pre><p>Ошибка:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">EXDEV (Invalid cross-device link)<\/code><\/pre><p>означает попытку `rename()` между разными файловыми системами.<\/p>\n<p>Смотрю на вывод df -T в исходной системе, и в target — и понимаю, что Debian (не)смог. \/tmp теперь не реальный каталог, а временная фс tmpfs в памяти<\/p>\n<p>Схема проблемы:<br \/>\n\/tmp<br \/>\n|<br \/>\n| tmpfs<br \/>\n|<br \/>\nX  rename()<br \/>\n|<br \/>\n\/var\/www\/blog\/pictures<br \/>\n|<br \/>\n| root filesystem<\/p>\n<p>PHP создавал временный upload-файл в \/tmp.<br \/>\nЭгея использовала move_uploaded_file(), который внутри PHP может выполнить быстрый rename().<br \/>\nНо rename(), как оказалось, между разными файловыми системами невозможен.<br \/>\nБахнул отдельный временный каталог на той же файловой системе, где и бложик.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">mkdir -p \/var\/www\/blog\/tmp\nchown www-data:www-data \/var\/www\/blog\/tmp\nchmod 700 \/var\/www\/blog\/tmp<\/code><\/pre><p>В PHP-FPM :<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">upload_tmp_dir = \/var\/www\/blog\/tmp<\/code><\/pre><p>После перезапуска:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">systemctl restart php8.2-fpm<\/code><\/pre><p>цепочка стала:<br \/>\n\/var\/www\/blog\/tmp\/phpXXXX<br \/>\n|<br \/>\n| rename()<br \/>\nv<br \/>\n\/var\/www\/blog\/pictures\/upload-XXXX<br \/>\nОшибка EXDEV исчезла — но аплоад не заработал.<br \/>\nДостаём BFG второй раз.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">strace -ff $(ps aux | awk &#039;\/php-fpm: pool www\/ &amp;&amp; !\/awk\/ {printf &quot;-p %s &quot;, $2}&#039;) -o \/tmp\/egeya-upload.strace<\/code><\/pre><p>Видим, что Эгея хочет поделиться тайнами мироздания в лог, но не может. Чиним и это.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">touch \/var\/www\/blog\/user\/logs\/main.log<\/code><\/pre><p>Повторяем аплоад. В логе:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">Process image: Done\nRegister upload: &lt;, , add, Array&gt;\nAjax file upload: couldn’t register upload<\/code><\/pre><p>Явно не по фэншую, не хватает параметров. Смотрим консоль отладки. Должны быть entity и entity-id<br \/>\nСмотрим в nginx. Конфиг нагенерила LLM по мотивам того, что было в сети — и он был рабочий...формально.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">location \/ {\n    log_not_found off;\n    try_files $uri $uri\/ \/index.php?go=$uri;\n}<\/code><\/pre><p>Запрос \/@ajax\/file-upload\/?entity=note&entity-id=247 после try_files превращался в \/index.php?go=\/@ajax\/file-upload\/. Параметры говорили «Мұнда параметрлер жоқ қой, иттер»<br \/>\nМеняем чудо-LLM на рабочее:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">location \/ {\n    log_not_found off;\n    rewrite ^(.*)$ \/index.php?go=$1 last;\n}<\/code><\/pre><p>Смысл изменения:<br \/>\ntry_files при внутреннем перенаправлении формирует новый URI и может потерять исходную строку параметров.<br \/>\nrewrite ... last выполняет внутренний rewrite с сохранением исходных GET-параметров. Рестартуем Nginx.<\/p>\n<p>Бинго!<\/p>\n",
            "summary": "Когда-то, в далёкие времена ковида и окончательного огосударствления ВК, посетила меня идея поднять свой бложик на Aegea",
            "date_published": "2026-09-25T23:22:24+07:00",
            "date_modified": "2026-09-26T09:43:00+07:00",
            "tags": [
                "Aegea",
                "Debian 13",
                "системное администрирование",
                "Эгея"
            ],
            "_date_published_rfc2822": "Fri, 25 Sep 2026 23:22:24 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "248",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "244",
            "url": "https:\/\/blog.m9k.space\/all\/sozdanie-polzovateley-v-debian-i-astra-linux\/",
            "title": "Создание пользователей в Debian и Astra Linux",
            "content_html": "<p>Cкрипт для создания локальных пользователей с настройкой passwordless sudo.<br \/>\nОпределяет дистрибутив через \/etc\/os-release. Для Astra Linux дополнительно выполняет pdpl-user -l 0:63 -i 63.<br \/>\nПроверяет наличие утилит, не пересоздаёт существующих пользователей и не изменяет их пароли. Конфигурация sudo проверяется через visudo. Поддерживает повторный запуск.<br \/>\nЗапуск:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">.\/create-users.sh user1 user2 user3<\/code><\/pre><p><a href=\"https:\/\/egit.m9k.space\/m9k\/blog_data\/src\/branch\/main\/scripts\/Base\/debian-astra-create-passwordless-user.sh\">скрипт<\/a><\/p>\n",
            "summary": "Cкрипт для создания локальных пользователей с настройкой passwordless sudo",
            "date_published": "2026-09-23T09:51:16+07:00",
            "date_modified": "2026-09-27T21:33:31+07:00",
            "tags": [
                "Astra",
                "debian",
                "linux",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Wed, 23 Sep 2026 09:51:16 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "244",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "242",
            "url": "https:\/\/blog.m9k.space\/all\/git-prevraschaem-vlozhenny-repozitoriy-v-obychnuyu-direktoriyu\/",
            "title": "Git: превращаем вложенный репозиторий в обычную директорию",
            "content_html": "<p>Если скопировать один Git-проект внутрь другого и выполнить commit, Git сохранит вложенный проект как gitlink (режим 160000). В GitLab вместо содержимого директории будет отображаться ссылка на commit:<br \/>\ninit-server @ 957c7776<br \/>\nПростое удаление вложенного .git не поможет: ссылка уже записана в индексе родительского репозитория.<br \/>\nРешение:<\/p>\n<ol start=\"1\">\n<li>Удаляем gitlink из индекса, сохраняя файлы на диске:<\/li>\n<\/ol>\n<pre class=\"e2-text-code\"><code class=\"\">git rm --cached init-server<\/code><\/pre><ol start=\"2\">\n<li>Удаляем метаданные вложенного репозитория, если они ещё существуют:<\/li>\n<\/ol>\n<pre class=\"e2-text-code\"><code class=\"\">rm -rf init-server\/.git<\/code><\/pre><ol start=\"3\">\n<li>Добавляем содержимое как обычные файлы:<\/li>\n<\/ol>\n<pre class=\"e2-text-code\"><code class=\"\">git add -A init-server\/<\/code><\/pre><ol start=\"4\">\n<li>Проверяем:<\/li>\n<\/ol>\n<pre class=\"e2-text-code\"><code class=\"\">git ls-files --stage init-server<\/code><\/pre><p>Вместо записи с режимом 160000 должны появиться отдельные файлы с режимами 100644 или 100755.<\/p>\n<ol start=\"5\">\n<li>Фиксируем изменения:<\/li>\n<\/ol>\n<pre class=\"e2-text-code\"><code class=\"\">git commit -m &quot;Convert init-server to regular directory&quot;\ngit push<\/code><\/pre>",
            "summary": "Если скопировать один Git-проект внутрь другого и выполнить commit, Git сохранит вложенный проект как gitlink...",
            "date_published": "2026-09-21T10:52:38+07:00",
            "date_modified": "2026-09-21T10:52:23+07:00",
            "tags": [
                "git",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Mon, 21 Sep 2026 10:52:38 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "242",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "240",
            "url": "https:\/\/blog.m9k.space\/all\/rootless-podman-vnutri-proxmox-lxc-problema-uid-gid-mapping\/",
            "title": "Rootless Podman внутри Proxmox LXC: проблема UID\/GID mapping",
            "content_html": "<p>При запуске File Browser через rootless Podman образ скачивался, но распаковка завершалась ошибкой:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">potentially insufficient UIDs or GIDs available in user namespace (requested 65534:65534)<\/code><\/pre><pre class=\"e2-text-code\"><code class=\"\">lchown \/home: invalid argument<\/code><\/pre><p>Причина: пользователю podman выделено только 50 000 subordinate UID\/GID. Образ содержит файлы с UID\/GID 65534, которые не помещаются в доступный диапазон.<\/p>\n<p>Механизм<\/p>\n<p>Используются два уровня отображения идентификаторов:<br \/>\nPodman: UID\/GID образа → UID\/GID внутри Debian LXC.<br \/>\nLXC: UID\/GID Debian → UID\/GID хоста Proxmox.<\/p>\n<p>Настройки Podman определяются файлами \/etc\/subuid и \/etc\/subgid. Внешний mapping LXC задаётся через lxc.idmap.<\/p>\n<p>Исходная конфигурация Proxmox:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">root:100000:65536<\/code><\/pre><pre class=\"e2-text-code\"><code class=\"\">deploy:165536:65536<\/code><\/pre><p>Непривилегированному LXC доступны 65 536 идентификаторов. Расширить диапазон непрерывно нельзя: следующие идентификаторы уже выделены пользователю deploy.<\/p>\n<p>Решение:<\/p>\n<p>Выделить свободный диапазон на Proxmox и добавить его в \/etc\/subuid и \/etc\/subgid хоста:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">root:231072:15536<\/code><\/pre><p>В конфигурации LXC сохранить исходное отображение и добавить новое:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">lxc.idmap = u 0 100000 65536<\/code><\/pre><pre class=\"e2-text-code\"><code class=\"\">lxc.idmap = g 0 100000 65536<\/code><\/pre><pre class=\"e2-text-code\"><code class=\"\">lxc.idmap = u 65536 231072 15536<\/code><\/pre><pre class=\"e2-text-code\"><code class=\"\">lxc.idmap = g 65536 231072 15536<\/code><\/pre><p>Внутри LXC контейнера дополнить существующий диапазон пользователя podman:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">podman:10000:50000<\/code><\/pre><pre class=\"e2-text-code\"><code class=\"\">podman:65536:15536<\/code><\/pre><p>Настроить аналогично UID и GID, предварительно проверив отсутствие пересечений с другими диапазонами. После остановки приложений перезапустить LXC и выполнить:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">podman system migrate<\/code><\/pre><p>Изменение mapping не переносит владельцев существующих файлов автоматически.<\/p>\n",
            "summary": "При запуске File Browser через rootless Podman образ скачивался, но распаковка завершалась ошибкой",
            "date_published": "2026-09-19T14:29:49+07:00",
            "date_modified": "2026-09-21T09:58:16+07:00",
            "tags": [
                "mapping",
                "podman",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Sat, 19 Sep 2026 14:29:49 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "240",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "239",
            "url": "https:\/\/blog.m9k.space\/all\/proxmox-7-vklyuchenie-docker-i-podman-v-lxc\/",
            "title": "Proxmox 7: включение Docker и Podman в LXC",
            "content_html": "<p>Для запуска Docker или Podman внутри LXC-контейнера необходимо активировать дополнительные возможности: nesting, keyctl и fuse.<\/p>\n<p>Скрипт запрашивает ID контейнера, проверяет наличие конфигурации, создаёт резервную копию с меткой времени и включает необходимые параметры. Остальные настройки features сохраняются.<\/p>\n<p>Запускать от root на хосте Proxmox.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">#!\/bin\/bash\n\nread -rp &quot;Введите ID контейнера (CTID): &quot; CTID\n\nif [[ ! &quot;$CTID&quot; =~ ^[0-9]+$ || ! -f &quot;\/etc\/pve\/lxc\/${CTID}.conf&quot; ]]; then\n    echo &quot;Ошибка: некорректный CTID или конфигурация не найдена&quot;\n    exit 1\nfi\n\nCONF=&quot;\/etc\/pve\/lxc\/${CTID}.conf&quot;\nBACKUP_DIR=&quot;\/root\/lxc-config-backups&quot;\n\nmkdir -p &quot;$BACKUP_DIR&quot; &amp;&amp;\ncp &quot;$CONF&quot; &quot;$BACKUP_DIR\/${CTID}.conf.$(date +%Y%m%d_%H%M%S).bak&quot; &amp;&amp;\npct set &quot;$CTID&quot; --features &quot;$(awk -F, &#039;\/^features:\/{sub(\/^features:[[:space:]]*\/, &quot;&quot;); for(i=1;i&lt;=NF;i++) if($i !~ \/^(nesting|keyctl|fuse)=\/) printf &quot;%s,&quot;, $i} END{print &quot;nesting=1,keyctl=1,fuse=1&quot;}&#039; &quot;$CONF&quot;)&quot; &amp;&amp;\npct config &quot;$CTID&quot; | grep &#039;^features:&#039;<\/code><\/pre><p>Пример выполнения:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">Введите ID контейнера (CTID): 177\nfeatures: fuse=1,keyctl=1,nesting=1<\/code><\/pre><p>Резервная копия конфигурации сохраняется в \/root\/lxc-config-backups\/.<\/p>\n<p>После изменения параметров необходимо полностью остановить и запустить контейнер:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">pct stop 177 &amp;&amp; pct start 177<\/code><\/pre><p>Update:<br \/>\nЕщё одна ошибка:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">Failed to open() \/dev\/net\/tun: No such file or directory\nFailed to set up tap device in namespace<\/code><\/pre><p>Чиним:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">#!\/bin\/bash\nset -euo pipefail\n\nCTID=178\nCONF=&quot;\/etc\/pve\/lxc\/${CTID}.conf&quot;\nBACKUP=&quot;${CONF}.$(date +%Y%m%d%H%M%S).bkp&quot;\n\nmodprobe tun\ntest -c \/dev\/net\/tun\n\ncp -a &quot;$CONF&quot; &quot;$BACKUP&quot;\n\nRULES=(\n    &quot;lxc.cgroup2.devices.allow: c 10:200 rwm&quot;\n    &quot;lxc.mount.entry: \/dev\/net\/tun dev\/net\/tun none bind,create=file&quot;\n)\n\nfor rule in &quot;${RULES[@]}&quot;; do\n    grep -Fqx -- &quot;$rule&quot; &quot;$CONF&quot; || printf &#039;%s\\n&#039; &quot;$rule&quot; &gt;&gt; &quot;$CONF&quot;\ndone\n\necho &quot;Backup: $BACKUP&quot;\necho &quot;Configuration:&quot;\ngrep -E &#039;^lxc\\.(cgroup2\\.devices\\.allow|mount\\.entry):&#039; &quot;$CONF&quot;<\/code><\/pre>",
            "summary": "Для запуска Docker или Podman внутри LXC-контейнера необходимо активировать дополнительные возможности",
            "date_published": "2026-09-19T10:20:57+07:00",
            "date_modified": "2026-09-19T21:20:16+07:00",
            "tags": [
                "docker",
                "linux",
                "lxc",
                "podman",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Sat, 19 Sep 2026 10:20:57 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "239",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "238",
            "url": "https:\/\/blog.m9k.space\/all\/prettymaps-2\/",
            "title": "Prettymaps в Docker: генератор картографических постеров",
            "content_html": "<div class=\"e2-text-picture\">\n<img src=\"https:\/\/blog.m9k.space\/pictures\/akademgorodok.png\" width=\"1000\" height=\"1045\" alt=\"\" \/>\n<\/div>\n<p>Поигрался с либой <a href=\"https:\/\/github.com\/marceloprates\/prettymaps\">PrettyMaps<\/a>, которая умеет строить из OSM прикольные постеры.<\/p>\n<p>Собрал контейнер, который позволяет сделать карту.<\/p>\n<p>Сборка без прокси:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">DOCKER_BUILDKIT=1 docker build -t prettymaps-docker-test .<\/code><\/pre><p>Сборка с SOCKS5-прокси:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">DOCKER_BUILDKIT=1 docker build --network=host --build-arg PROXY_SOCKS5=$PROXY_IP:$PROXY_PORT -t prettymaps-docker-test .<\/code><\/pre><p>Запуск:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">docker run --rm \\\n  -v &quot;$(pwd)&quot;:\/work \\\n  -e LAT=54.8482742 \\\n  -e LON=83.0973263 \\\n  -e RADIUS_M=1500 \\\n  -e TITLE=&quot;Академгородок&quot; \\\n  -e OUTPUT_NAME=akadem_coords \\\n  prettymaps-docker-test<\/code><\/pre><p><a href=\"https:\/\/egit.m9k.space\/m9k\/blog_data\/src\/commit\/a8d511f0464360c4696015f1373bf0d49415df00\/PrettyMaps\">Файлы <\/a><\/p>\n",
            "summary": "Поигрался с либой PrettyMaps, которая умеет строить из OSM прикольные постеры",
            "date_published": "2026-09-18T19:57:51+07:00",
            "date_modified": "2026-09-27T20:43:59+07:00",
            "tags": [
                "prettymaps",
                "python",
                "Карты",
                "постер",
                "системное администрирование"
            ],
            "image": "https:\/\/blog.m9k.space\/pictures\/akademgorodok.png",
            "_date_published_rfc2822": "Fri, 18 Sep 2026 19:57:51 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "238",
            "_e2_data": {
                "is_favourite": true,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/blog.m9k.space\/pictures\/akademgorodok.png"
                ]
            }
        },
        {
            "id": "236",
            "url": "https:\/\/blog.m9k.space\/all\/prinuditelny-full-gc-java-processov-po-cron\/",
            "title": "Принудительный Full GC Java-процессов по cron",
            "content_html": "<pre class=\"e2-text-code\"><code class=\"\">0 3 * * * for pid in $(pgrep -u spring -x java); do java_bin=$(readlink -f &quot;\/proc\/$pid\/exe&quot;) &amp;&amp; jcmd=&quot;$(dirname &quot;$java_bin&quot;)\/jcmd&quot; &amp;&amp; [ -x &quot;$jcmd&quot; ] &amp;&amp; &quot;$jcmd&quot; &quot;$pid&quot; GC.run; done &gt;\/dev\/null 2&gt;&amp;1<\/code><\/pre><p>Суть:<\/p>\n<p>pgrep -u spring -x java находит все процессы с именем java, принадлежащие пользователю spring.<\/p>\n<p>Для каждого PID через \/proc\/$pid\/exe определяется реальный исполняемый файл Java.<\/p>\n<p>Рядом с найденным java ищется соответствующий jcmd:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">jcmd=&quot;$(dirname &quot;$java_bin&quot;)\/jcmd&quot;<\/code><\/pre><p>Если jcmd существует и исполняемый, выполняется:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">jcmd PID GC.run<\/code><\/pre><p>Команда инициирует Full GC внутри соответствующей JVM.<\/p>\n<p>jcmd должен иметь возможность подключиться к JVM. Сron следует запускать от пользователя spring или от root. Если jcmd отсутствует рядом с используемым java, эта JVM будет молча пропущена.<\/p>\n",
            "summary": "Суть",
            "date_published": "2026-09-17T16:57:50+07:00",
            "date_modified": "2026-09-17T16:57:47+07:00",
            "tags": [
                "Java",
                "jcmd",
                "linux",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Thu, 17 Sep 2026 16:57:50 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "236",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "228",
            "url": "https:\/\/blog.m9k.space\/all\/kak-vremenno-rasshirit-zfs-pul-s-pomoschyu-zvol-iz-drugogo-pula\/",
            "title": "Как временно расширить ZFS-пул с помощью zvol из другого пула",
            "content_html": "<p>Иногда ZFS-пул оказывается заполнен почти полностью, но переносить данные на другой файловый раздел неудобно или слишком долго.<\/p>\n<p>Было два независимых пула:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zpool list\n\nNAME    SIZE  ALLOC   FREE  FRAG  CAP  HEALTH\ndb     11.7T  8.99T  2.71T   18%  76%  ONLINE\nlimon  79.5G  77.0G  2.46G   90%  96%  ONLINE<\/code><\/pre><p>Пул limon закончился, а в пуле db оставалось несколько терабайт свободного пространства.<\/p>\n<p>Требовалось временно дать пулу limon около 10 ГБ, очистить логи и затем вернуть конфигурацию в исходное состояние.<\/p>\n<h3>Почему нельзя просто передать свободное место?<\/h3>\n<p>Свободное пространство принадлежит конкретному ZFS-пулу. Датасет одного пула не может непосредственно использовать место из другого пула.<\/p>\n<h3>Топология исходного пула<\/h3>\n<p>Пул limon состоял из одного раздела:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zpool status limon\n\npool: limon\nstate: ONLINE\nconfig:\n\n    NAME        STATE     READ WRITE CKSUM\n    limon       ONLINE       0     0     0\n      sdd1      ONLINE       0     0     0<\/code><\/pre><p>Для последующего удаления временного vdev необходимо проверить поддержку функции device_removal:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zpool get feature@device_removal limon<\/code><\/pre><p>В моём случае функция была включена:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">NAME   PROPERTY                VALUE    SOURCE\nlimon  feature@device_removal  enabled  local<\/code><\/pre><p>Также необходимо определить фактическое значение ashift существующего vdev.<\/p>\n<p>Команда:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zpool get ashift limon<\/code><\/pre><p>может показать:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">limon  ashift  0  default<\/code><\/pre><p>Значение 0 означает автоматический выбор для новых устройств и не показывает фактический ashift уже существующего vdev.<\/p>\n<p>Реальное значение можно получить через zdb:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zdb -C limon | grep -E &#039;path|ashift&#039;<\/code><\/pre><p>Результат:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">path: &#039;\/dev\/sdd1&#039;\nphys_path: &#039;acpi-VMBUS:01-vmbus-3d4511ba2bac43a19298b9d3bddf7b27-lun-2&#039;\nashift: 12<\/code><\/pre><p>Таким образом, временный vdev тоже нужно добавлять с ashift=12.<\/p>\n<h3>Важное предупреждение<\/h3>\n<p>Описанный способ создаёт зависимость одного ZFS-пула от другого.<\/p>\n<p>Пока временный zvol входит в пул limon:<\/p>\n<ul>\n<li>пул db нельзя экспортировать;<\/li>\n<li>zvol нельзя удалить;<\/li>\n<li>сервер нельзя загружать без доступного пула db;<\/li>\n<li>потеря пула db может привести к потере всего пула limon<br \/>\nЭто аварийная временная мера, а не постоянная архитектура хранения.<\/li>\n<\/ul>\n<h3>Создание временного zvol<\/h3>\n<p>Чтобы не повторять изменяемые значения в командах, зададим переменные:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">POOL_SOURCE=&quot;db&quot;\nPOOL_TARGET=&quot;limon&quot;\nZVOL_NAME=&quot;limon-temp-vdev&quot;\nZVOL_SIZE=&quot;10G&quot;\nZVOL_DATASET=&quot;${POOL_SOURCE}\/${ZVOL_NAME}&quot;\nZVOL_DEVICE=&quot;\/dev\/zvol\/${ZVOL_DATASET}&quot;\nASHIFT=&quot;12&quot;\nVOLBLOCKSIZE=&quot;16K&quot;<\/code><\/pre><p>Создаём обычный, неразреженный zvol:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zfs create -V &quot;${ZVOL_SIZE}&quot; -b &quot;${VOLBLOCKSIZE}&quot; -o compression=off &quot;${ZVOL_DATASET}&quot;<\/code><\/pre><p>Проверяем созданный zvol:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zfs get volsize,volblocksize,refreservation,compression &quot;${ZVOL_DATASET}&quot;<\/code><\/pre><p>Проверяем наличие блочного устройства:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">udevadm settle\nls -l &quot;${ZVOL_DEVICE}&quot;\nblockdev --getsize64 &quot;${ZVOL_DEVICE}&quot;<\/code><\/pre><p>У обычного zvol параметр refreservation должен быть примерно равен его размеру. Это гарантирует, что необходимые 10 ГБ действительно зарезервированы в пуле db.<\/p>\n<h3>Проверка добавления без изменения пула<\/h3>\n<p>Сначала выполняем пробный запуск:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zpool add -n -o ashift=&quot;${ASHIFT}&quot; &quot;${POOL_TARGET}&quot; &quot;${ZVOL_DEVICE}&quot;<\/code><\/pre><p>В предполагаемой конфигурации исходный диск и временный zvol должны находиться на одном уровне:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">limon\nsdd1\n\/dev\/zvol\/db\/limon-temp-vdev<\/code><\/pre><h3>Добавление временного пространства<\/h3>\n<p>Если пробная проверка не показывает ошибок, добавляем zvol:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zpool add -o ashift=&quot;${ASHIFT}&quot; &quot;${POOL_TARGET}&quot; &quot;${ZVOL_DEVICE}&quot;<\/code><\/pre><p>После этого проверяем состояние:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zpool status -P &quot;${POOL_TARGET}&quot;\nzpool list &quot;${POOL_TARGET}&quot;\nzfs list &quot;${POOL_TARGET}&quot;<\/code><\/pre><p>Дополнительно проверяем ashift обоих vdev:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zdb -C &quot;${POOL_TARGET}&quot; | grep -E &#039;path|ashift&#039;<\/code><\/pre><p>Оба top-level vdev должны иметь ashift=12. Разный ashift может помешать последующему удалению временного устройства.<\/p>\n<p>После добавления zvol пул limon получит немного меньше 10 ГБ полезного пространства из-за внутренних накладных расходов ZFS.<\/p>\n<h3>Удаление временного vdev<\/h3>\n<p>Сначала проверяем возможность удаления:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zpool remove -n &quot;${POOL_TARGET}&quot; &quot;${ZVOL_DEVICE}&quot;<\/code><\/pre><p>Параметр -n не выполняет полноценную симуляцию переноса данных. Он используется для оценки параметров операции, включая необходимый объём памяти для таблиц перенаправления.<\/p>\n<p>Если ошибок нет, запускаем удаление с ожиданием завершения:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zpool remove -w &quot;${POOL_TARGET}&quot; &quot;${ZVOL_DEVICE}&quot;<\/code><\/pre><p>Во время удаления ZFS переносит блоки с временного zvol обратно на исходный диск sdd1.<\/p>\n<p>За процессом можно наблюдать в другом терминале:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">watch -n 5 &quot;zpool status ${POOL_TARGET}; zpool list ${POOL_TARGET}&quot;<\/code><\/pre><p>До полного завершения операции нельзя:<\/p>\n<ul>\n<li>уничтожать zvol;<\/li>\n<li>экспортировать пул db;<\/li>\n<li>отключать устройство;<\/li>\n<li>перезагружать систему без необходимости.<\/li>\n<\/ul>\n<p>После успешного удаления проверяем, что в пуле остался только исходный vdev:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zpool status -P &quot;${POOL_TARGET}&quot;<\/code><\/pre><p>В конфигурации должен снова отображаться только sdd1.<\/p>\n<h3>Удаление временного zvol<\/h3>\n<p>Только после успешного завершения zpool remove можно уничтожить zvol:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zfs destroy &quot;${ZVOL_DATASET}&quot;<\/code><\/pre><p>Финальная проверка:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">zpool status &quot;${POOL_TARGET}&quot;\nzpool list &quot;${POOL_TARGET}&quot;\nzfs list &quot;${POOL_SOURCE}&quot; &quot;${POOL_TARGET}&quot;<\/code><\/pre>",
            "summary": "Иногда ZFS-пул оказывается заполнен почти полностью, но переносить данные на другой файловый раздел неудобно или слишком долго",
            "date_published": "2026-07-21T16:36:32+07:00",
            "date_modified": "2026-09-18T23:28:31+07:00",
            "tags": [
                "linux",
                "ZFS",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Tue, 21 Jul 2026 16:36:32 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "228",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "227",
            "url": "https:\/\/blog.m9k.space\/all\/v-terminale-linux-propal-tekst-vernite-echo\/",
            "title": "Проблемы с отображение текста в консоли",
            "content_html": "<p>Если терминал выполняет команды, но не отображает вводимый текст, восстановите отображение:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">stty echo<\/code><\/pre><p>Причина — отключённый режим echo, например после некорректного завершения программы, скрывавшей ввод пароля.<\/p>\n<p>Если проблема сохраняется, сбросьте настройки терминала:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">stty sane<\/code><\/pre>",
            "summary": "Если терминал выполняет команды, но не отображает вводимый текст, восстановите отображение",
            "date_published": "2026-07-21T15:40:59+07:00",
            "date_modified": "2026-09-19T10:03:48+07:00",
            "tags": [
                "linux",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Tue, 21 Jul 2026 15:40:59 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "227",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "224",
            "url": "https:\/\/blog.m9k.space\/all\/clash-verge-rev-local-tunnel\/",
            "title": "Clash Verge Rev Local Tunnel",
            "content_html": "<p>Получив намедни на локальной станции отвал сразу нескольких утилит, которые умеют пробрасывать трафик через TUN-режим, я пошёл искать альтернативы. В итоге нашлась Clash Verge:<br \/>\n<a href=\"https:\/\/github.com\/clash-verge-rev\/clash-verge-rev\">https:\/\/github.com\/clash-verge-rev\/clash-verge-rev<\/a><\/p>\n<p>Задача простая: завернуть трафик через TUN в локальный прокси.<\/p>\n<p>Идём по пути:<\/p>\n<p>Профили -> Новый -> Local -> Выбираем файл<\/p>\n<p>Дальше скармливаем Clash Verge YAML-конфиг:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">mixed-port: 7890\nallow-lan: false\nmode: global\nlog-level: info\nipv6: false\n\ntun:\n  enable: true\n  stack: mixed\n  auto-route: true\n  auto-detect-interface: true\n  strict-route: true\n  dns-hijack:\n    - any:53\n    - tcp:\/\/any:53\n\ndns:\n  enable: true\n  ipv6: false\n  enhanced-mode: fake-ip\n  listen: 127.0.0.1:1053\n  default-nameserver:\n    - 1.1.1.1\n    - 8.8.8.8\n  nameserver:\n    - 1.1.1.1\n    - 8.8.8.8\n  fake-ip-filter:\n    - &quot;*.lan&quot;\n    - &quot;*.local&quot;\n    - &quot;localhost&quot;\n\nproxies:\n  - name: lan-socks5\n    type: socks5\n    server: %IP прокси%\n    port: %порт прокси%\n    udp: true\n\nproxy-groups:\n  - name: PROXY\n    type: select\n    proxies:\n      - lan-socks5\n      - DIRECT\n\nrules:\n  - IP-CIDR,%IP прокси%\/32,DIRECT,no-resolve\n  - IP-CIDR,192.168.0.0\/16,DIRECT,no-resolve\n  - IP-CIDR,10.0.0.0\/8,DIRECT,no-resolve\n  - IP-CIDR,172.16.0.0\/12,DIRECT,no-resolve\n  - MATCH,PROXY<\/code><\/pre><p>После этого можно на главной включать Tun.<br \/>\nВ результате весь трафик, кроме локальных сетей и самого прокси-сервера, будет заворачиваться через указанный SOCKS5-прокси. Локальные диапазоны оставлены в DIRECT, чтобы не словить прекрасный цирк с недоступными локальными ресурсами, роутерами, NAS и прочими домашними зверушками.<\/p>\n",
            "summary": "Получив намедни на локальной станции отвал сразу нескольких утилит, которые умеют пробрасывать трафик через TUN-режим, я пошёл искать альтернативы",
            "date_published": "2026-06-11T00:27:20+07:00",
            "date_modified": "2026-06-11T00:27:05+07:00",
            "tags": [
                "Clash Verge",
                "SOCKS5",
                "TUN2Proxy",
                "прокси",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Thu, 11 Jun 2026 00:27:20 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "224",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "220",
            "url": "https:\/\/blog.m9k.space\/all\/id-docker-konteynerov-maksimalnyh-potrebiteley-swap-v-linux\/",
            "title": "ID Docker контейнеров — максимальных потребителей SWAP в Linux",
            "content_html": "<pre class=\"e2-text-code\"><code class=\"\">for pid in $(ls \/proc | grep -E &#039;^[0-9]+$&#039;); do     swap=$(grep VmSwap \/proc\/$pid\/status 2&gt;\/dev\/null | awk &#039;{print $2}&#039;);          if [[ -n &quot;$swap&quot; &amp;&amp; &quot;$swap&quot; -gt 0 ]]; then         cmd=$(tr &#039;\\0&#039; &#039; &#039; &lt; \/proc\/$pid\/cmdline);          cgroup=$(cat \/proc\/$pid\/cgroup 2&gt;\/dev\/null);\n        container_id=$(echo &quot;$cgroup&quot; | grep -Eo &#039;[a-f0-9]{64}&#039; | head -n1);\n        if [[ -z &quot;$container_id&quot; ]]; then             container_id=$(echo &quot;$cgroup&quot; | grep -Eo &#039;docker-[a-f0-9]+&#039; | sed &#039;s\/docker-\/\/&#039; | head -n1);         fi;          echo -e &quot;$swap KB\\tPID: $pid\\tCID: ${container_id:-host}\\tCMD: $cmd&quot;;     fi; done | sort -nr | head<\/code><\/pre>",
            "summary": "",
            "date_published": "2026-04-27T09:48:52+07:00",
            "date_modified": "2026-04-27T09:48:51+07:00",
            "tags": [
                "docker",
                "linux",
                "swap",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Mon, 27 Apr 2026 09:48:52 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "220",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "210",
            "url": "https:\/\/blog.m9k.space\/all\/proxmox-7-debian-12-13-lxc-template\/",
            "title": "Proxmox 7 + Debian 12|13 LXC Template",
            "content_html": "<p>У Proxmox есть неплохая обратная совместимость с более свежими версиями LXC-контейнеров по сравнению с той версией Debian, на которой основан текущий релиз. В нашем случае используется Proxmox 7 (на базе Debian 11).<\/p>\n<p>При попытке создать контейнер получаем ошибку вида:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">TASK ERROR: unable to create CT 100 - unsupported debian version &#039;1X&#039;<\/code><\/pre><p>Для обхода проблемы правим файл<br \/>\n\/usr\/share\/perl5\/PVE\/LXC\/Setup\/Debian.pm в двух местах.<\/p>\n<p>Обновляем карту версий:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">my $version_map = { &#039;stretch\/sid&#039; =&gt; 9.1, &#039;buster\/sid&#039; =&gt; 10, &#039;bullseye\/sid&#039;=&gt; 11, &#039;bookworm\/sid&#039;=&gt; 12, &#039;trixie\/sid&#039; =&gt; 13, &#039;kali-rolling&#039;=&gt; 11, };<\/code><\/pre><p>Расширяем допустимый диапазон версий:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">if !($version &gt;= 4 &amp;&amp; $version &lt;= 14);<\/code><\/pre><p>После этого перезапускаем связанные демоны:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">systemctl restart pvedaemon \nsystemctl restart pveproxy<\/code><\/pre><p>Готово, контейнер успешно создаётся.<\/p>\n<p>Разумеется, это грязный хак: при обновлениях Proxmox изменения будут перезаписаны, так что решение подходит только как временное.<\/p>\n",
            "summary": "У Proxmox есть неплохая обратная совместимость с более свежими версиями LXC-контейнеров по сравнению с той версией Debian, на которой основан текущий релиз",
            "date_published": "2026-02-07T10:27:43+07:00",
            "date_modified": "2026-02-07T10:27:32+07:00",
            "tags": [
                "debian",
                "Proxmox",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Sat, 07 Feb 2026 10:27:43 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "210",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "203",
            "url": "https:\/\/blog.m9k.space\/all\/windows-peremeschenie-faylov-v-direktorii-po-sovpadeniyu-chasti\/",
            "title": "Windows перемещение файлов в директории по совпадению части имени",
            "content_html": "<p>В указанной директории ищем файлы с «_45» или «_16», перемещаем во внутреннюю создаваемую директорию.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">$dir = &quot;C:\\work\\pics&quot;\n$target = Join-Path $dir &#039;substrateInstagram&#039;\nNew-Item -ItemType Directory -Path $target -Force | Out-Null\nGet-ChildItem -Path $dir -Filter *.jpg -File |\n  Where-Object { $_.BaseName -match &#039;(_45|_16)&#039; } |\n  Move-Item -Destination $target<\/code><\/pre>",
            "summary": "В указанной директории ищем файлы с «_45» или «_16», перемещаем во внутреннюю создаваемую директорию",
            "date_published": "2025-10-10T09:38:06+07:00",
            "date_modified": "2025-10-10T09:38:04+07:00",
            "tags": [
                "Powershell",
                "windows",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Fri, 10 Oct 2025 09:38:06 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "203",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "202",
            "url": "https:\/\/blog.m9k.space\/all\/linux-passwordless-oneliner\/",
            "title": "Linux Passwordless oneliner",
            "content_html": "<pre class=\"e2-text-code\"><code class=\"\">TARGET=user@host; PORT=22; [[ -r ~\/.ssh\/id_ed25519.pub ]] || ssh-keygen -t ed25519 -N &quot;&quot; -f ~\/.ssh\/id_ed25519; ssh -p &quot;$PORT&quot; &quot;$TARGET&quot; &#039;umask 077; mkdir -p ~\/.ssh &amp;&amp; cat &gt; ~\/.ssh\/authorized_keys&#039; &lt; ~\/.ssh\/id_ed25519.pub &amp;&amp; ssh -p &quot;$PORT&quot; &quot;$TARGET&quot; &#039;echo &quot;&gt;&gt;&gt; Root доступ подтверждён&quot;&#039;<\/code><\/pre>",
            "summary": "",
            "date_published": "2025-08-25T17:44:17+07:00",
            "date_modified": "2025-08-25T17:44:14+07:00",
            "tags": [
                "linux",
                "passwordless",
                "ssh",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Mon, 25 Aug 2025 17:44:17 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "202",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "201",
            "url": "https:\/\/blog.m9k.space\/all\/skreschivaem-uzha-i-ezha\/",
            "title": "CouchDB, Debian, AstraLinux, Docker и все-все-все",
            "content_html": "<p>Возникла необходимость запустить Couchdb в контейнере, где базовая ось (и базовые же пакеты) должны быть очень-очень свежими, в противной случай контейнер не проходит встроенную проверку на безопасность.<br \/>\nСтандартный официальный образ собран на базе Debian 12.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">FROM debian:bookworm-slim<\/code><\/pre><p>Простая замена базового образа на<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">FROM debian:trixie-slim<\/code><\/pre><p>приводит к тому, что контейнер не собирается по зависимостям.<br \/>\nСмотрим официальную репу в части зависимостей и видим прекрасное — libmozjs-78-0 (>= 78.15.0). Пробуем ставить свежую версию, которая есть в репах — не помогает, пакет couchdb принципиально требует libmozjs-78-0.<\/p>\n<p>Ок, скрещиваем ежа и ужа.<\/p>\n<p>У debian есть механизм apt-pinning. Это механизм, позволяющий задать приоритеты для версий пакетов из разных источников (репозиториев, release-веток, PPA), чтобы фиксировать версию, запрещать обновление или предпочитать конкретный источник.<\/p>\n<p>Коротко приоритеты делятся на:<\/p>\n<ul>\n<li>< 0 — никогда не устанавливать.<\/li>\n<li>1..99 — ставить только при явном указании версии.<\/li>\n<li>100 — приоритет уже установленной версии.<\/li>\n<li>500 — дефолт для неприоритетных источников.<\/li>\n<li>990 — приоритет для целевого релиза (-t), либо если явно указан release.<\/li>\n<li>>1000 — разрешает понижение версии (downgrade) к закреплённой.<\/li>\n<\/ul>\n<p>Добавляет в наш Dockerfile:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">RUN echo &quot;deb http:\/\/deb.debian.org\/debian bookworm main&quot; &gt; \/etc\/apt\/sources.list.d\/bookworm.list &amp;&amp; \\\n    echo &quot;Package: *\\nPin: release n=bookworm\\nPin-Priority: 500&quot; &gt; \/etc\/apt\/preferences.d\/bookworm &amp;&amp; \\\n    echo &quot;Package: *\\nPin: release n=trixie\\nPin-Priority: 600&quot; &gt; \/etc\/apt\/preferences.d\/trixie &amp;&amp; \\\n    echo &quot;deb [signed-by=\/usr\/share\/keyrings\/couchdb-archive-keyring.gpg] https:\/\/apache.jfrog.io\/artifactory\/couchdb-deb\/ bookworm main&quot; \\\n            &gt; \/etc\/apt\/sources.list.d\/couchdb.list &amp;&amp; \\\n    apt-get update<\/code><\/pre><p>Основная часть пакетов ставится из свежей репы, часть, которых в ней нет — из старых.<\/p>\n",
            "summary": "Возникла необходимость запустить Couchdb в контейнере, где базовая ось (и базовые же пакеты) должны быть очень-очень свежими, в противной случай контейнер не проходит встроенную проверку на безопасность",
            "date_published": "2025-08-14T17:40:09+07:00",
            "date_modified": "2025-08-14T17:40:08+07:00",
            "tags": [
                "apt-pinning",
                "astralinux",
                "couchdb",
                "debian",
                "docker",
                "linux",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Thu, 14 Aug 2025 17:40:09 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "201",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "200",
            "url": "https:\/\/blog.m9k.space\/all\/curl-over-ssh-tunnel\/",
            "title": "Curl over ssh-tls-tunnel",
            "content_html": "<p>Есть ресурс resource.name, на который нужно ходить curl-ом с засылкой данных через JSON. Специфика ресурса такова, что он принимает соединения через TLS 1.3.<br \/>\nЕсть сервер A server.name, на котором нет выхода на нужный ресурс, но есть Nginx, и есть ssh-доступ до сервера B<br \/>\nЕсть сервер B, на котором есть выход на нужный ресурс, но сам сервер очень старый, и TLS на нём не проходит по требования resource.name.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">printf &#039;&#039; | openssl s_client -connect 127.0.0.1:8443 -servername resource.name -tls1_3 -brief<\/code><\/pre><p>Костылим:<br \/>\nНа сервере A поднимаем туннель на внутренний порт:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">ssh -fN -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -L 8443:resource.name:443 user@serverB<\/code><\/pre><p>На Nginx рисуем конфиг:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">server {\n        listen 9090;\n        server_name         server.name;\n\n        access_log  \/site\/logs\/nginx\/port_9090.log  main;\n        error_log   \/site\/logs\/nginx\/port_9090.error.log;\n\nlocation \/ {\n        proxy_pass https:\/\/127.0.0.1:8443;\n\n        proxy_set_header Host resource.name;\n        proxy_ssl_server_name on;\n        proxy_ssl_name resource.name;\n        proxy_ssl_protocols TLSv1.3;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto https;\n        proxy_connect_timeout 10s;\n        proxy_read_timeout 60s;\n        proxy_ssl_verify off;\n}\n}<\/code><\/pre><p>Кидаемся тестовым запросом:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">curl -v -X POST &#039;http:\/\/server.name:9090\/resource&#039; -H &#039;Content-Type: application\/json&#039; --data &#039;{&quot;login&quot;:&quot;$LOGIN&quot;,&quot;password&quot;:&quot;$PASSWORD&quot;}&#039;<\/code><\/pre>",
            "summary": "Есть ресурс resource.name, на который нужно ходить curl-ом с засылкой данных через JSON. Специфика ресурса такова, что он принимает соединения через TLS 1",
            "date_published": "2025-08-14T17:21:23+07:00",
            "date_modified": "2025-08-14T17:43:11+07:00",
            "tags": [
                "curl",
                "curl-over-ssh",
                "linux",
                "ssh",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Thu, 14 Aug 2025 17:21:23 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "200",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "199",
            "url": "https:\/\/blog.m9k.space\/all\/unzip-bez-unzip\/",
            "title": "Unzip без unzip",
            "content_html": "<p>Есть сервер без доступа в интернет, с древней ОС и мёртвыми репозиториями. Нужно распаковать ZIP-архив, но unzip отсутствует, а установить его неоткуда.<\/p>\n<p>Решаем вопрос штатным модулем Python zipfile, без дополнительных зависимостей.<\/p>\n<p>Python 2.7:<\/p>\n<pre class=\"e2-text-code\"><code class=\"python\">python - &lt;&lt;&#039;EOF&#039;\nimport zipfile\nimport os\n\narchive = &#039;archive.zip&#039;\ntarget_dir = &#039;archive&#039;\n\nif not os.path.exists(target_dir):\nos.makedirs(target_dir)\n\nwith zipfile.ZipFile(archive, &#039;r&#039;) as z:\nfor name in z.namelist():\nz.extract(name, target_dir)\nEOF<\/code><\/pre><p>Python 3:<\/p>\n<pre class=\"e2-text-code\"><code class=\"bash\">python3 -m zipfile -e archive.zip archive<\/code><\/pre><p>Для Python 3 достаточно встроенного интерфейса командной строки модуля zipfile. Никаких дополнительных пакетов.<\/p>\n<p>В обоих случаях архив распаковывается в каталог archive. Для Python 3 каталог назначения должен существовать заранее, поэтому при необходимости сначала выполните mkdir -p archive.<\/p>\n",
            "summary": "Есть сервер без доступа в интернет, с древней ОС и мёртвыми репозиториями. Нужно распаковать ZIP-архив, но unzip отсутствует, а установить его неоткуда...",
            "date_published": "2025-08-14T17:07:44+07:00",
            "date_modified": "2026-10-11T23:56:46+07:00",
            "tags": [
                "linux",
                "python",
                "zip",
                "системное администрирование"
            ],
            "_date_published_rfc2822": "Thu, 14 Aug 2025 17:07:44 +0700",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "199",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        }
    ],
    "_e2_version": 4199,
    "_e2_ua_string": "Aegea 11.5 (v4199)"
}