Bash WP-CLI Update — bash-скрипт, который обновляет ядро, плагины, темы и схему базы WordPress на списке сайтов, заходя в каждый под его владельцем, с дампом базы до изменений, JSON-отчётом и путём восстановления. Это не «обёртка над wp plugin update --all»: у инструмента двадцать режимов, из которых десять ничего не пишут, конфигурация читается как данные (а не подключается через source), дамп снимается до первого изменения, а параллельный прогон отсекается блокировкой. Ниже — разбор возможностей версии 8.0.4 с примерами и с тем, что я реально прогнал 24 октября 2026 года на Debian 12 с настоящим WordPress 7.1.3: команды, вывод, коды возврата и найденные шероховатости. Вывод в примерах приведён с сокращениями: длинные пути заменены на /srv/www/example.com и /var/backups/wp-cli-update, служебные строки убраны, значимые — сохранены дословно.

Bash WP-CLI Update — менеджер обслуживания парка WordPress
Обложка: Bash WP-CLI Update 8.0.4 — двадцать режимов и восстановление базы

TL;DR: что даёт менеджер и как проверить за десять минут

  1. Один bash-скрипт (201 706 байт) плюс искатель сайтов. Зависимости: bash >= 4.2, GNU coreutils/findutils, WP-CLI; root нужен потому, что каждый вызов идёт от владельца сайта через runuser/sudo/su.
  2. Двадцать режимов: десять изменяющих (--full, --core, --plugins, --themes, --db-optimize, --db-fix, --cron, --astra, --plugin-manage, --restore-db) и десять только читающих (--list-plugins, --verify, --health, --secrets, --check, --status, --doctor, --list-sites, --list-backups, --completion).
  3. Дамп до изменений и обратный путь: --backup db снимает wp db export перед правкой сайта, --restore-db FILE --yes возвращает базу и сам проверяет её через wp db check.
  4. Конфигурация — данные: файл KEY=VALUE никогда не подключается через source; строка с $(, обратной кавычкой или ; отклоняет файл целиком с кодом 4, групповые права — тоже.
  5. Коды возврата — контракт: 0 успех, 1 операционная ошибка, 2 ошибка аргументов, 3 ошибка окружения (в том числе занятый лок), 4 ошибка конфигурации.
  6. npm-канал работает: пакет bash-wp-cli-update@8.0.4 опубликован 24 октября 2026 года, ставит два шима wp-cli-update и wp-find, версия из npm-копии отвечает Bash_WP-CLI_Update.sh 8.0.4.
  7. Проверка за десять минут — четыре read-only команды, ничего не меняют и не берут блокировку:
sudo wp-cli-update --doctor                  # хост: wp, переключение пользователя, инструменты, место
sudo wp-cli-update --check                   # список сайтов и каждый сайт: wp-config.php, владелец
sudo wp-cli-update --health                  # версия ядра, ожидающие обновления, размер базы
sudo wp-cli-update --full --site /srv/www/example.com --dry-run   # что именно будет выполнено

Ожидаемый результат: --doctor называет бинарник wp и механизм переключения пользователя, --check печатает по галочке на сайт, --health выводит таблицу, сухой прогон показывает конкретные команды wp. Если хоть один шаг отвечает не то — до парка не доходить.

Оглавление

Проблема: цикл по сайтам, который ничего не гарантирует

Типичный самодельный прогон выглядит так:

for site in $(cat /root/sites.txt); do
    wp --path="$site" --allow-root plugin update --all
    wp --path="$site" --allow-root core update
done

Он работает ровно до первого сбоя. Что в нём отсутствует по существу:

  • владелец сайта. wp под root создаёт файлы root:root в wp-content — после этого плагин не может писать в свой каталог, а обновление из админки падает;
  • дамп до изменений. Обновление несовместимого плагина откатывать нечем: файлы прежней версии уже перезаписаны;
  • границы отказа. Ошибка на третьем сайте не останавливает цикл и не видна в итоге: код возврата потерян, stderr ушёл в никуда;
  • отчёта. Утром нельзя ответить, что именно обновилось и на каком сайте;
  • защиты от наложения. Ночной cron встречается с ручным прогоном — два процесса одновременно тянут один и тот же пакет;
  • различения режимов. Проверить состояние парка нечем: любой запуск что-то меняет, поэтому «посмотреть» и «сделать» — одна и та же команда.

Bash WP-CLI Update закрывает именно эти шесть пунктов, и это его настоящая ценность — а не сам факт обновления плагинов, которое умеет и wp.

Что это за инструмент и чего он не делает

Устройство простое и от этого предсказуемое: один менеджер (Bash_WP-CLI_Update.sh, 201 706 байт), один искатель сайтов (Find_WP_Senior.sh, 33 136 байт), пример конфигурации, набор поведенческих тестов и каталог contrib/ с юнитом systemd, таймером, примером cron и правилом logrotate. Никакой сборки, зависимостей от Ruby/Python/Node и внешних библиотек: только bash и GNU-утилиты.

Требование Значение
ОС Linux (пакет помечен os: linux)
Оболочка bash >= 4.2
Обязательное GNU coreutils/findutils, WP-CLI, root, одна из runuser / sudo / su
Опциональное flock, timeout (или perl), tar, gzip, nice, ionice, curl, mail
Node (только для npm-пути) >= 16
Лицензия PolyForm Noncommercial 1.0.0

Что инструмент не делает и не обещает:

  • не ставит WP-CLI (это отдельная установка, npm её тоже не выполняет);
  • не лечит несовместимость плагинов с новой версией ядра — он даёт дамп и путь назад, а решение остаётся за администратором;
  • не заменяет полноценный бэкап хостинга: --backup full — это tar.gz дерева сайта, полезный для откатов плагинов, но не для восстановления сервера;
  • не работает на macOS: встроенный там bash 3.2 не подходит, нужен контейнер или виртуальная машина.

Двадцать режимов

Режим задаётся ровно один на прогон; два режима сразу — ошибка аргументов с кодом 2 (проверено: --full --health → exit=2).

Режим Что делает Пишет
--full ядро + плагины + темы + схема базы + db optimize/db repair + cron (+ Astra при лицензии) да
--core core update, core update-db --skip-plugins да
--plugins обновление всех плагинов либо перечисленного набора (--only-active, --exclude-plugins, --hold-plugins) да
--themes обновление тем с --exclude-themes / --hold-themes да
--db-optimize db optimize, затем db repair да
--db-fix только db repair да
--cron cron event run --due-now да
--astra обновление astra-addon с активацией лицензии при необходимости да
--plugin-manage активировать, деактивировать или удалить один плагин (delete требует --yes) да
--restore-db FILE импорт дампа в базу сайта; обязателен --yes, свежий дамп снимается первым да
--list-plugins инвентаризация плагинов в table, json, csv или tsv нет
--verify core verify-checksums и plugin verify-checksums --all нет
--health версия ядра, ожидающие обновления плагинов и тем, размер базы нет
--secrets имена секретных констант в wp-config.php, права файла, старые копии нет
--check окружение и каждый сайт из списка нет
--status сводка последнего прогона и размеры журналов нет
--doctor диагностика хоста, список сайтов не нужен нет
--list-sites разрешённый список сайтов нет
--list-backups дампы, снятые инструментом, от свежих к старым нет
--completion скрипт дополнения для bash или zsh нет

Отдельно стоит правило: read-only значит read-only. --health, --secrets, --list-plugins, --verify, --list-backups и --check не берут блокировку, не снимают дамп и не пишут ничего, кроме строк журнала. Поэтому --health можно дёргать из мониторинга хоть каждую минуту.

Установка: три канала доставки

1. npm. Пакет bash-wp-cli-update опубликован 24 октября 2026 года (версия 8.0.4, распакованный размер 351 384 байта, лицензия PolyForm-Noncommercial-1.0.0, os: linux, node >= 16).

npm install -g bash-wp-cli-update
wp-cli-update --version        # Bash_WP-CLI_Update.sh 8.0.4
wp-find --help

Проверено на Debian 12 с node 22.22.1 и npm 11.12.1: npm install --prefix /opt/wpcli-npm bash-wp-cli-update добавляет один пакет, кладёт wp-cli-update и wp-find в node_modules/.bin, обе команды работают, и весь прогон в этой статье выполнен именно npm-копией. Шим разрешает свой настоящий путь через readlink -f и делает exec bash .../Bash_WP-CLI_Update.sh "$@" — то есть аргументы доходят без искажений.

Важная граница: npm — канал доставки, а не установщик. Он не ставит WP-CLI, не создаёт каталоги /var/log/wp-cli-update, /var/backups/wp-cli-update, /var/lib/wp-cli-update, не пишет /etc/wp-cli-update.conf и ничего не запускает. Для промхоста это означает: после npm всё равно нужен шаг 2 или ручная подготовка каталогов и конфигурации.

2. Установщик из архива. Идемпотентный, с сухим прогоном и удалением:

curl -LO https://github.com/paulmann/Bash_WP-CLI_Update/releases/latest/download/bash-wp-cli-update-8.0.4.tar.gz
tar xzf bash-wp-cli-update-8.0.4.tar.gz
sudo ./install.sh --dry-run      # показать шаги, ничего не делать
sudo ./install.sh --systemd      # сделать и поставить ночной таймер
sudo ./install.sh --prefix /opt/wp --config /etc/wp-cli-update.conf

3. Вручную — клон репозитория, chmod 755 на оба скрипта, символические ссылки в /usr/local/bin, три каталога и конфигурация с правами 600.

Во всех трёх случаях после установки список сайтов строится искателем, а не руками:

sudo wp-find --output /var/lib/wp-cli-update/wp-found.txt /var/www /home
sudo wc -l /var/lib/wp-cli-update/wp-found.txt
sudo wp-cli-update --list-sites | head

wp-find умеет JSON, ограничение глубины и исключения; в моём случае он не понадобился только потому, что песочница — один сайт, переданный через --site.

Конфигурация как данные

Слои от слабого к сильному: встроенные значения → /etc/wp-cli-update.conf → wp-cli-update.conf рядом со скриптом → окружение WP_CLI_UPDATE_* → командная строка. Посмотреть, что победило и откуда пришло:

sudo wp-cli-update --print-config | grep -E 'WP_CLI_PATH|JOBS|BACKUP |MIN_FREE_MB'
WP_CLI_PATH              /usr/local/bin/wp                  file:good.conf -
JOBS                     2                                  file:good.conf -
BACKUP                   db                                 file:good.conf -
MIN_FREE_MB              500                                file:good.conf -

Две защиты важнее остальных и обе проверены:

# строка с метасимволом → файл отклонён целиком
Bash_WP-CLI_Update.sh: config: /etc/wp-cli-update.conf:3: shell metacharacter in a config line; refusing to read this file (use plain KEY=VALUE)
# exit=4

# групповые права на запись → отказ до чтения
Bash_WP-CLI_Update.sh: config: /etc/wp-cli-update.conf: group- or world-writable; chmod 600 or 640 it first (a writable config would be executable code with root privileges)
# exit=4

Это не паранойя, а разница между «конфигурация» и «скрипт от root»: файл, доступный на запись группе, — это исполняемый код с правами root.

Профиль для продакшена (пути те, что создаёт install.sh; без них встроенные значения указывают на каталог скрипта, и ночной прогон ищет список сайтов не там):

# /etc/wp-cli-update.conf
WP_CLI_PATH=/usr/local/bin/wp
SITES_FILE=/var/lib/wp-cli-update/wp-found.txt
LOG_FILE=/var/log/wp-cli-update/manager.log
ERROR_LOG_FILE=/var/log/wp-cli-update/errors.log
BACKUP_DIR=/var/backups/wp-cli-update
AUTO_DISCOVER=0
JOBS=4
BACKUP=db
KEEP_BACKUPS=5
BACKUP_AGE_DAYS=14
BACKUP_COMPRESS=1
MIN_FREE_MB=500
NICE_LEVEL=10
IONICE_CLASS=idle
TIMEOUT=600
RETRY=2
RETRY_DELAY=30
MAX_FAILURES=5
FAIL_ON=any
REPORT_FILE=/var/log/wp-cli-update/last-run.json
LOG_JSON=1

Ключи 6.1-эпохи (WP_TIMEOUT, WP_CLI_BIN, SITE_URL, BACKUP_KEEP, DEFAULT_JOBS) принимаются с предупреждением — конфигурация старой установки не ломается молча.

Безопасность по умолчанию

Семь правил, заложенных в инструмент, стоит знать до первого боевого прогона — они объясняют его поведение:

  1. Аргументы, а не строки. Каждый вызов WP-CLI — bash-массив; ничего не собирается конкатенацией, eval не используется, printf %q тоже. Переключение пользователя передаёт аргументы позиционными параметрами, поэтому путь с кавычкой или пробелом ничего не ломает.
  2. Конфигурация не исполняется. source нет вовсе (см. выше).
  3. Лицензия не становится аргументом. Ключ Astra пишется во временный файл, доступный root и группе владельца сайта, дочерний shell читает его во время вызова, файл удаляется при возврате. В ps ключа не видно.
  4. Дамп раньше изменения. Неудачный бэкап останавливает сайт, а не продолжает обновление без защиты (--fail-on never ослабляет это осознанно).
  5. Разрушающее — в два ключа. --restore-db требует --yes, предварительно снимает текущую базу и после импорта выполняет wp db check.
  6. Права дампа. Начиная с 8.0.3 дамп пишется в приватный временный каталог владельца сайта и переносится в хранилище root-ом, который ставит root:root 0640. Проверено: -rw-r----- root:root 98606 .../db-20261024-151242-1968398.sql. До 8.0.3 дамп писался сразу в дерево бэкапов, и на каталоге 0700 (а именно так его создаёт install.sh) wp db export падал на каждом сайте с владельцем не-root — то есть ночной прогон с BACKUP=db пропускал весь парк, сообщая только предупреждение.
  7. Коды возврата различают причину. 2 — ошибка аргументов, 4 — ошибка конфигурации, 3 — окружение (в том числе занятый лок), а не «что-то пошло не так» с кодом 1.

Пример 1: инвентаризация парка без изменений

Начинать знакомство с инструментом на проде стоит с режимов, которые ничего не пишут.

sudo wp-cli-update --health
SITE                               CORE      PLUGINS  THEMES   DB MiB
-------------------------------------------------------------------------------
example.com                        7.1.3     0        0        1

Колонки PLUGINS и THEMES — число ожидающих обновлений, DB MiB — размер базы. Таблица идёт в stdout, служебные строки — в stderr, поэтому её можно завернуть в скрипт и сравнить с прошлой ночью.

Инвентаризация плагинов — в машинных форматах, без «прогресса» в потоке данных:

sudo wp-cli-update --list-plugins --format csv --site /srv/www/example.com
sudo wp-cli-update --list-plugins --format json --fields name,status,update,version | jq '.[] | select(.update != "none")'

Проверка целостности — единственный режим, который отвечает на вопрос «а файлы те, что ожидались»:

sudo wp-cli-update --verify --site /srv/www/example.com

Две операции: wp core verify-checksums и wp plugin verify-checksums --all; итог operations ok: 2, код 0. Ненулевой код здесь означает изменённые файлы ядра или плагина — то, что не покажет ни одна проверка версий.

Аудит секретов — с маскированием значений:

sudo wp-cli-update --secrets --site /srv/www/example.com
ERR /srv/www/example.com: wp-config.php is world-readable (mode 644); chmod 640 it
  /srv/www/example.com/wp-config.php:30  DB_PASSWORD  <redacted>
  /srv/www/example.com/wp-config.php:52  AUTH_KEY  <redacted>
  ...
INF /srv/www/example.com: 10 secret-bearing constant(s) found (values not printed)

Обратите внимание: это не список, а гейт. Найденные имена печатаются, значения — нет, а мировые права на wp-config.php дают код 1. Именно так это и должно работать в регулярной проверке парка: «посмотрел» и «всё в порядке» — разные вещи.

Проверка площадки целиком:

sudo wp-cli-update --check
== requested site ==
site            : /srv/www/example.com
  ✓ wp-config.php
  ✓ wp-includes/version.php
  ✓ owner: siteowner

Пример 2: один сайт — от сухого прогона до обновления

Сухой прогон печатает не «намерения», а точные команды с окружением и приоритетом:

sudo wp-cli-update --full --site /srv/www/example.com --backup db --dry-run
INF [dry-run] would export the database of /srv/www/example.com
INF [dry-run] (siteowner@example.com) env DOCUMENT_ROOT=/srv/www/example.com HOME=/srv/www/example.com 
    nice -n 10 /usr/local/bin/wp --path=/srv/www/example.com core update
INF [dry-run] ... plugin update --all
INF [dry-run] ... theme update --all
INF [dry-run] ... core update-db --skip-plugins
INF [dry-run] ... db optimize
INF [dry-run] ... db repair
INF [dry-run] ... cron event run --due-now
INF [dry-run] ... plugin update astra-addon

Видно сразу три вещи, ради которых сухой прогон и нужен: порядок операций (ядро → плагины → темы → схема → оптимизация → cron → Astra), пользователь, от которого пойдёт каждая команда, и место дампа в последовательности.

Боевой прогон того же сайта:

sudo wp-cli-update --full --site /srv/www/example.com 
    --backup db --backup-dir /var/backups/wp-cli-update 
    --report /var/log/wp-cli-update/run.json
 OK database backup: /var/backups/wp-cli-update/example.com/db-20261024-151242-1968398.sql (98606 bytes)
 OK changed (plugin): akismet 5.3 -> 5.7.2
 OK changed (theme): twentytwentyfour 1.1 -> 1.6
  operations ok:         7
  operations failed:     0
  changes applied:       2
  duration:              29s
✓ run finished without errors

Строка changed (plugin): akismet 5.3 -> 5.7.2 — это и есть ответ на утренний вопрос «что вчера обновилось», причём в том же виде он попадает в JSON-отчёт:

{"run":{"version":"8.0.4","mode":"full","dry_run":false,"host":"web01","duration_s":29,
        "sites_total":42,"sites_ok":41,"sites_failed":1,"sites_skipped":0,
        "ops_ok":180,"ops_failed":1,"warnings":2,"retries":1,"changes":6,"exit":1},
 "sites":[{"path":"/srv/www/a","status":"OK"},{"path":"/srv/www/b","status":"FAILED"}],
 "changes":[{"site":"/srv/www/a","kind":"plugin","name":"jetpack","from":"13.0","to":"13.5"}]}

Формат отчёта стабилен и плоский: сводка run, массив sites, массив changes. Этого достаточно, чтобы положить файл в сборщик логов и построить график «сколько сайтов обновляется за ночь».

Пример 3: парк целиком — параллельность и предохранитель

sudo wp-cli-update --full --jobs 4 --backup db --report /var/log/wp-cli-update/run.json --retry 2 --max-failures 5

Что здесь важно по смыслу, а не по синтаксису:

Ключ Зачем именно он
--jobs 4 партия из четырёх сайтов; это не «4 потока на сайт», а четыре сайта параллельно
--retry 2 повтор для таймаута загрузки или занятой базы; для неверного --url повтор бесполезен, поэтому по умолчанию 0
--max-failures 5 предохранитель: сломанный WP-CLI или полный диск останавливают прогон, а не пишут одну и ту же ошибку двести раз
--run-timeout 3600 общий бюджет времени: остаток парка помечается пропущенным, а не «успешно завершённым»
--min-free-mb 500 отказ писать дамп, если на диске меньше 500 МиБ — иначе wp db export добьёт файловую систему
--nice 10 / --ionice idle обновление не должно выдавливать веб-сервер из процессора и диска

Дисциплина повторов — отдельное достоинство: --retry не «пробует ещё раз всё подряд», а перезапускает именно упавший сайт, и каждый повтор виден в отчёте (retries).

Пример 4: ночной прогон и мониторинг

Штатный путь — таймер systemd из contrib/ (или пример для cron), с логом и ротацией:

sudo ./install.sh --systemd
systemctl list-timers 'wp-cli-update*'

Мониторинг держится на двух свойствах: --health не берёт блокировку и ничего не пишет, а коды возврата различают причину. Поэтому проверка из Zabbix или самописного дежурного скрипта выглядит так:

sudo wp-cli-update --health || notify "health exit=$?"
sudo wp-cli-update --status | tail -12

Уведомления — вебхук и почта, с условием отправки (--notify-on never|failure|always). Полезная деталь: полезная нагрузка содержит только счётчики — ни путей, ни лицензии, ни содержимого сайтов. Для сборщика логов есть --json-log: одна строка журнала — один JSON-объект.

--status показывает сводку последнего прогона и размеры журналов — этого хватает, чтобы заметить «лог вырос до 5 МБ и ротация не работает».

Коды возврата в дежурной практике читаются так:

Код Что значит Действие дежурного
0 при --fail-on any каждая операция на каждом сайте успешна ничего, свериться с .changes в отчёте
1 хотя бы одна операция или сайт упали sites_failed в отчёте, затем ERROR_LOG_FILE
2 ошибка командной строки ошибка в runbook или таймере, а не на хосте
3 окружение: нет wp, занят лок, запуск не от root сначала --doctor, потом повтор
4 конфигурация отклонена права файла (600) или метасимвол в строке
130 / 143 / 131 SIGINT / SIGTERM / SIGHUP сводка и отчёт уже записаны; сайт, на котором прервалось, проверить --verify

Пример 5: восстановление базы

Обратный путь встроен и намеренно неудобен. Сначала — что есть:

sudo wp-cli-update --list-backups --site /srv/www/example.com
/srv/www/example.com
2026-10-24 15:15      98550 /var/backups/wp-cli-update/example.com/db-20261024-151517-2058940.sql

Без подтверждения восстановление не пойдёт:

sudo wp-cli-update --restore-db /var/backups/.../db-20261024-151517-2058940.sql --site /srv/www/example.com
# ERR /srv/www/example.com: --restore-db overwrites the live database; re-run with --yes to confirm
# exit=1

С подтверждением — три шага: свежий дамп текущего состояния, импорт, проверка:

sudo wp-cli-update --restore-db /var/backups/.../db-20261024-151517-2058940.sql 
    --site /srv/www/example.com --yes
WRN /srv/www/example.com: restoring the database from .../db-20261024-151517-2058940.sql
 OK database backup: .../db-20261024-152025-2155909.sql (98550 bytes)
 OK /srv/www/example.com: database restored and db check passed

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

Одновременные прогоны и блокировка

Второй прогон при живом первом не начинает работу:

ERR another Bash_WP-CLI_Update.sh run holds /var/lib/wp-cli-update/.lock (pid 2127002); refusing to run concurrently
# exit=3

Если наложение возможно по расписанию (например, ручной прогон поверх ночного), есть ожидание:

sudo wp-cli-update --full --lock-wait 900 --backup db

Проверено: без --lock-wait второй прогон завершается кодом 3, с --lock-wait — ждёт и завершается успешно. Read-only режимы блокировку не берут, поэтому мониторинг не мешает ночной работе.

Подводные камни

1. Уведомления PHP в stdout ломают разбор. Инструмент читает вывод WP-CLI как данные. На хосте с PHP 8.5 и штатным wp-cli.phar сам WP-CLI печатает Deprecated: ... in phar:///usr/local/bin/wp/vendor/... в стандартный поток, и тогда:

ERR wp on /srv/www/example.com did not return a JSON plugin list
ERR site failed: /srv/www/example.com

а в --health остаётся пустой колонка CORE. Это не дефект менеджера, но диагностируется он не сразу. Лечится обёрткой, которая гасит уведомления, и указанием её через --wp (проверено, после этого --list-plugins и --health работают штатно):

cat > /usr/local/bin/wp-quiet <<'EOF'
#!/bin/sh
exec php -d error_reporting="E_ALL & ~E_DEPRECATED & ~E_STRICT" /usr/local/bin/wp "$@"
EOF
chmod 755 /usr/local/bin/wp-quiet

sudo wp-cli-update --health --wp /usr/local/bin/wp-quiet

Правильный порядок — сначала обновить WP-CLI и PHP-окружение, а обёртку держать как временную меру: любое сообщение в stdout — это всегда риск для любого инструмента, который парсит вывод.

2. --skip-if-no-updates не пропускает сайт в --full и --core. Замер: wp core check-update возвращает код 0 и когда сайт актуален (Success: WordPress is at the latest version.), и когда обновления есть (печатает таблицу версий, код тоже 0). Внутренняя проверка «ядро актуально» построена на этом коде, поэтому для режимов с ядром сайт считается «есть что делать» всегда. Практическое следствие: на полностью актуальном сайте --full --skip-if-no-updates выполнил 7 операций, sites_skipped: 0. Для --plugins и --themes тот же ключ работает как задумано: INF ... nothing pending, skipped, sites_skipped: 1, код 0. Если ночной прогон должен экономить время — делите его на --plugins и --themes либо полагайтесь на отчёт, а не на пропуск.

3. --full всегда делает шаг Astra. Даже если astra-addon не установлен, в прогоне появляются две строки предупреждений (plugin update astra-addon did not succeed, no Astra licence configured; the Astra step is skipped). Это предупреждения, не ошибки, но в отчёте warnings: 2 на каждом сайте. Если Astra в парке нет — либо не используйте --full, либо держите отдельные режимы, чтобы шум не маскировал настоящие предупреждения.

4. --secrets — это проверка с ненулевым кодом при находке. Мировые права на wp-config.php дают exit=1 и строку ERR ... world-readable (mode 644); chmod 640 it. В скрипте дежурного это надо учитывать: код 1 здесь означает «нашли проблему», а не «команда не сработала».

5. --restore-db без --yes — код 1, а не 2. Аргументы формально верны, отказ приходит на уровне операции: сайт помечается failed, итог — exit=1. Для автоматики различайте «не подтвердил» и «импорт сломался» по тексту журнала.

6. Права на хранилище дампов. Дамп создаётся как root:root 0640, а временный каталог — как приватный каталог владельца сайта. Если вы чистите BACKUP_DIR руками или монтируете его с nosuid/особыми ACL, проверьте, что root может писать, а владелец сайта — читать свой временный каталог.

7. macOS и BSD-утилиты не поддерживаются. Пакет помечен os: linux, инструменту нужны bash 4.2 и GNU coreutils. Для проб на ноутбуке — контейнер или виртуальная машина.

8. Лицензия не для коммерческой инфраструктуры. PolyForm Noncommercial 1.0.0 — это не MIT: обслуживание парка сайтов клиентов под неё не подпадает без отдельной лицензии. Проверять до раскатки, а не после.

9. WP-CLI должен быть найден. По умолчанию путь /usr/local/bin/wp. Если phar лежит иначе — --wp или WP_CLI_PATH; иначе --doctor покажет настроенный путь, а первый же прогон получит wp: command not found в журнале ошибок.

Чек-лист внедрения

  1. bash --version | head -1 — не ниже 4.2; id -u — 0 при боевом прогоне.
  2. command -v wp, command -v runuser || command -v sudo || command -v su — оба обязательны.
  3. Установка одним каналом: npm, install.sh или вручную; каталоги /var/log, /var/backups, /var/lib созданы.
  4. /etc/wp-cli-update.conf — права 600, AUTO_DISCOVER=0, пути абсолютные.
  5. sudo wp-find --output /var/lib/wp-cli-update/wp-found.txt /var/www — список сайтов собран.
  6. sudo wp-cli-update --print-config — фактическое состояние совпадает с ожидаемым.
  7. sudo wp-cli-update --doctor — wp, переключение пользователя, flock, timeout, место на диске.
  8. sudo wp-cli-update --check — код 0 и по галочке на каждый сайт.
  9. Один сайт: --dry-run, затем боевой прогон с --backup db.
  10. Проверка результата: --list-backups, --verify, --health, отчёт run.json.
  11. Только теперь — парк: --jobs 4 --max-failures 5 --retry 2 --report ....
  12. Таймер включён, --health заведён в мониторинг, журнал ротируется.

FAQ

Чем это отличается от wp plugin update --all в цикле?

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

Нужен ли root?

Да, на время работы. Менеджер переключается в системного пользователя каждого сайта, иначе файлы в wp-content станут root:root и сайт потеряет возможность обновляться из админки. Для проб есть --no-user-switch, но на парке это не то поведение, которое нужно.

Можно ли ставить через npm на боевой хост?

Можно: с 24 октября 2026 года пакет bash-wp-cli-update@8.0.4 есть в реестре, установка проверена. Но npm приносит только два шима — каталоги, конфигурацию и WP-CLI он не создаёт, и root во время работы всё равно нужен.

Что будет, если бэкап не удался?

Сайт останавливается: инструмент не обновляет то, что не смог защитить. Это поведение по умолчанию; --fail-on never его ослабляет осознанно и должно применяться только там, где внешний бэкап гарантирован.

Обновление прервали по таймауту — что дальше?

Прогон завершится кодом прерывания (130/143/131), частичная сводка всё равно печатается, JSON-отчёт пишется. Сайт, на котором прервалось, проверяйте --verify и, при необходимости, откатывайте базу из дампа --list-backups.

Как понять, что инструмент не врёт про «всё ок»?

По коду возврата и по отчёту: exit 0 при fail-on any означает, что каждая операция на каждом сайте завершилась успешно. Отчёт дополнительно содержит ops_ok, ops_failed, changes и warnings — расхождение между «ноль ошибок» и «ноль изменений» видно сразу.

Итог

Bash WP-CLI Update решает задачу, которая обычно живёт в виде полусамодельного скрипта у каждого администратора парка: обновить WordPress на десятках сайтов, ничего не потеряв и сумев объяснить утром, что произошло. Ценность здесь не в скорости обновления, а в шести гарантиях — владелец сайта, дамп до изменений, отчёт, коды возврата, блокировка и обратный путь к базе. Версия 8.0.4 добавляет к этому третий канал доставки (npm) и честно оговаривает его границы.

Проверка на живом WordPress 7.1.3 подтвердила основной сценарий целиком: --doctor → --check → --health → сухой прогон → боевое обновление (7 операций, 2 изменения, дамп 0640) → восстановление базы с db check. Найденные шероховатости — чувствительность к уведомлениям PHP в stdout, неработающий пропуск сайта в режимах с ядром и неизбежный шаг Astra в --full — перечислены выше честно, потому что знать о них дешевле, чем выяснять ночью.

Цитата: Менеджер парка начинается не с команды обновления, а с ответа на вопрос «чем я откачусь, если обновление окажется неудачным»; всё остальное — удобство вокруг этого ответа.

Связанные материалы

LLM Summary

  • Тема: Bash WP-CLI Update — bash-менеджер обслуживания парка WordPress через WP-CLI; разобрана версия 8.0.4 (релиз 21.10.2026, npm-публикация 24.10.2026).
  • Устройство: один менеджер (201 706 байт) и искатель сайтов (33 136 байт); зависимости — bash >= 4.2, GNU coreutils/findutils, WP-CLI, root, одна из runuser/sudo/su; лицензия PolyForm Noncommercial 1.0.0; os: linux.
  • Режимы: двадцать — десять изменяющих (--full, --core, --plugins, --themes, --db-optimize, --db-fix, --cron, --astra, --plugin-manage, --restore-db) и десять read-only (--list-plugins, --verify, --health, --secrets, --check, --status, --doctor, --list-sites, --list-backups, --completion).
  • Ключевые команды: wp-cli-update --doctor, --check, --health, --full --site X --backup db --dry-run, --full --jobs 4 --backup db --report run.json, --list-backups, --restore-db FILE --yes, --print-config.
  • Гарантии: дамп базы до изменений (root:root 0640), отчёт JSON со списком изменений, блокировка от параллельного прогона, коды возврата 0/1/2/3/4, конфигурация читается как данные без source.
  • Замеры 24.10.2026: боевой прогон на WordPress 7.1.3 — 7 операций, 2 изменения, дамп 98 606 байт, 29 с; восстановление базы с db check passed; второй прогон при живом первом — код 3.
  • Найденные ограничения: уведомления PHP в stdout ломают --list-plugins и колонку CORE в --health (лечится обёрткой --wp); --skip-if-no-updates не пропускает сайт в --full/--core, потому что wp core check-update возвращает 0 в обоих случаях; --full всегда выполняет шаг Astra; --secrets возвращает 1 при находке.
  • Кому полезно: администраторам парков WordPress на Linux, хостингам и студиям, дежурным, которым нужен read-only обзор ожидающих обновлений без блокировки.
  • Ссылки: https://github.com/paulmann/Bash_WP-CLI_Update, https://github.com/paulmann/Bash_WP-CLI_Update/releases/tag/v8.0.4, https://www.npmjs.com/package/bash-wp-cli-update

Промт для агента (установка)

Готовый промт: шаги и команды взяты из AGENTS.md репозитория — это инструкция автора для агента, а не пересказ. Скопируйте блок целиком.

Задача: поставить Bash WP-CLI Update 8.0.4 на Linux-хост, проверить его и обновить один сайт.

Материалы:
- репозиторий: https://github.com/paulmann/Bash_WP-CLI_Update
- инструкция для агентов (читать первой): https://github.com/paulmann/Bash_WP-CLI_Update/blob/main/AGENTS.md
- релиз: https://github.com/paulmann/Bash_WP-CLI_Update/releases/tag/v8.0.4

Два правила из AGENTS.md, которые важнее остальных:
1) не запускать изменяющий режим на боевом хосте с первого раза: установка, --doctor, --check,
   один сайт с --dry-run, один сайт по-настоящему, и только потом парк;
2) не удалять бэкапы, файлы сайтов и базы «чтобы прибраться»: остановиться и доложить.

Предусловия — каждую строку сверить с ожидаемым ответом; расхождение это стоп и отчёт:
bash --version | head -1
id -u
command -v wp
command -v runuser || command -v sudo || command -v su
command -v flock; command -v timeout; command -v tar; command -v gzip

Установка (любой канал, один):
npm install -g bash-wp-cli-update
либо: ./install.sh --dry-run && sudo ./install.sh --systemd
либо вручную: chmod 755 оба скрипта, ссылки в /usr/local/bin, три каталога, конфиг 600.

Конфигурация: WP_CLI_PATH, SITES_FILE, AUTO_DISCOVER=0, LOG_FILE, ERROR_LOG_FILE, BACKUP=db,
BACKUP_DIR, KEEP_BACKUPS, MIN_FREE_MB, JOBS, TIMEOUT, RETRY, MAX_FAILURES. Файл не подключается
через source: обратная кавычка, $(, |, ;, &, < или > в строке отклоняют файл целиком.

Список сайтов и проверка по порядку, шаг не пропускать:
sudo wp-find --output /var/lib/wp-cli-update/wp-found.txt /var/www /home
sudo wp-cli-update --print-config
sudo wp-cli-update --doctor
sudo wp-cli-update --check
sudo wp-cli-update --health
sudo wp-cli-update --full --site /srv/www/example.com --backup db --dry-run
sudo wp-cli-update --full --site /srv/www/example.com --backup db
sudo wp-cli-update --status

Ограничения: не запускать тесты под root «чтобы прошли проверки переключения пользователя»;
не отключать fail-on и не менять JOBS/BACKUP «временно»; не вызывать --restore-db без причины
и без --yes; если команда падает — остановиться и показать команду, вывод и код возврата.

Что считать успехом: --doctor называет wp и механизм переключения пользователя; --check даёт
код 0 и галочку по каждому сайту; --health печатает таблицу; сухой прогон показывает ожидаемые
команды; боевой прогон одного сайта даёт код 0, а .changes в отчёте совпадают с увиденным.