Что нового

EOL, мёртвый PPA и тильда в номере версии: один долгий апгрейд

281 день молчания: как зависший apt-get привёл меня от EOL-системы к Ubuntu 26.04 LTS​

1781788385434.png

Началось всё с команды, которая должна была отработать за секунду:

Код:
apt update
Error: Could not get lock /var/lib/apt/lists/lock. It is held by process 478781 (apt-get)

Обычно это значит «подожди, кто-то уже обновляется». В этот раз — не значило.

Часть 1. Процесс, который не отпускал лок​


Первым делом — не убивать наугад, а посмотреть, что это вообще такое:

Код:
ps -p 478781 -o pid,etime,cmd
    PID     ELAPSED CMD
 478781 281-13:30:12 apt-get -qq -y update

281 день, 13 часов. Обычный `apt-get update` так не живёт никогда — это не «чуть подвис», это труп, который никто не закрыл.

Журнал systemd подтвердил диагноз и сразу показал кое-что более важное, чем сам лок:

Код:
journalctl -u apt-daily -u apt-daily-upgrade -u unattended-upgrades -n 50 --no-pager

Последняя строчка в логе — «Starting apt-daily.service» без последующего «Deactivated successfully». Это и есть тот самый зависший запуск. С тех пор systemd не запускал таймер повторно: пока предыдущий экземпляр считается живым, новый просто не стартует. То есть автообновления на этой машине молчали не «недавно», а буквально девять месяцев — и это важнее, чем сам факт залоченного apt.

Лечится без танцев с lock-файлами — файл лока это обычный flock, ядро снимает его само в момент смерти процесса:

Код:
kill 478781
sleep 3
ps -p 478781   # пусто — процесс мёртв
apt update     # лок свободен

Часть 2. Один PPA, два конфликтующих источника​


apt отпустило, но сразу же выдало новую порцию проблем:

Код:
Err:5 http://ppa.launchpad.net/ondrej/php/ubuntu jammy InRelease
  The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 71DAEAAB4AD4CAB6 NO_PUBKEY 4F4EA0AAE5267A6C
Err:7 https://ppa.launchpadcontent.net/ondrej/php/ubuntu plucky Release
  404  Not Found

Два разных провала на один и тот же PPA (sury/ondrej, под PHP). Смотрю, что реально лежит в `/etc/apt/sources.list.d/`:

Код:
grep -rl ondrej /etc/apt/sources.list.d/
/etc/apt/sources.list.d/ondrej-ubuntu-php-plucky.sources
/etc/apt/sources.list.d/php-ondrej-jammy.list

Два файла на один и тот же PPA — классическое наследие нескольких релизов Ubuntu подряд без уборки за собой:

  • jammy.list — старый однострочный формат, добавлен ещё когда система была на более старом релизе. У него просто не подтянулся ключ (legacy apt-key truststore на новых apt уже не работает так, как раньше).
  • plucky.sources — современный DEB822-файл, автоматически сгенерированный под текущий codename системы. 404 — потому что под этим codename ничего никогда не существовало.

Тут и выяснилась настоящая причина, а не просто «сеть подвела»: система стояла на Ubuntu 25.04 «Plucky Puffin» — а это не LTS, а интерим-релиз с девятью месяцами жизни, который закончил поддержку 15.01.2026. Sury публикует свой PPA только под текущие LTS-релизы (на тот момент — jammy/noble). Под plucky там никогда и не появится сборки — файл был мёртв с рождения, а не «отвалился со временем».

Часть 3. Тактический патч: noble вместо plucky​


Раз ondrej/php живёт только на LTS — временно занимаем эту нишу под noble (24.04), это известный и рабочий обход именно для этого PPA:

Код:
mv /etc/apt/sources.list.d/php-ondrej-jammy.list /etc/apt/sources.list.d/php-ondrej-jammy.list.disabled
sed -i 's/^Suites:.*/Suites: noble/' /etc/apt/sources.list.d/ondrej-ubuntu-php-plucky.sources
apt update

apt ожил, но при попытке поставить весь набор php8.4-* зацепился за зависимость:

Код:
php8.4-zip : Depends: libzip4t64 (>= 1.7.0) but it is not installable
      [no choices]

Не «не хватает версии» — пакета с таким именем в plucky нет вообще. Дело в истории переименований: в Ubuntu 24.04 (noble) при миграции на 64-битный time_t библиотеку libzip упаковали как `libzip4t64`. В plucky к моменту его сборки libzip успел дойти до версии 1.11.x с другим SONAME — пакет там называется просто `libzip5`. Это не косметика: смена SONAME с 4 на 5 означает реальный ABI-разрыв, поэтому симлинком `.so.4 → .so.5` тут не обманешь — бинарник от noble физически ищет файл с именем `.so.4`, и если его нет — он не загрузится, а не «возможно заработает».

Решение — затащить именно недостающую версию из noble, не трогая остальное:

Код:
echo "deb http://archive.ubuntu.com/ubuntu noble universe" > /etc/apt/sources.list.d/_tmp-libzip.list
apt update
apt install libzip4t64
rm /etc/apt/sources.list.d/_tmp-libzip.list
apt update

`libzip4t64` и `libzip5` спокойно живут рядом — у них разные SONAME, конфликта нет. После этого весь набор php8.4-* встал на сборки sury версии 8.4.22.

Часть 4. ProtectSystem=full — та же мина, но в этот раз заранее​


С этой же сборкой php8.4-fpm (8.4.22) на основном сервере sysadmin.guru уже был отдельный инцидент: Sury добавил в юнит `ProtectSystem=full`, который монтирует `/usr` внутри namespace процесса в режиме «только чтение», и любые записи туда тихо превращаются в Read-only file system. В этот раз — проверяю заранее, а не после жалоб:

Код:
systemctl cat php8.4-fpm | grep -i protectsystem
ProtectSystem=full

Подтвердилось — это не особенность одной конкретной машины, а общая практика этой линейки сборок Sury. Дальше смотрю, куда реально пишет пул:

Код:
grep -E '^(chdir|php_admin_value\[(open_basedir|upload_tmp_dir|session\.save_path)\])' /etc/php/8.4/fpm/pool.d/*.conf

Пусто — значит сайт работает с дефолтного докрута, без явных путей в конфиге пула. По аналогии с основным сервером добавляю override:

Код:
mkdir -p /etc/systemd/system/php8.4-fpm.service.d
cat > /etc/systemd/system/php8.4-fpm.service.d/override.conf <<'EOF'
[Service]
ReadWritePaths=/usr/www
EOF
systemctl daemon-reload
systemctl restart php8.4-fpm

Важный момент по методике: «не упало в логе» — не доказательство само по себе, это может просто означать, что никто и не пытался писать на диск с момента рестарта. Доказательство — реальная запись через приложение:

Код:
journalctl -u php8.4-fpm --since "15 minutes ago" | grep -iE "read-only|permission denied"

— и сразу следом загрузка изображения через сам сайт. Прошло чисто, в логе ничего. Override на месте.

Часть 5. Почему латать дальше не имело смысла​


К этому моменту stало ясно: patch-on-top больше не стратегия. Plucky мёртв с января, а его прямой потомок — 25.10 «Questing Quokka» — сам подходит к EOL примерно в июле того же года. Сидеть на интерим-релизе, который через месяц снова потребует переезда, бессмысленно.

Официальные релиз-ноуты Ubuntu прямо описывают путь: с интерим-релиза вроде 25.04 нельзя прыгнуть в следующую LTS напрямую — обязательная промежуточная остановка на следующем по очереди релизе. Для этой машины маршрут получился такой:

25.04 (plucky, EOL) → 25.10 (questing) → 26.04 LTS (resolute)

Отдельно проверил по живому community-треду: сразу после релиза 26.04 (23.04.2026) апгрейд с 25.10 на неё был временно закрыт самим инструментом до оценки релиз-командой на стабильность — открылось это окно буквально через пару недель после релиза. К моменту, когда дошли руки, прошло уже почти два месяца, так что путь должен был быть полностью открыт.

Часть 6. Сам переезд​


Подготовка перед первым хопом — без неё дальше не стоило и начинать:

  • tmux — обрыв ssh-сессии посреди dist-upgrade чинится не переподключением, а восстановлением системы с нуля;
  • снэпшот всей машины — сделан перед стартом;
  • `apt update && apt upgrade -y && apt autoremove -y` — система должна быть полностью актуальной перед апгрейдом релиза;
  • `Prompt=normal` в `/etc/update-manager/release-upgrades` — с `Prompt=lts` инструмент просто не предложит 25.10, она не LTS.

Дальше — `do-release-upgrade`, и по дороге несколько гейтов, на которых стоило притормозить осознанно, а не жать Enter вслепую:

  • «Foreign Packages Installed» — список из всех наших php8.4-* и php-* от sury, с рекомендацией поставить «поддерживаемые версии из архива Ubuntu». Продолжил осознанно — это и так временный noble-костыль, который планировалось менять отдельно.
  • Кодировка консоли — вопрос про шрифт для локального терминала (Cyrillic/Latin/итд). Это настройка локальной консоли, по ssh её не существует физически — выбрал «23. Guess optimal character set», пусть определяет сам по локали системы.
  • sshd_config — конфликт между версией пакета и локально модифицированным файлом. Оставил локальную версию: не время разбирать дифф вживую, сидя в сессии именно через этот sshd. Новая версия осталась рядом как `.dpkg-dist`, сравню отдельно, не на бегу.
  • Итоговый summary перед каждым из двух хопов (remove/install/upgrade, объём скачивания) — смотрел перед подтверждением каждый раз, искал в «removed» что-нибудь неожиданное вроде nginx или mysql-client.

Оба хопа прошли без растрат: после первого — `questing`, после второго — финал:

Код:
Welcome to Ubuntu 26.04 LTS (GNU/Linux 7.0.0-22-generic x86_64)

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

Часть 7. Что выжило после двух прыжков — и почему​


Первая проверка после апгрейда — что осталось от php-стека:

Код:
ls /etc/apt/sources.list.d/ | grep -i ondrej
ondrej-ubuntu-php-plucky.sources
php-ondrej-jammy.list.disabled

dpkg -l | grep php8.4
[...все одиннадцать пакетов — те же 8.4.22-1+ubuntu24.04.1+deb.sury.org+1...]

Ни один файл, ни один пакет не тронут за оба хопа. На первый взгляд — удача, но за этим стоит конкретный механизм: `do-release-upgrade` отключает сторонние источники, которые соответствуют релизу, с которого идёт апгрейд. А наш файл всё это время был подписан как `Suites: noble` — и ни на одном из двух хопов (plucky→questing, questing→resolute) фактический codename машины не совпадал со строкой в файле. Инструмент его просто не узнал как «источник старого релиза» и не тронул. Сработало — но по случайному совпадению логики, а не потому что так было задумано.

`ProtectSystem=full` и наш `ReadWritePaths=/usr/www` пережили оба хопа без каких-либо действий с моей стороны — drop-in override лежит отдельно от пакета и его обновлениями не перезатирается:

Код:
systemctl cat php8.4-fpm | grep -iE "protectsystem|readwritepaths"
ProtectSystem=full
ReadWritePaths=/usr/www

Часть 8. Старый PPA окончательно мёртв — переезд на собственный канал Sury​


Раз уж добрался до настоящей LTS — самое время убрать костыль с noble и поставить нативную сборку под resolute. Но прямой замены `Suites: noble → resolute` в том же файле не получится: Launchpad-PPA ppa:ondrej/php для 26.04 не публиковался вообще — есть открытый баг-репорт автору с прямой претензией, что репозиторий застрял на noble уже два релиза назад. Сам автор тем временем перенёс основные усилия на отдельный канал — `packages.sury.org` — и там сборки под resolute уже реально есть.

Раз источники разные — старый Launchpad-PPA убираю целиком, а не оставляю рядом с новым (иначе рискую получить конфликт `Signed-By` или задвоенные кандидаты):

Код:
rm -f /etc/apt/sources.list.d/ondrej-ubuntu-php-plucky.sources /etc/apt/sources.list.d/php-ondrej-jammy.list.disabled

apt-get install -y lsb-release ca-certificates curl
curl -sSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb
dpkg -i /tmp/debsuryorg-archive-keyring.deb
echo "deb [signed-by=/usr/share/keyrings/debsuryorg-archive-keyring.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list
apt update

Это официальная схема установки с самого `packages.sury.org` — отдельный keyring-пакет вместо ручного ключа, и одна строка источника, привязанная к реальному codename системы через `$(lsb_release -sc)`.

Часть 9. Ловушка с тильдой​

1781788447063.png

Проверка кандидата показала именно то, что нужно:

Код:
apt-cache policy php8.4-fpm
  Installed: 8.4.22-1+ubuntu24.04.1+deb.sury.org+1
  Candidate: 8.4.22-1+0~20260606.50+ubuntu26.04~1.gbpc1d74e
     500 https://packages.sury.org/php resolute/main

Но прямой `apt install php8.4-fpm ...` (без указания версии) выдал по всем одиннадцати пакетам:

Код:
php8.4-fpm is already the newest version (8.4.22-1+ubuntu24.04.1+deb.sury.org+1).

apt не глючил — он честно сравнил версии по своим правилам, и по этим правилам ошибся относительно того, что реально нужно. Дело в символе `~` в хвосте новой версии (`...+0~20260606.50+...`). В системе сравнения версий Debian/Ubuntu тильда сортируется ниже всего, даже ниже отсутствия символа — это специальный маркер для пред-релизных/снапшотных тэгов (`1.0~rc1` формально младше `1.0`). У resolute-сборки `~` стоит прямо в начале хвоста, и по факту более новая и нужная сборка формально считается «более старой» версией, чем уже установленная noble-сборка. apt отказывается понижать версию по доброй воле — и это, в данном случае, неверное решение, основанное на верной логике.

Лечится явным указанием версии вместо доверия к автоматическому выбору:

Код:
PKGS="php8.4-cli php8.4-common php8.4-curl php8.4-fpm php8.4-gd php8.4-mbstring php8.4-mysql php8.4-opcache php8.4-readline php8.4-xml php8.4-zip"
TARGETS=""
for p in $PKGS; do
  v=$(apt-cache madison "$p" | awk -F'|' '/packages.sury.org\/php resolute/{gsub(/ /,"",$2); print $2; exit}')
  TARGETS="$TARGETS $p=$v"
done
apt install --allow-downgrades $TARGETS

`--allow-downgrades` нужен ровно из-за того же эффекта — без него apt откажется выполнять то, что сам считает понижением версии. Установка прошла, в выводе честно значилось `DOWNGRADING: ...` — название говорит про формальную логику apt, а не про реальное направление изменений.

Часть 10. Последняя паранойя​


Пакеты заменены на диске, но мастер-процесс php8.4-fpm крутился ещё с предыдущего рестарта — стоило убедиться, что он реально перечитал новый бинарник, а не продолжает жить со старым, уже удалённым с диска файлом:

Код:
PID=$(systemctl show -p MainPID --value php8.4-fpm)
ls -l /proc/$PID/exe
/proc/15250/exe -> /usr/sbin/php-fpm8.4

Без суффикса `(deleted)` — процесс и текущий файл на диске совпадают, скорее всего постинст пакета сам перезапустил сервис при установке. Но раз сомнение возникло — дешевле просто перезапустить и проверить, чем продолжать рассуждать:

Код:
systemctl restart php8.4-fpm
systemctl status php8.4-fpm | grep -i active

И снова — реальная загрузка файла через сайт, а не просто тишина в логе.

Итог​


БылоСтало
Ubuntu 25.04 «plucky», EOL с 15.01.2026Ubuntu 26.04 LTS «resolute», поддержка до 2031
apt-daily мёртв молча 281 деньтаймеры снова живы
php8.4 — кросс-сборка под noble через мёртвый Launchpad-PPAнативная сборка resolute с packages.sury.org
ProtectSystem=full — не диагностировандиагностирован, ReadWritePaths поставлен и проверен реальной записью

Что забираю с собой на будущее:

  • Зависший процесс — это не «подождать», это сразу `ps -p` на PID из ошибки лока. Секунды не экономишь, а вот потерять девять месяцев патчей — легко.
  • Третьесторонние PPA, привязанные к конкретному codename текстом в файле, не следят за релизом системы сами — после `do-release-upgrade` каждый такой файл нужно перепроверять руками, а не доверять автоматике.
  • Сравнение версий Debian/Ubuntu — не просто «больше число — новее». Тильда — это не опечатка в номере, это значимый символ сортировки, и от него зависит, поставит apt нужную сборку или тихо откажется.
  • «В логе пусто» доказывает отсутствие конкретной ошибки в конкретный момент, а не отсутствие проблемы. Где можно — проверяю реальной операцией (запись файла, рестарт, hash бинарника), а не молчанием журнала.
Об авторе
Guru
Василий, cистемный админ /gnu/linux/windows/macos/mikrotik/troubleshooter, создатель сайта
Интересуюсь всем что делает инфраструктуру быстрой и надёжной
Открыт к общению и проектам, написать мне можно через форму или в личном сообщении

❗ Если есть пожелания по обзору какого-либо вопроса не представленного на сайте - пиши в комментариях

Комментарии

Нет комментариев для отображения.

Информация о статье

Автор
Guru
Время чтения статьи
10 мин чтения
Просмотры
9
Посл. обновление

Ещё в Linux

Ещё от Guru

Поделиться этой статьёй

Назад
Верх