Make Softaculous Use DirectAdmin’s Selected PHP Version
Softaculous may detect the server’s default PHP version instead of the PHP version selected for the target domain in DirectAdmin. The result is confusing: Softaculous can reject an installation or upgrade because it thinks PHP is too old, even though phpinfo() for the domain shows a newer version.
This workaround makes Softaculous ask DirectAdmin’s domain configuration for the effective PHP version before install/upgrade checks run.
Overview
The solution has three parts:
- A root-owned helper script that returns the PHP version for one domain or subdomain.
- A sudo wrapper that allows DirectAdmin users to call the helper safely.
- A Softaculous hook that defines
php_versionfrom the helper result.
The helper returns only a numeric version such as:
8.1
8.2
8.3
1. Install The DirectAdmin PHP Version Helper
Save this as in your preferred location for such utility scripts e.g.:
/path/to/da/helpers/da_domain_php_version.sh
#!/bin/bash
set -u
OPTIONS_CONF=/usr/local/directadmin/custombuild/options.conf
DOMAINOWNERS=/etc/virtual/domainowners
DA_USERS_DIR=/usr/local/directadmin/data/users
usage() {
printf 'Usage: %s DOMAIN_OR_SUBDOMAIN\n' "$0" >&2
}
die() {
printf 'ERROR: %s\n' "$*" >&2
exit 1
}
php_version_for_slot() {
local slot=$1 ver
ver=$(awk -F= -v k="php${slot}_select" '$1 == k {print $2; exit}' "$OPTIONS_CONF")
if [ -z "$ver" ]; then
ver=$(awk -F= -v k="php${slot}_release" '$1 == k {print $2; exit}' "$OPTIONS_CONF")
fi
[ -n "$ver" ] || die "php${slot}_release not found in $OPTIONS_CONF"
printf '%s\n' "$ver"
}
url_decode() {
local raw=${1//+/ }
printf '%b' "${raw//%/\\x}"
}
[ "$#" -eq 1 ] || {
usage
exit 2
}
name=${1%.}
[ -n "$name" ] || die "empty domain"
[ -r "$OPTIONS_CONF" ] || die "cannot read $OPTIONS_CONF"
[ -r "$DOMAINOWNERS" ] || die "cannot read $DOMAINOWNERS"
domain=
username=
candidate=$name
while :; do
username=$(awk -F':[[:space:]]*' -v d="$candidate" '$1 == d {print $2; exit}' "$DOMAINOWNERS")
if [ -n "$username" ]; then
domain=$candidate
break
fi
case "$candidate" in
*.*) candidate=${candidate#*.} ;;
*) break ;;
esac
done
[ -n "$domain" ] || exit 1
sudo_user=${SUDO_USER:-}
if [ -n "$sudo_user" ] && [ "$username" != "$sudo_user" ]; then
exit 1
fi
domain_conf="$DA_USERS_DIR/$username/domains/$domain.conf"
[ -r "$domain_conf" ] || die "cannot read $domain_conf"
domain_slot=$(awk -F= '$1 == "php1_select" {print $2; exit}' "$domain_conf")
[ -n "$domain_slot" ] || domain_slot=1
if [ "$name" = "$domain" ]; then
php_version_for_slot "$domain_slot"
exit 0
fi
sub_prefix=${name%."$domain"}
[ -n "$sub_prefix" ] && [ "$sub_prefix" != "$name" ] || die "$name is not a subdomain of $domain"
override_file="$DA_USERS_DIR/$username/domains/$domain.subdomains.docroot.override"
if [ -r "$override_file" ]; then
raw_config=$(awk -F= -v s="$sub_prefix" '$1 == s {sub(/^[^=]*=/, ""); print; exit}' "$override_file")
if [ -n "$raw_config" ]; then
decoded_config=$(url_decode "$raw_config")
sub_slot=$(
printf '%s\n' "$decoded_config" |
awk '
match($0, /php[1-9]_select=[1-9]/) {
v = substr($0, RSTART, RLENGTH)
sub(/^.*=/, "", v)
print v
exit
}
match($0, /(^|[&?])php=[1-9]/) {
v = substr($0, RSTART, RLENGTH)
sub(/^.*=/, "", v)
print v
exit
}
'
)
[ -n "$sub_slot" ] || sub_slot=$domain_slot
php_version_for_slot "$sub_slot"
exit 0
fi
fi
standard_file="$DA_USERS_DIR/$username/domains/$domain.subdomains"
if [ -r "$standard_file" ] && awk -v s="$sub_prefix" '$0 == s {found=1} END {exit !found}' "$standard_file"; then
php_version_for_slot 1
exit 0
fi
die "subdomain not found for $name"Make it executable:
The script follows DirectAdmin’s PHP-selection rules:
- main domains use
php1_selectfrom the domain.conffile - override subdomains use
.subdomains.docroot.override - override subdomains without their own PHP setting inherit the parent domain slot
- standard
.subdomainsentries use the global default PHP slot - when called through
sudo, the requested domain must belong toSUDO_USER; mismatches exit with code1and no output
2. Add A Safe User-Facing Wrapper
DirectAdmin users cannot read all DirectAdmin domain config files directly, so provide a very narrow sudo rule and a wrapper command.
Adjust the helper path if you install it somewhere else.
cat > /etc/sudoers.d/domain-php-version <<'EOF'
Defaults!/path/to/da/helpers/da_domain_php_version.sh !requiretty
ALL ALL = (root) NOPASSWD: /path/to/da/helpers/da_domain_php_version.sh
EOF
cat > /usr/local/bin/domain-php-version <<'EOF'
#!/bin/bash
exec sudo /path/to/da/helpers/da_domain_php_version.sh "$@"
EOF
chmod 755 /usr/local/bin/domain-php-versionTest as root:
Test as a DirectAdmin user:
If example.com belongs to another DirectAdmin user, the helper exits silently with error code 1.
3. Add The Softaculous Hook
Softaculous hooks live here:
/usr/local/directadmin/plugins/softaculous/enduser/hooks/
Create PHP hook files from the templates if they do not already exist:
cd /usr/local/directadmin/plugins/softaculous/enduser/hooks/
cp -n pre_install.txt pre_install.php
cp -n pre_upgrade.txt pre_upgrade.phpIn both pre_install.php and pre_upgrade.php, add this block under the comment that says:
if (!empty($_REQUEST['softdomain'])) {
$domain_input = trim($_REQUEST['softdomain']);
$domain_pattern = '/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i';
if (preg_match($domain_pattern, $domain_input)) {
$version_raw = exec('/usr/local/bin/domain-php-version ' . $domain_input, $lines, $rc);
$version = trim($version_raw);
if ($rc === 0 && preg_match('/^\d+\.\d+(?:\.\d+)*$/', $version)) {
define('php_version', $version);
}
}
}The domain is validated before being passed to the wrapper. The regex allows only letters, digits, dots, and dashes, so no extra shell quoting is needed for this restricted value.
4. Verify
Run the wrapper as the DirectAdmin user who owns the domain:
Expected output:
8.2
Then retry a Softaculous install or upgrade for that domain. Softaculous should now evaluate the PHP requirement using the version selected in DirectAdmin rather than the server-wide default PHP version.
Notes
- Keep the sudo rule narrow. Do not allow arbitrary shell commands.
- The helper intentionally returns only the PHP version on success. Errors go to stderr.
- If DirectAdmin’s PHP version order changes in CustomBuild, the helper still resolves the selected slot through
options.conf. - If Softaculous changes its hook API in a future release, re-check that defining
php_versionin the pre-install/pre-upgrade hook still overrides its detection path.
