ApisCP v3.2.49 released

This release includes KSK-2024 for any servers running systemd-resolved under which dnssec trust anchors do not automatically update. Rather than move the patch up to .8, let’s do a full reset with PHP 8.3 for use with future development. upcp -sb will always use Mitogen where available for an appreciable reduction in memory and boost to speed.

Moving preferences to a concurrency-friendly Windows Registry-style database, multi PHP-FPM pools, and systemd-based jobs will land in the next release.

Note, this changelog was summarized by AI given copyediting from commit logs takes up the better part of a day on release.

SECURITY:
[Login] Fixed weak visibility on the unauthenticated login controller that allowed open queries of site info and email. Reported by Curtis (terabit.io).
[Kernel] Hotfix for CVE-2026-31431.
[PHP-FPM] Fixed weak ownership on Apache logfiles that would let an attacker inside a compromised webapp unlink a log and link it to a protected system file, potentially corrupting it.

NEW:
[artisan] opcenter:plan supports --update for plan parameter conversion.
[Compiler] lld and mold linker support via the new build.linker setting.
[cpcmd] add phpdbg support to cpcmd, AddDomain, EditDomain, et al. from -x flag.
[Debugging] Runtime breakpoint instrumentation through the DEBUG_MSG and DEBUG_MASK environment variables. DEBUG_MSG breaks on the next opcode after a matching message (weak string match, or regex when the value is a valid expression). DEBUG_MASK breaks on messages matching a level such as “success”, “warning” or “fatal”, or a PHP level prefixed with e_ (e.g. “e_deprecated”).
[Diagnostics] Unhandled exceptions and fatal errors now receive an event ID, logged to storage/logs/php-fpm.log. debug_event() weak-matches against event IDs in start.log and php-fpm.log.
[Letsencrypt] [letsencrypt] => preferred_root follows alternative certificate chains until a root containing the specified string is found. ISRG is sunsetting the X1/X2 roots, so these may be unavailable for some orders.
[Letsencrypt] Issuance is retried up to [letsencrypt] => bootstrap_attempts, since events like orderNotReady are transitory.
[mail] Outbound rate limiting: 1000 emails per hour and 4000 per 24 hours per SASL-authenticated sender. Configurable with rspamd_max_nh_email, where n is 1 or 24.
[mail] DMARC rejection/quarantine policy is now tunable.
[Opcenter] add_site() and edit_site() (and the AddDomain/EditDomain helpers) accept dot-notation, hydrated into a multidimensional array. As a result, “.” can no longer be used in service group or service names.
[PHP] PHP 8.5 support. PHP 8.4 is now the default.
[Platform] RHEL 10 support, plus preliminary Rocky 9 support. Includes yescrypt password hashing on RHEL 10.
[PostgreSQL] PostgreSQL 18 support.
[swap] zswap support. Enable with zswap_enabled, then run filesystem/swap. Suited to memory-constrained systems (roughly 1.75-2.5x better swap utilization at the cost of higher CPU).
[systemd] New site@ and group@ helpers toggle all services/timers bound to a site or reseller group at once.
[upcp] New -x flag runs the upgrade as bash -x (-vvv with -x increases bash visibility). New -b flag bypasses pending migrations.

FIXED:
[Ansible] actionable_classic callback incompatible with ansible-core ≥ 2.14.
[API] Inherited API methods are now reported in WSDL and intellisense generation (python, ruby, go modules and Web Apps). Wildcard-permission methods defined in a parent are now callable.
[Cgroup] Fixed orphaned transition groups during self-referential migration, a cgroupv2 io.stat read on root, and the default cpu.weight (0 → 100).
[ClamAV] Fixed a race during uninstall that could leave invalid ownership on the database directory.
[Composer] Updated the Packagist API URI. The p1 endpoint was retired September 1 and /p returns 403.
[cPanel] Pathmap dereferenced parked_domains as a list.
[crons] A raw “grep” file was accidentally written to the filesystem due to a bad IO redirect.
[Daphnie] metricscron dropped all past chunks regardless of telemetry.retention_ttl (#54). Also fixed lock-limit failures during cleanup, an older_than timestamp miscalculation, removeData() expecting an interval, and compatibility with newer TimescaleDB releases.
[DNS] SMIMEA records were not recognized. Record sets with multiple records now compare parameters correctly.
[DNS Manager] Viewing a domain hosted by another provider no longer shows PowerDNS records when powerdns is the server default.
[file] fs.protected_fifos (standard on EL9+) no longer breaks tar streams in file downloads.
[Frontend] Proxied client IP and site ID/username are now reported in PHP-FPM layouts.
[Ghost] mod_pagespeed double-encoded Ghost data; Apache now handles Brotli encoding. Also fixed node-gyp builds on EL7/8.
[Go] Role usage reset Go version updates.
[Hetzner] A _verify_conf error is now a warning, so it no longer unwinds a domain edit.
[kernel] Fixed non-idempotent flag removal from GRUB_CMDLINE_LINUX and boot entries being listed by date on EL8+.
[Letsencrypt] challenges() threw a TypeError; the full certificate chain is now used for deep inspection; renewal inspection includes the missing chain when the YE1/YR1 intermediate is absent from the OS CA store.
[mapCheck] Domain names matching site[0-9] were incorrectly matched as site ID masks.
[mysql, pgsql] Removing a database now removes its backup task. A symlinked docroot no longer fails database export. Renaming a database prefix no longer leaves secondary users under the old prefix.
[Net] Compatibility with the null-byte clampdown in PHP 8.3.20+ during ip2long() resolution.
[pgsql] change_owner() always reported false.
[PHP] imagick 3.8.0 resolves compile failures against PHP 8.4/ImageMagick 7.
[PHP Pools] php:pool-info signature matched when no pools are configured.
[PHP-FPM] logrotate no longer sends USR1 to itself when php-fpm-MAIN is deactivated.
[redis] running() fixed for insufficient privileges and for valkey process names on EL10+.
[upcp] Fixed branch ambiguity when switching between edge and release.
[views] Error formatting in apps using routes.php no longer leaks into the bounding container.
[web] add_subdomain() could not resolve ownership of user-based global subdomains until created. get_user() produced excessive error diagnostics.
[WordPress] Reconfiguring a user preserves the @domain datum instead of replacing it with the app hostname (which can’t log in directly for subdomains).

CHANGED:
[Admin\Settings] Setting mail.spam-filter to rspamd while mail_enabled is false implicitly sets rspamd_enabled. PHP EOL version nags are parameterized. Bootstrapper scope overrides can be unset by passing “null”.
[Argos] Python 3.11+ support; ntfy ruamel dependency bumped.
[Cache] Handles the Valkey repair variant.
[Error Reporter] Logging survives autoloader failure and handles recursive symbolic messages. $errcontext (removed in PHP 8.0) is replaced by a stack trace.
[fail2ban] Prefers systemd journald integration. Reloads promptly on jail config updates.
[Ghost] Updated minimum Node for Ghost 6.0.
[Invoice Ninja] Uses a release with bundled React assets.
[Letsencrypt] YE/YR roots are supported (YE1 fingerprint, YR1 intermediate). Chain resolution is more aggressive and stops at the first trusted system certificate.
[mail] Postfix switched from btree to lmdb. postqueue visibility is restricted to mail admins. Bayes scoring now ramps on a sigmoid curve, weighting upstream spam flags more heavily. The reputation module is disabled when Redis is unavailable due to low memory.
[mapCheck] Cleans up /etc/httpd/conf/virtual and recreates missing admin system users. Handles more edge cases when reconstructing minimally viable data, and corrupted accounts sharing a GID with an existing account can now be removed.
[MySQL/PostgreSQL] io_uring enabled on EL10+ (MySQL) and PostgreSQL 18+ on Linux 5.1+.
[PHP] apache,jail merged into php,enabled: the jail feature now depends entirely on PHP-FPM being present. /etc/php.ini is editable within the VFS (edits copy up). The panel runtime is now PHP 8.3.
[PHP-FPM] Services are now php-fpm@GROUP-NAME, an instantiated template. Start/restart/watchdog timers back off on resource-constrained systems.
[PowerDNS] Default server bumped to 5.0.
[ssl] get_certificate() with $name omitted returns the full certificate including chain. resolve_chain() takes a $full parameter (default false). install() accepts PEM and is available to admin.
[webapp] set_meta() accepts a docroot as $hostname.
[yum-post] “-d” dependency resolution takes a depth up to 16 (-r implies 16; plain -d installs immediate dependencies only). Check first with “depends -d PACKAGE”.

REMOVED:
[PHP-FPM] Watchdog feature removed on EL7 4.x/5.x/6.x kernels. Shared mounts inherited from tmpfs were mislabeled slave, so bind mounts could tear down the whole peer group and trigger a 6/ABRT start-kill loop every 30 seconds. Not seen on EL8/9/10.
[PHP-FPM] PID file generation removed on EL9/EL10 (RHEL 10 mounts /var/run as 0755 tmpfs; notify-based services don’t need it).