blog.thms.uk

Patching PHP Before Remi Does: Rebuilding Remi’s Packages on AlmaLinux

Today’s release includes a critical PHP security fix, so I need to update PHP on my AlmaLinux servers. I’m running PHP 8.5.11 from Remi’s repo. Unfortunately that repo isn’t getting the update today, so I’ll have to build from source.

Post by @[email protected]
View on Mastodon

This post is a rough walkthrough of how I’m going to do that. I’ll be building on a VM that matches my prod server as closely as possible.

1. Build VM

Create a VM: AlmaLinux 10, x86_64, around 4 vCPU, 8 GB RAM and 30 GB disk.

Set up repositories to match prod, so the build dependencies resolve the same way:

dnf install -y epel-release
dnf config-manager --set-enabled crb
dnf install -y https://rpms.remirepo.net/enterprise/remi-release-10.rpm
dnf install -y rpm-build rpmdevtools dnf-plugins-core

Get Remi’s 8.5.11 source RPM and install its build dependencies:

dnf download --source php85-php-8.5.11
rpm -ivh php85-php-8.5.11-1.el10.remi.src.rpm
sudo dnf builddep -y ~/rpmbuild/SPECS/php.spec

2. Prepare prod’s rollback

On the prod server, as root:

rpm -qa 'php85-*' 'gd3php' 'oniguruma5php' | sort > /root/php-before.txt
mkdir -p /root/php-rollback && cd /root/php-rollback
dnf download $(rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE}\n' 'php85-php*' | grep -v pecl)

Keeping local copies of Remi’s 8.5.11 packages means you can roll back even if the repo changes later. If the host supports snapshots (an EBS snapshot, or a VPS provider snapshot), take one before installing the update as well.

You shouldn’t need to rebuild PECL extensions (redis, swoole, whatever). The extension API doesn’t change within a patch release, so the existing extension packages keep working with your rebuilt core.

3. Build PHP

Check the announcement to confirm the version number, and change V below if needed:

V=8.5.12
cd ~/rpmbuild/SOURCES
curl -fLO https://www.php.net/distributions/php-$V.tar.xz
curl -fLO https://www.php.net/distributions/php-$V.tar.xz.asc

cd ~/rpmbuild/SPECS
sed -i "s/^%global upver .*/%global upver        $V/" php.spec
sed -i 's/^Release:\s*1%{?dist}/Release: 0.1%{?dist}/' php.spec
rpmspec -q --srpm --qf '%{NAME}-%{VERSION}-%{RELEASE}\n' php.spec
# expect: php85-php-8.5.12-0.1.el10.remi

rpmbuild -bp php.spec              # takes seconds: checks the tarball signature and applies all patches
time rpmbuild -bb php.spec --nocheck 2>&1 | tee ~/build-$V.log

--nocheck skips the test suite. The build log must end with + exit 0.

Confirm the package names start with php85-php- and carry the new version:

rpm -qp --qf '%{NAME}-%{VERSION}-%{RELEASE}\n' ~/rpmbuild/RPMS/x86_64/php85-php-fpm-$V-*.rpm

4. Ship to prod

Collect only the 14 subpackages the server has, checksum them, and copy them across. On the build VM:

rm -rf ~/ship && mkdir ~/ship && cd ~/rpmbuild/RPMS/x86_64
for p in php85-php php85-php-bcmath php85-php-cli php85-php-common php85-php-fpm \
         php85-php-gd php85-php-intl php85-php-mbstring php85-php-mysqlnd php85-php-pdo \
         php85-php-process php85-php-soap php85-php-sodium php85-php-xml; do
  cp $p-$V-*.x86_64.rpm ~/ship/
done
cd ~/ship && ls *.rpm | wc -l      # expect 14
sha256sum *.rpm > SHA256SUMS
ssh <prod> mkdir -p php-new
scp ./* <prod>:~/php-new/

5. Install patched version

Log into the prod server and, as root, run:

cd ~/php-new && sha256sum -c SHA256SUMS
dnf install --assumeno ./*.rpm     # expect: Upgrade 14 packages, nothing else
dnf install -y ./*.rpm
/opt/remi/php85/root/usr/sbin/php-fpm -t && systemctl reload php85-php-fpm
systemctl status php85-php-fpm --no-pager
php -v                           # expect the new version (CLI)
/opt/remi/php85/root/usr/sbin/php-fpm -v   # expect the new version (FPM)

You should now be on the latest, patched PHP version.

6. Rollback

If you need to roll back:

dnf history undo last -y
# or, explicitly:
dnf downgrade -y /root/php-rollback/*.rpm
/opt/remi/php85/root/usr/sbin/php-fpm -t && systemctl reload php85-php-fpm
php -v                           # expect 8.5.11

Wrapping up

It’s not the most elegant process, but it’ll get the fix onto prod the same day without waiting for the repo. And because the rebuilt packages use release 0.1, Remi’s official 8.5.12-1 packages are newer and will replace them with a normal dnf upgrade once they land, so there’s nothing to clean up afterwards.