M9K blog

Заметки ITшника обо всём

Suno. «Мирна ноћ».

И ещё один нейроэксперимент, на этот раз с перекрёстной валидацией текста LLM — ибо проверить корректность текста по причинам незнания целевого языка я не могу. Вероятно (~99.9%) для носителя языка будет звучать неестественно ( с учётом того, что Suno может пропеть практически всё, не взирая на ударения), но с музыкальной точки зрения звучит приятно, и даже с нотками оригинала (который, опять же, использовать в насквозь коммерческом продукте) — нельзя. Обложка трека — интерпретация реальной фотографии by me.

Кро́вови дрхте под те́ретом да́на,
Не́бески па́стир о́блаке на́паса.
Град за́сипа ноћ са́чмом све́тала,
Ал’ ноћ је ја́ча — све јој при́пада.

О́нима што ле́жу — спо́којан сан,
Ми́рна ноћ.
О́нима што ле́жу — спо́којан сан,
Ми́рна ноћ.

Че́кала сам то вре́ме, и вре́ме је сти́гло,
О́ни што су ћу́тали, сад ди́жу глас.
Ко не́ма шта да че́ка, се́да у се́дло,
Ни́ко их не мо́же сти́ћи, не мо́же сти́ћи.

А о́нима што ле́жу — спо́којан сан,
Ми́рна ноћ.
О́нима што ле́жу — спо́којан сан,
Ми́рна ноћ.

Ко́мшије до́лазе — чу́ју то́пот ко́пита,
Не да им да за́спе, ре́мети им сан.
Ко не́ма шта да че́ка, кре́ће на пут,
О́ни што су спа́сени — о́ни што су спа́сени.

А о́нима што ле́жу — спо́којан сан,
Ми́рна ноћ.
О́нима што ле́жу — спо́којан сан,
Ми́рна ноћ.

Suno. «Белград, 05:24».

В мае 2024 года я впервые оказался в Белграде. Ранний прилёт, рассвет над крылом самолёта, почти пустые улицы, троллейбусные провода и архитектура, в которой смешались несколько эпох. Первое впечатление оказалось странным: город совершенно незнакомый, но при этом подозрительно узнаваемый. Кириллица, обшарпанные фасады, роскошная лепнина, кондиционеры на балконах, граффити и афиши русскоязычных музыкантов.

В тот раз я поселился на Светогорска и буквально сразу после заселения отправился изучать город. Прогулка по центру довольно быстро привела меня к разрушенному зданию Генштаба. В результате осталось несколько десятков фотографий и немало впечатлений.

Предыдущий трек о Белграде были LLM-переработкой моих же собственных дневниковых текстов. Здесь же цепочка другая: Image → Text → Sound. После небольшой корректуры получился «Белград, 05:24»  — холодный, немного ироничный рассказ о городе, в котором знакомые буквы складываются в чужие слова.

Воистину странное время: умный и хитрый статанализатор научился вполне убедительно выражать эмоции.

Контекст метафор:

05:24 — время первого снимка из иллюминатора 7 мая. Никакой нумерологии, просто удачно получилось.

Светогорска и семь названий. Одна из исходных фотографий, скормленных нейросетке — табличка с историческими переименованиями. Два белых голубя, Светогорская, Битольская, Жоржа Клемансо, снова Светогорская, Лоле Рибара и опять Светогорская. Семь периодов, несколько политических эпох и одна улица, которая никуда не делась.

Генштаб. Комплекс зданий Генерального штаба Югославии пострадал во время бомбардировок НАТО в 1999 году. Я фотографировал его утром 8 мая 2024-го, спустя ровно четверть века после одного из ударов по комплексу. Совпадение дат обнаружил анализ LLM

«МЫ ЕСТЬ!» — не абстрактный экзистенциальный лозунг, а реальная афиша концертного фильма Ромы Либерова, попавшая в кадр. Среди участников — Noize MC.

Лирика:

Восьмое мая.
Пять двадцать четыре.
Доброе утро, Белград.

Рассвет разливается тонкой полоской,
Крыло самолёта разрезало тьму.
И город встречает улыбкой неброской,
Как будто давно я знакома ему.

Здесь утро встречает клубками проводов,
Балконами, пылью, рекламой аптек.
И город, похоже, к приезду готов:
Он видел таких уже тысячу лет.

Кириллица смотрит со старых домов,
Знакомые буквы — чужие названья.
И город из сотни забытых миров
Мне выдал прописку без права свиданья.

А улицы помнят чужие порядки,
И время застряло в бетонных стенах.
Здесь прошлое вечно играет в прятки,
Но прячется плохо в соседних дворах.

Белград, пять двадцать четыре,
Рассвет на чужой стороне.
Я снова одна в этом мире,
Который знаком почему-то мне.

Белград, пять двадцать четыре,
И май отражается в стекле.
Мы прежде друг друга не знали,
Но виделись будто во сне.

Здесь улица семь раз меняла названье,
Два белых голубя, Лоле Рибар.
У каждого века своё оправданье,
У каждого времени — новый кошмар.

На старом подъезде фамилии в ряд,
Здесь русский и сербский почти побратимы.
Знакомые буквы со мной говорят,
Но мы друг для друга непереводимы.

Роскошная лепни́на, ржавый балкон,
И плющ забирается выше антенны.
История пишет очередной закон,
А стены не ждут никакой перемены.

И если мне скажут: «Начни всё сначала»,
Я спрошу у таблички на старой стене:
«Скажите, вам легче от новых названий?»
Она промолчит, как положено ей.

Белград, пять двадцать четыре,
Рассвет на чужой стороне.
Я снова одна в этом мире,
Который знаком почему-то мне.

Белград, пять двадцать четыре,
И май отражается в стекле.
Мы прежде друг друга не знали,
Но виделись будто во сне.

Восьмое мая. Утро серого цвета,
Разбитый Генштаб неподвижно стоит.
Двадцать пять лет. Не случилось ответа,
А город молчит — но бетон говорит.

На стенах — «МЫ ЕСТЬ!», чуть подальше — ругательства,
А рядом афиши, футбол и металл.
Здесь каждое новое доказательство,
Что мир до конца ничего не понял —
И не узнал.

На башнях бетонных кондиционеры,
Машины, деревья, обычная жизнь.
И все революции, лозунги, меры
Смешались в короткое слово «держись».

Здесь не обещают счастливого завтра,
Не просят забыть и не учат прощать.
И город, уставший от собственной правды,
Не просит остаться, не велит уезжать.

Белград, пять двадцать четыре,
Рассвет на чужой стороне.
Я больше не гостья в этом мире —
Он странно похож на меня во сне.

Белград, пять двадцать четыре,
Мой рейс растворился во мгле.
Мы столько всего пережили,
Чтоб просто остаться на этой земле.

Восьмое мая.
Пять двадцать четыре.
Знакомые буквы.
Чужие слова.

Мы есть.

Мониторинг релизов GitHub

Очередной небольшой эксперимент с вайбкодингом — Bash-скрипт для отслеживания новых релизов в GitHub-репозиториях.

Раз в сутки проверяет заданный список проектов через GitHub API и отправляет уведомления в Telegram: версия, ссылка и краткий список изменений.

Поддерживает SOCKS5-прокси, хранит историю обработанных релизов, умеет работать в режиме dry-run. Конфигурация — JSON и .env. Зависимости — Bash, curl и jq.

Сейчас используется для мониторинга Lychee, Firefly III и Memos.

Никаких постоянно работающих сервисов: обычный скрипт, запускаемый через cron.

Репа

Миграция Lychee 6 -> 7 в Docker (LXC)

Развлекался намедни обновлением Lychee с миграцией с базового LXC-контейнера Debian 12 -> Debian 13
Исходно: Lychee 6.10.3 в Debian 12 LXC. База — MariaDB 10.
Данные приложения находились в:

/lychee/conf
/lychee/uploads
/lychee/sym
/lychee/pictures
/lychee/db

Цель: обновить приложение и MariaDB 10->11
В Lychee 7 изменилась структура каталогов внутри контейнера (см. Update Guide))

Новая схема:

volumes:
  - ./lychee/uploads:/app/public/uploads
  - ./lychee/logs:/app/storage/logs
  - ./lychee/tmp:/app/storage/tmp
  - ./lychee/storage/app:/app/storage/app

База смонтирована в локальную директорию, но т. к. хочу поднять версию базы, делаем миграцию через дамп.

docker exec lychee_db \
  mysqldump \
  -u root \
  -p'PASSWORD' \
  --single-transaction \
  lychee > /lychee/lychee.sql

Переносим всё другой сервер, восстанавливаем.

APP_KEY

В Lychee 7 обязателен `APP_KEY`. Без него контейнер завершается с ошибкой:

ERROR: APP_KEY is not set

Ключ можно сгенерировать:

echo "base64:$(openssl rand -base64 32)"

и передать контейнеру:

environment:
  APP_KEY: "base64:..."

Восстановление базы

docker compose up -d lychee_db

Восстанавливаем дамм.

docker exec -i lychee_db \
  mariadb \
  -u root \
  -p'PASSWORD' \
  lychee <lychee.sql

В MariaDB 11 используется клиент `mariadb`, а не старое имя `mysql`.

Пересчёт статистики

После успешного запуска Lychee 7 стоит пересчитать размеры альбомов и thumbnails

docker exec -it lychee php artisan lychee:recompute-album-sizes

и статистику альбомов:

docker exec -it lychee php artisan lychee:recompute-album-stats

Reverse proxy

После миграции при работе через Nginx возникала плавающая 502, причём выглядела она интересно — на одном и том же ПК веб-морда могла открывать в одном браузере, и не открывать в другом. В логах:

upstream sent too big header while reading response header from upstream

Проблема решилась увеличением proxy buffers:

Эгея. Хроники обновления.

Когда-то, в далёкие времена ковида и окончательного огосударствления ВК, посетила меня идея поднять свой бложик на Aegea. Поднимал его я на актуальном тогда Proxmox 5, ещё ого-го как живом CentOS 7 в LXC. Внутри была, страшно сказать, MariaDB 5 и несвежий апачик. Утекло с тех лет много воды: контейнер несколько раз менял хостовый Proxmox, но внутрянку я не трогал.

И тут зачесались руки привести историческую инфраструктуру в CI-ный вид: плейбук там, плейбук тут.

База — Debian 13, обновление с миграцией на свежую версию движка, потому что с 2020 года Aegea я обновлял всего один раз.

Вообще, это заметка должна была быть одой LLM и нытьём на тему того, что нейросети заменят админодевопсов нафиг.

И, в общем и целом, я был в восторге. Написав некоторую длинную портянку текста о том, что, как и почему, я получил длинный плейбук, который, без шуток, выполнился с первого захода. И это с учётом того, что исходный сервер настраивал админ с сильно меньшим пониманием жизни и профессии.

Отрефлексировать я это отрефлексировал тем, что ТЗ было составлено с кучей ограничений, которые человек без опыта ковыряния во всём этом просто не сформулировал бы. Машине скормили хороший запрос — машина выдала хороший результат. Плейбук был запущен, и через пару минут я получил рабочий блог.

Но рефлексия не удалась.

При попытке загрузить картинку в новый пост ловлю: «не удалось зарегистрировать загруженный файл».
В nginx access log:

POST /@ajax/file-upload/?entity=note&entity-id=247 HTTP/1.1" 200

При этом в /var/www/blog/pictures появлялись временные файлы:

-rw------- 1 www-data www-data 0 upload-XXXXXX

Очевидное: место, права, лимиты на аплоад php, просто загрузку — проверил. PHP корректно получал файл:
Движок в фришной версии обфусцирован, разбираться в коде — неблагодарное дело — мы холопоадмины, наше дело логи читать. Достаём BFG.

strace -ff $(ps aux | awk '/php-fpm: pool www/ && !/awk/ {printf "-p %s ", $2}') -o /tmp/egeya-upload.strace

Проблема раз:

rename("/tmp/phpXXXX", "/var/www/blog/pictures/upload-XXXX") = -1 EXDEV

Ошибка:

EXDEV (Invalid cross-device link)

означает попытку `rename()` между разными файловыми системами.

Смотрю на вывод df -T в исходной системе, и в target — и понимаю, что Debian (не)смог. /tmp теперь не реальный каталог, а временная фс tmpfs в памяти

Схема проблемы:
/tmp
|
| tmpfs
|
X rename()
|
/var/www/blog/pictures
|
| root filesystem

PHP создавал временный upload-файл в /tmp.
Эгея использовала move_uploaded_file(), который внутри PHP может выполнить быстрый rename().
Но rename(), как оказалось, между разными файловыми системами невозможен.
Бахнул отдельный временный каталог на той же файловой системе, где и бложик.

mkdir -p /var/www/blog/tmp
chown www-data:www-data /var/www/blog/tmp
chmod 700 /var/www/blog/tmp

В PHP-FPM :

upload_tmp_dir = /var/www/blog/tmp

После перезапуска:

systemctl restart php8.2-fpm

цепочка стала:
/var/www/blog/tmp/phpXXXX
|
| rename()
v
/var/www/blog/pictures/upload-XXXX
Ошибка EXDEV исчезла — но аплоад не заработал.
Достаём BFG второй раз.

strace -ff $(ps aux | awk '/php-fpm: pool www/ && !/awk/ {printf "-p %s ", $2}') -o /tmp/egeya-upload.strace

Видим, что Эгея хочет поделиться тайнами мироздания в лог, но не может. Чиним и это.

touch /var/www/blog/user/logs/main.log

Повторяем аплоад. В логе:

Process image: Done
Register upload: <, , add, Array>
Ajax file upload: couldn’t register upload

Явно не по фэншую, не хватает параметров. Смотрим консоль отладки. Должны быть entity и entity-id
Смотрим в nginx. Конфиг нагенерила LLM по мотивам того, что было в сети — и он был рабочий...формально.

location / {
    log_not_found off;
    try_files $uri $uri/ /index.php?go=$uri;
}

Запрос /@ajax/file-upload/?entity=note&entity-id=247 после try_files превращался в /index.php?go=/@ajax/file-upload/. Параметры говорили «Мұнда параметрлер жоқ қой, иттер»
Меняем чудо-LLM на рабочее:

location / {
    log_not_found off;
    rewrite ^(.*)$ /index.php?go=$1 last;
}

Смысл изменения:
try_files при внутреннем перенаправлении формирует новый URI и может потерять исходную строку параметров.
rewrite ... last выполняет внутренний rewrite с сохранением исходных GET-параметров. Рестартуем Nginx.

Бинго!

Социалочка

Намедни закинул обрезанную версию обзора на Cost of Hope в профильное хранилище дядюшки Габена. Мой поток сознания не то, чтобы сильно уникален и не единичен даже для cyrillic сегмента — на основной проект всего 107k+ отзывов, на DLC — под две.

И тем необычнее в рамках понятных грустных обстоятельств и реалий года 2026 видеть на почте:

Стоит ребятам отдать должное — следят за комьюнити, даже в известных обстоятельствах. Пусть даже и ответ логичный «мы делаем то, что делаем, потому что продаётся».
Дякую, приємно.

Suno. «Не по ГОСТу».

Чутка про жизнь.

Мне тридцать с лишним. Всё нормально.
Есть отпуск, пропуск и пароль.
И даже кризис экзистенциальный
Теперь записан как рабочий сбой.

С утра упал продакшен, вечером — мораль,
Я снова поднимаю то, что рухнуло вчера.
Коллега снова прошлое торжественно достал
И объясняет молодым, как правильно тогда.

Он помнит вкус пломбира, пионерский звонкий хор,
Хотя застал Союз примерно несколько минут.
Ему уютно думать: раньше был другой простор,
Где правильные люди правильнее живут.

А я чиню систему, где опять потерян час,
Где будущее требует исправить каждый раз.
А если я опять не прохожу контроль —
Быть может, я играю просто не свою роль.

Мне говорят: «Пора уже серьёзней выбирать:
Семья, машина, дети — всё положено иметь».
Но если это взрослость — научиться выбирать
Из списка только то, что полагается иметь?

Я не по ГОСТу, не по плану,
Не по семейному уставу.
Мне говорят: «Ты станешь взрослой».
Я взрослой стала. Что мне стало?

Я не по ГОСТу, не по плану,
Не помещаюсь в ваш чертёж.
Мне говорят: «Когда-нибудь ты
До нашей истины дойдёшь».

Я столько лет училась всё упавшее чинить:
Процессы, базы, сервисы — проверить и поднять.
Но непонятно, как себя обратно подключить,
Когда нигде не пишут, что сломалось и опять.

Вокруг давно размечен обязательный маршрут:
Работа, дом, машина, дети — правильный этап.
И люди точно знают, как приличные живут,
И каждый шаг заранее прописан в roadmap.

А я смотрю на город — бесконечный хоровод:
Работа, дом, покупки — неизменный оборот.
Но где-нибудь найдётся правильный поворот —
Туда, где на табличке крупно: «Аэропорт».

Я не по ГОСТу, не по плану,
Не по семейному уставу.
Мне говорят: «Ты станешь взрослой».
Я взрослой стала. Что мне стало?

Я не по ГОСТу, не по плану,
Не помещаюсь в ваш чертёж.
Мне говорят: «Когда-нибудь ты
До нашей истины дойдёшь».

В аэропорту я становлюсь чуть-чуть живей:
Здесь никому нет дела до отсутствия детей.
Здесь не спросят, почему не куплена квартира,
И не потребуют любить руины старого мира.

Сдаю багаж, прохожу контроль,
Оставив на досмотре социальную роль.
И нелюбимый город уменьшается в окне,
И первый раз за месяцы спокойнее во мне.

Там внизу остались «надо», «принято», «пора»,
Разговоры, как прилично человеку тридцать плюс.
А облака не знают ни начальства, ни двора,
Ни почему от правильного будущего грусть.

В иллюминаторе — оранжевая нить.
Я не решила, как мне дальше, где мне жить.
Но на минуту между облаком и тьмой
Мне хорошо от права просто быть собой.

Но самолёт садится.
Так заведено.
И город возникает
За мокрым стеклом.

Я знаю, как всё чинят.
Как сервисы поднять.
Но как мне дальше жить —
Негде прочитать.

Я не по ГОСТу, не по плану,
Не помещаюсь в ваш чертёж.
Мне говорят: «Когда-нибудь ты
До нашей истины дойдёшь».

Но если ваша жизнь нормальна,
А всё другое — ерунда,
Зачем мне легче ненормальной
На высоте, чем здесь всегда?

Мне тридцать с лишним. Всё нормально.
Есть отпуск, пропуск и пароль.
И только кризис экзистенциальный
Не закрывается как сбой.

Создание пользователей в Debian и Astra Linux

Cкрипт для создания локальных пользователей с настройкой passwordless sudo.
Определяет дистрибутив через /etc/os-release. Для Astra Linux дополнительно выполняет pdpl-user -l 0:63 -i 63.
Проверяет наличие утилит, не пересоздаёт существующих пользователей и не изменяет их пароли. Конфигурация sudo проверяется через visudo. Поддерживает повторный запуск.
Запуск:

./create-users.sh user1 user2 user3

скрипт

Cost of Hope

Я отдал GSC деньги за ванильный STALKER 2 (предзаказ!), прошёл его на релизе и, несмотря на весь цирк с A-Life, странную мотивацию Скифа; персонажей, которые периодически разговаривали и действовали как люди с очень специфическими повреждениями лобной доли, в целом остался доволен. Где-то кринжевал, где-то матерился, где-то даже сопереживал Бродяге. А Припять дала совершенно роскошное ощущение узнавания.

И дело не в том, что «ой, панельки, ржавчина, СССР». Я живу примерно в такой же постсоветской срани и прекрасно знаю, как она выглядит. Какая плитка бывает в коридоре. Какого цвета красят нижнюю половину стены. Как рядом существуют дверь из восьмидесятых, китайский автомат из двухтысячных и табличка «Двор образцового содержания» образца 1971 года. Как труба идёт не там, где красиво, а там, где сантехник смог её провести, местами переходя в неевклидово пространство. Как бетонный забор, административное здание, подстанция и двор образуют пространство, которое никто не проектировал для эффектного скриншота.
.cut
Ванильный STALKER 2 местами попадал в это ощущение почти физически. Он выглядел не как тематический парк «Восточная Европа после апокалипсиса», а как знакомое пространство, в которое зачем-то добавили Зону.

Cost of Hope это ощущение для меня практически уничтожил.

Главная проблема дополнения даже не в плохом сюжете, не в Мавке, не в Зулусе, не в монорельсе, не в мозге в банке и не в затопленных подвалах НИИЧАЗа. Проблема в том, что поменялся сам договор между игрой и игроком. В хорошем STALKER мир существует сам по себе, а ты в нём временно находишься. В Cost of Hope мир слишком часто существует исключительно для того, чтобы через тридцать секунд развлечь тебя следующим аттракционом.

Вот дверь. За ней бой. Вот коридор. В конце катсцена. Вот ЧАЭС. Разумеется, там будет локальный Call of Duty с бесконечными пачками вооружённых болванчиков. Вот лаборатория. Конечно, в ней должен быть пространственный пазл. Вот огромная установка. Надо по ней полазить. Вот мозг в колбе. Надо устроить вокруг него финальную арену. Вот мутанты. Их тоже кинем в перестрелку, потому что вдруг игрок на двадцать секунд останется без «контента».

Особенно смешно это выглядит на фоне того, как GSC два года чинила ванильную игру от синдрома «я повернулся, а в десяти метрах за спиной материализовалась новая жизнь». Они буквально два года приводили A-Life к состоянию, когда игрок меньше замечает спавнер. И после этого в Cost of Hope устраивают на ЧАЭС ту же болезнь в пятикратном масштабе, только уже намеренно.

Первое исследование ЧАЭС ещё работает. Потому что некоторое время игра просто позволяет тебе там находиться. Огромные промышленные сооружения, территория, которая долго была недоступна, само ощущение места. Ничего особенного даже делать не нужно. ЧАЭС сама по себе уже событие.

А потом начинается война группировок, и всё быстро превращается в карту для многопользовательского шутера. Болванчики бегут туда, болванчики бегут сюда, дикий респавн, стрельба , для веселья ещё и мутантов подсыпали.

И чем больше людей игра запихивает на ЧАЭС, тем меньше становится сама ЧАЭС. Если там одновременно вполне нормально функционируют небольшие армии, это уже не запретное сердце Зоны. Это просто горячий район с плохой транспортной доступностью.

Железный лес при этом замечательный. Именно поэтому дополнение особенно раздражает: оно периодически показывает, что всё ещё умеет быть STALKER. Открытые пространства, реальные или хотя бы правдоподобные промышленные объекты, места, в которых география первична, а геймплей к ней приспосабливается.

Потом начинается Лиманск, и я попадаю в сон собаки про нуарный НИИ «Радиоволна».

Я понимаю, почему его любят. Там красиво. Туман. Свет. Мрачные силуэты. Странные интерьеры. Всё очень атмосферно, если атмосфера для вас — это количество атмосферных (простите за тавтологию) объектов на квадратный метр.

Но старый STALKER был сильнее именно тогда, когда сначала показывал что-то абсолютно обыденное. НИИ должен сначала выглядеть местом, где в 1985 году сидела бухгалтерия, курил инженер Коля, а на втором этаже висел стенд по технике безопасности. И уже потом ты узнаёшь, что в подвале кто-то пытался свернуть человеческое сознание в трубочку.

В Cost of Hope некоторые места с порога кричат: «Смотрите, какая у нас загадочная хоррор-локация». После этого они перестают быть загадочными примерно через пять минут.

Отдельный привет истории с монорельсом, соединяющим лаборатории. Мы лазили по этим лабораториям двадцать лет. X18, X16, X-что-то-там. Мы читали документы, таскали папки, встречались с учёными, военными, сталкерами, бывшими сотрудниками исследовательских программ. И никто никогда не считал нужным упомянуть, что под Зоной, оказывается, существует секретное метро стратегического масштаба.

Это не расширение лора. Это реткон из категории «сейчас мы добавим что-нибудь очень большое, потому что большое значит важное».

Причём чем дольше об этом думаешь, тем смешнее становится. Подземная сеть на километры — это не «тайный тоннель». Это проходка, вентиляция, дренаж, энергоснабжение, депо, ремонт, бетон, вывоз грунта, геологи, строители, техника, годы работ и тысячи людей. В какой-то момент сама инфраструктура становится более грандиозным секретным проектом, чем лаборатории, которые она якобы должна незаметно соединять.

Но сценарий просто кладёт этот факт перед тобой и идёт дальше. Прими, пожалуйста. Следующая сцена уже загружается.

Старая серия была умнее именно потому, что многое не объясняла. Мы знали, что лаборатории связаны. Оборудование перемещалось. Документы ходили между объектами. Люди работали в рамках общей программы. Этого было достаточно.

Не каждую дырку в лоре обязательно заливать бетоном.

Иногда вопрос «а как именно всё это было связано?» намного интереснее ответа «там был секретный монорельс».

С нарративом в целом та же болезнь: игра постоянно повышает ставки, совершенно не заботясь о том, повысился ли мой интерес.

Ванильный STALKER 2 тоже был «голливудским». Скиф временами пустой ГГ, мотивация почти анекдотическая; крупные персонажи любят внезапно совершать эффектные поступки вместо разумных. Но история хотя бы имела хребет. Скиф приехал по конкретной причине, оказался втянут в более крупный конфликт и постепенно понял, насколько глубоко в него залез.

Cost of Hope выглядит как кипиш ради кипиша.

Скиф встречает людей, которых знает от силы пять минут, и почти сразу готов ради них лезть в ад. Через некоторое время он уже решает судьбу Долга и Свободы, участвует в гражданской войне, раскапывает очередной пласт секретных программ и спасает персонажей, к которым игра ещё не успела дать мне возможность хоть что-нибудь почувствовать.

Особенно хорошо это видно в лагере в Железном лесу. Игра очень хочет трагедии. Вот «пипл, знакомьтесь, сталкер».Вот карты. Вот сцена. Вот погибшие. Вот серьёзные лица. Пожалуйста, испытывайте эмоции.

А я смотрю на это и думаю: кто все эти люди?

В лагере новичков SoC 2007 года персонажи состояли из трёх полигонов, двух анимаций и одного набора лиц. Но я туда возвращался. Там сидели люди. Кто-то играл на гитаре. Стоял Волк. Был Петруха. Был костёр. Через несколько часов это становилось местом. Игра не включала трагическую музыку, чтобы объяснить мне, что место важно.

Cost of Hope почему-то уверена, что эмоциональную связь можно заменить постановкой.

Мавка и Зулус существуют примерно по тому же принципу. Они яркие, хорошо выглядят, неплохо озвучены и совершенно разваливаются, если попытаться воспринимать их как живых людей, а не механизмы для запуска нужных сценаристу событий.

В нормальной драме персонаж обладает характером, поэтому принимает решение, из которого возникает событие. Здесь слишком часто сначала существует событие, а потом сценарист быстро подкручивает персонажа так, чтобы тот сделал необходимую глупость.

Нужна эскалация — вот вам эскалация. Нужен фанатизм — сейчас будет фанатизм. Нужна жестокость — человек, который пять минут назад выглядел относительно адекватным, внезапно включит режим сюжетного психопата.

Потом игрок зачем-то должен сам собирать из этих кусков цельную психологию.

Наёмников дополнение сливает ещё смешнее. В Железном лесу и Лиманске они почти боги. Организованные, профессиональные, опасные, знающие территорию. Игра долго убеждает тебя, что это действительно серьёзный противник.

Потом приходит лаборатория с мозгом в банке.

И эти элитные убийцы начинают выбегать пачками, получать очередь и освобождать место для следующей пачки. В этот момент они перестают быть наёмниками и становятся расходным материалом encounter designer’а.

Нет более надёжного способа уничтожить угрозу врага, чем заставить игрока убить его сорок раз подряд.

То же самое происходит с мутантами. Первая встреча с кровососом на Агропроме в 2007 году обеспечивала несколько кирпичей не потому, что кровосос обладал искусственным интеллектом уровня DeepMind. Игра просто правильно его подавала. Подземелье, темнота, звуки, неизвестность.

В STALKER 2 кровососа выкатывают примерно через пятнадцать минут после начала. Ну ладно. Фансервис. Ребята, вы купили STALKER — держите кровососа. В Cost of Hope мутанты на ЧАЭС уже окончательно превращаются в специю для боя. Людей недостаточно? Добавим монстра.

Кровосос проходит прекрасный путь от таинственной твари до фирменного маскота, которого можно кинуть в encounter table между снайпером и очередным отрядом NPC.

Подвалы НИИЧАЗа стали для меня главным хоррором дополнения, только не в том смысле, в каком хотелось бы авторам. На последней сложности я умер там около десяти раз и в итоге открыл прохождение.

Это не «сложный момент».

Это провал левел-дизайна.

Когда я умираю, потому что неправильно оценил противника, плохо подготовился или не понял правило аномалии — прекрасно. Я чему-то учусь внутри игры.

Когда после очередной смерти я думаю: «Так, а в какой именно точке дизайнер предполагает, что я должен всплыть и на какую железку запрыгнуть?» — я больше не играю Скифом. Я занимаюсь обратной инженерией маршрута — а это хватает и на работе.

И если для прохождения обязательного линейного участка мне проще посмотреть видео, чтобы понять намерение автора, то автор облажался. Тут даже спорить особенно не о чем.

Гигантомания в НИИЧАЗ и лабораториях выглядит примерно так же. Огромные помещения, огромные установки, многоэтажные конструкции, пространственные головоломки, прыжки по балкам.

Где-то из 1998 года машет рукой Lambda Core. Если это отсылка к Half-Life, то спасибо, конечно. Я понял — я его застал на релизе.

В Half-Life подобные вещи работали, потому что вся Black Mesa была построена на этом художественном языке. Гордон всю игру лазил по лифтам, механизмам, трубам, реакторам и конвейерам. Lambda Core был кульминацией того, чему игра его до этого учила.

В STALKER я хочу войти в лабораторию и подумать: «Что здесь, чёрт возьми, делали?»

В Cost of Hope я захожу в помещение и думаю: «Ага, здесь сейчас будет платформинг».

Или: «Вот это арена».
Или: «По этим балкам мне явно придётся идти».
Как только я начинаю видеть намерение левел-дизайнера раньше самого места, погружение заканчивается.

Колбы с мутантами формально вообще не противоречат лору. Тайные эксперименты были всегда. Но опять же важна подача. В старых лабораториях было ощущение, что здесь когда-то происходило что-то страшное и теперь ты ходишь по последствиям.

Cost of Hope временами устраивает экспозицию.

Вот мутант.
Вот ещё мутант.
Вот большой мозг.
Вот очень большая научная штука.
Получается не заброшенная советская лаборатория, а очередной Resident Evil facility.
Вроде бы слова те же, а язык уже другой.

Даже визуально DLC для меня местами теряет ту самую постсоветскую достоверность ванили. Базовая игра долго разрабатывалась ещё до 2022 года и унаследовала очень характерную историческую мешанину советского, русского, украинского. Именно мешанину, потому что реальное пространства так и выглядят.

В Cost of Hope местами появляется ощущение, что исторический объект начали художественно переписывать в соответствии с очевидной и понятной печальной повесткой дня текущего. Проблема в том, что ЧАЭС и Припять — это материальные объекты Украинской ССР. Они неизбежно содержат смесь СССР, УССР, русского, украинского, советской ведомственной типографики, эмблем, канцелярии и конкретного исторического мусора. И видя очередную «небезпеку» на объекте, который всем своим видом отсылает сильно в другому историческому периоду — погружение ломается.

Если этот слой слишком аккуратно причесать, объект перестаёт выглядеть найденным. Он начинает выглядеть нарисованным.

И для человека из постсоветской среды эта фальшь заметна намного сильнее, чем, вероятно, для рецензента, который видит просто «old Eastern European industrial building».

В итоге с жанром у дополнения тоже получается довольно смешно.

Как хоррор Cost of Hope проигрывает множеству современных инди, которым не нужны огромные бюджеты, чтобы заставить игрока бояться коридора.

Как экшен он временами напоминает какой-то польский шутер про американских морпехов: всё взрывается, все куда-то бегут, враги идут пачками, вокруг происходит Очень Важная Битва.

Как immersive exploration он сам себе постоянно мешает скриптами.

Как нарратив он требует принимать слишком много новых допущений и слишком быстро привязываться к людям, которых игрок почти не знает.

Зато красивые экстерьеры у него действительно красивые.

Вот на них всё и держится.

Поэтому я плохо понимаю тот масштаб восторга, который вызвало дополнение у рецензентов/игрожуров. Видимо, мы просто хотим от STALKER совершенно разных вещей.

Если для человека хороший STALKER — это больше событий, больше катсцен, больше необычных механик, больше сюжетных поворотов, больше боёв и меньше пустого пространства, Cost of Hope действительно может показаться прекрасным развитием формулы.

Для меня STALKER работает ровно наоборот.

Ты идёшь десять минут, и ничего не происходит.
Потом ещё пять.
Вдалеке кто-то стреляет.
Ты решаешь туда не ходить.
Потом заходишь в пустое здание.
Слышишь непонятный звук.
И вот это уже событие.

Современный AAA-дизайнер посмотрит на первые пятнадцать минут и скажет: «Надо улучшить pacing».

Нет.
Это и был pacing.

Я заплатил за дополнение к игре, которая при всех своих недостатках давала мне иллюзию враждебного мира, существующего независимо от моего присутствия.

А получил аттракцион.
Причём не лучший аттракцион.

С посредственным нарративом, местами посредственным геймдизайном, гигантоманией, ретконами, тирными вставками, аренными боями, пространственными головоломками из прошлой эпохи и режиссурой, которая постоянно пытается сообщить мне, что вот сейчас, пожалуйста, надо испытать сильную эмоцию.

Cost of Hope лучше всего работает там, где у дизайнеров меньше свободы и есть реальный прототип, который приходится уважать.

Как только дверь закрывается и разработчики остаются один на один со своей фантазией, я слишком часто перестаю видеть Зону.

Я вижу уровень.

Для STALKER это, пожалуй, худшее, что можно сказать.
.cutend

Git: превращаем вложенный репозиторий в обычную директорию

Если скопировать один Git-проект внутрь другого и выполнить commit, Git сохранит вложенный проект как gitlink (режим 160000). В GitLab вместо содержимого директории будет отображаться ссылка на commit:
init-server @ 957c7776
Простое удаление вложенного .git не поможет: ссылка уже записана в индексе родительского репозитория.
Решение:

  1. Удаляем gitlink из индекса, сохраняя файлы на диске:
git rm --cached init-server
  1. Удаляем метаданные вложенного репозитория, если они ещё существуют:
rm -rf init-server/.git
  1. Добавляем содержимое как обычные файлы:
git add -A init-server/
  1. Проверяем:
git ls-files --stage init-server

Вместо записи с режимом 160000 должны появиться отдельные файлы с режимами 100644 или 100755.

  1. Фиксируем изменения:
git commit -m "Convert init-server to regular directory"
git push

Suno. «Чужое своё».

Продолжение темы городов — далёких и близких

Мне было пять.
Первый раз — за границу.
Хотя какая граница,
когда тебе пять?

Я потом сюда ездил десятки раз —
Самолётом, поездом, летом, зимой.
И однажды заметил: знакомый пейзаж
Становится дальше от слова «чужой».
Здесь «выше» — до гор, а «ниже» — от них,
И город читается без указаний.
Я к этой системе давно уж привык,
Без карт и подсказок, без лишних названий.

Я не местный — и спорить об этом нелепо,
Но туристом назвать себя тоже смешно.
Я смотрю на проспекты, на крыши, на небо —
И всё это знакомо до странности, но
Здесь другая реальность могла бы сложиться,
Другой адрес, привычки, работа и дом.
Я не знаю, каким бы я стал в этой жизни,
Но порой узнаю себя в городе том.

Алматы — чужое, где вижу своё,
Как жизнь, что могла бы достаться мне.
Другой поворот, пара улиц, район —
И, может быть, я бы остался в нём.
Где горы висят на краю облаков,
Где город знаком, хоть и мне не дан.
Я будто читаю историю сна,
Где жизнь незнакомая слишком родна.

Я знаю язык на смешном бытовом:
«Дүке́н», «кіта́п», «су́» — и ещё с деся́ток.
А «балмұзда́қ» запо́мнил ещё пацано́м.
Ведь мороженое понятно на языке любом.
«Как мне на Абая?» — спросили опять.
«До Космонавтов, направо, кварталов на пять».
«До Байтурсы́нова», — тут же поправлю себя.
Я старого имени не застал никогда.

Но оба названия помню на равных,
Как будто знакомы мне с детства давно.
И странно: чужой город стал настолько понятным,
Что два его имени слились в одно.

Здесь Пушкин и Аба́й, здесь Гоголь и Досты́к —
К соседству этих улиц я давно уже привык.
У каждого названия свой голос и свой след,
Но между ними спора на городской карте нет.

Алматы — чужое, где вижу своё,
Как жизнь, что могла бы достаться мне.
Другой поворот, пара улиц, район —
И, может быть, я бы остался в нём.
Где горы висят на краю облаков,
Где город знаком, хоть и мне не дан.
Я будто читаю историю сна,
Где жизнь незнакомая слишком родна.

А ночами мне снятся не улицы даже.

Меде́у — ступени теряются в утренней мгле.
Шымбұла́қ — и кажется, будто я больше не на земле.
Көк-Жайла́у — тропа растворяется в облаках,
А Алматы́ остаётся внизу, в огнях.

И смешно:
во сне я не думаю,
чей это город,
чья это земля,
чей язык на вывеске.

Я просто знаю дорогу.

И если бы жизнь повернула иначе
Другой самолёт, другой двор, другой класс
Я б горы считал просто частью пейзажа
Не вскидывал голову каждый раз.
Я мог бы всю неделю спешить на работу,
Ругаться на пробки, решать все дела.
А в горы с друзьями уехать в субботу,
Подальше от шума, асфальта, тепла.

Мог бы знать этот город не лучше — иначе:
По рабочим маршрутам, по будням, дождям.
Не искать в нём чудес, не ловить каждый миг,
А с рождения просто считать, что привык.

Алматы — чужое, где вижу своё,
Как жизнь, что прошла в параллельной строке.
Другой поворот, пара улиц, район —
И, может быть, я бы остался в нём.

Мне было пять.
Первый город за границей.

Наверное, поэтому
я так и не научился
считать его заграницей.

Rootless Podman внутри Proxmox LXC: проблема UID/GID mapping

При запуске File Browser через rootless Podman образ скачивался, но распаковка завершалась ошибкой:

potentially insufficient UIDs or GIDs available in user namespace (requested 65534:65534)
lchown /home: invalid argument

Причина: пользователю podman выделено только 50 000 subordinate UID/GID. Образ содержит файлы с UID/GID 65534, которые не помещаются в доступный диапазон.

Механизм

Используются два уровня отображения идентификаторов:
Podman: UID/GID образа → UID/GID внутри Debian LXC.
LXC: UID/GID Debian → UID/GID хоста Proxmox.

Настройки Podman определяются файлами /etc/subuid и /etc/subgid. Внешний mapping LXC задаётся через lxc.idmap.

Исходная конфигурация Proxmox:

root:100000:65536
deploy:165536:65536

Непривилегированному LXC доступны 65 536 идентификаторов. Расширить диапазон непрерывно нельзя: следующие идентификаторы уже выделены пользователю deploy.

Решение:

Выделить свободный диапазон на Proxmox и добавить его в /etc/subuid и /etc/subgid хоста:

root:231072:15536

В конфигурации LXC сохранить исходное отображение и добавить новое:

lxc.idmap = u 0 100000 65536
lxc.idmap = g 0 100000 65536
lxc.idmap = u 65536 231072 15536
lxc.idmap = g 65536 231072 15536

Внутри LXC контейнера дополнить существующий диапазон пользователя podman:

podman:10000:50000
podman:65536:15536

Настроить аналогично UID и GID, предварительно проверив отсутствие пересечений с другими диапазонами. После остановки приложений перезапустить LXC и выполнить:

podman system migrate

Изменение mapping не переносит владельцев существующих файлов автоматически.

Proxmox 7: включение Docker и Podman в LXC

Для запуска Docker или Podman внутри LXC-контейнера необходимо активировать дополнительные возможности: nesting, keyctl и fuse.

Скрипт запрашивает ID контейнера, проверяет наличие конфигурации, создаёт резервную копию с меткой времени и включает необходимые параметры. Остальные настройки features сохраняются.

Запускать от root на хосте Proxmox.

#!/bin/bash

read -rp "Введите ID контейнера (CTID): " CTID

if [[ ! "$CTID" =~ ^[0-9]+$ || ! -f "/etc/pve/lxc/${CTID}.conf" ]]; then
    echo "Ошибка: некорректный CTID или конфигурация не найдена"
    exit 1
fi

CONF="/etc/pve/lxc/${CTID}.conf"
BACKUP_DIR="/root/lxc-config-backups"

mkdir -p "$BACKUP_DIR" &&
cp "$CONF" "$BACKUP_DIR/${CTID}.conf.$(date +%Y%m%d_%H%M%S).bak" &&
pct set "$CTID" --features "$(awk -F, '/^features:/{sub(/^features:[[:space:]]*/, ""); for(i=1;i<=NF;i++) if($i !~ /^(nesting|keyctl|fuse)=/) printf "%s,", $i} END{print "nesting=1,keyctl=1,fuse=1"}' "$CONF")" &&
pct config "$CTID" | grep '^features:'

Пример выполнения:

Введите ID контейнера (CTID): 177
features: fuse=1,keyctl=1,nesting=1

Резервная копия конфигурации сохраняется в /root/lxc-config-backups/.

После изменения параметров необходимо полностью остановить и запустить контейнер:

pct stop 177 && pct start 177

Update:
Ещё одна ошибка:

Failed to open() /dev/net/tun: No such file or directory
Failed to set up tap device in namespace

Чиним:

#!/bin/bash
set -euo pipefail

CTID=178
CONF="/etc/pve/lxc/${CTID}.conf"
BACKUP="${CONF}.$(date +%Y%m%d%H%M%S).bkp"

modprobe tun
test -c /dev/net/tun

cp -a "$CONF" "$BACKUP"

RULES=(
    "lxc.cgroup2.devices.allow: c 10:200 rwm"
    "lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file"
)

for rule in "${RULES[@]}"; do
    grep -Fqx -- "$rule" "$CONF" || printf '%s\n' "$rule" >> "$CONF"
done

echo "Backup: $BACKUP"
echo "Configuration:"
grep -E '^lxc\.(cgroup2\.devices\.allow|mount\.entry):' "$CONF"

Prettymaps в Docker: генератор картографических постеров

Поигрался с либой PrettyMaps, которая умеет строить из OSM прикольные постеры.

Собрал контейнер, который позволяет сделать карту.

Сборка без прокси:

DOCKER_BUILDKIT=1 docker build -t prettymaps-docker-test .

Сборка с SOCKS5-прокси:

DOCKER_BUILDKIT=1 docker build --network=host --build-arg PROXY_SOCKS5=$PROXY_IP:$PROXY_PORT -t prettymaps-docker-test .

Запуск:

docker run --rm \
  -v "$(pwd)":/work \
  -e LAT=54.8482742 \
  -e LON=83.0973263 \
  -e RADIUS_M=1500 \
  -e TITLE="Академгородок" \
  -e OUTPUT_NAME=akadem_coords \
  prettymaps-docker-test

Файлы

Ранее Ctrl + ↓