Import alt-mysql plugin

This commit is contained in:
Marek Miklewicz
2026-07-04 15:48:19 +02:00
commit d71fcf9ace
101 changed files with 18635 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
$pluginDir = dirname(__DIR__);
require_once $pluginDir . '/exec/lib/Settings.php';
require_once $pluginDir . '/exec/lib/DirectAdminUser.php';
function fail(string $message): void
{
fwrite(STDERR, "FAIL: {$message}\n");
exit(1);
}
function assert_true(bool $condition, string $message): void
{
if (!$condition) {
fail($message);
}
}
function assert_same($expected, $actual, string $message): void
{
if ($expected !== $actual) {
fail($message . ' expected=' . var_export($expected, true) . ' actual=' . var_export($actual, true));
}
}
function load_settings(string $contents): Settings
{
$path = tempnam(sys_get_temp_dir(), 'altmysql-settings-');
if ($path === false) {
fail('cannot create temp settings file');
}
file_put_contents($path, $contents);
$settings = Settings::load($path);
unlink($path);
return $settings;
}
function make_da_user(Settings $settings, array $userConf, array $packageConf): DirectAdminUser
{
$class = new ReflectionClass(DirectAdminUser::class);
/** @var DirectAdminUser $user */
$user = $class->newInstanceWithoutConstructor();
foreach ([
'username' => 'demo',
'prefix' => 'demo_',
'userConf' => $userConf,
'packageConf' => $packageConf,
'settings' => $settings,
] as $property => $value) {
$prop = $class->getProperty($property);
$prop->setAccessible(true);
$prop->setValue($user, $value);
}
return $user;
}
$alwaysSettings = load_settings("always_enabled=1\nMYSQL_PLUGIN_DEFAULT_DB_LIMIT=5\n");
assert_true($alwaysSettings->alwaysEnabled(), 'always_enabled=1 should be enabled');
$alwaysUser = make_da_user($alwaysSettings, [
'mysql_enabled' => 'no',
'mysql' => '0',
'plugins_deny' => 'alt-mysql',
], []);
assert_true($alwaysUser->hasPluginAccess(), 'always_enabled=1 should bypass per-user enable and plugin deny rules');
assert_same(0, $alwaysUser->maxDatabases(), 'always_enabled=1 should make database limit unlimited');
$defaultSettings = load_settings("");
assert_true($defaultSettings->alwaysEnabled(), 'always_enabled should default to enabled');
$defaultUser = make_da_user($defaultSettings, [], []);
assert_true($defaultUser->hasPluginAccess(), 'default always_enabled should grant access without custom package items');
assert_same(0, $defaultUser->maxDatabases(), 'default always_enabled should make database limit unlimited');
$legacySettings = load_settings("always_enabled=0\nMYSQL_PLUGIN_DEFAULT_DB_LIMIT=5\n");
assert_true(!$legacySettings->alwaysEnabled(), 'always_enabled=0 should disable automatic access');
$legacyUser = make_da_user($legacySettings, [
'mysql_enabled' => 'no',
'mysql' => '0',
], []);
assert_true(!$legacyUser->hasPluginAccess(), 'always_enabled=0 should preserve per-user access checks');
assert_same(0, $legacyUser->maxDatabases(), 'explicit mysql=0 remains zero in legacy mode');
echo "always_enabled_test: OK\n";
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
FILE="$PLUGIN_DIR/exec/lib/BackupQueueService.php"
fail() {
echo "FAIL: $*" >&2
exit 1
}
if grep -Fq '$safeBase,' "$FILE"; then
fail "BackupQueueService.php still references undefined safeBase"
fi
echo "backup_queue_service_test: OK"
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
fail() {
echo "FAIL: $*" >&2
exit 1
}
CPIF="$TMP_DIR/custom_package_items.conf"
SETTINGS="$TMP_DIR/plugin-settings.conf"
ALT_MYSQL_TEST_ASSUME_ROOT=1 \
ALT_MYSQL_TEST_ONLY_CUSTOM_PACKAGE_ITEMS=1 \
ALT_MYSQL_CUSTOM_PACKAGE_ITEMS_CONF="$CPIF" \
ALT_MYSQL_SETTINGS_FILE="$SETTINGS" \
bash "$PLUGIN_DIR/scripts/install.sh" >/dev/null
[ -f "$CPIF" ] || fail "custom package items file was not created"
if grep -Eq '^(mysql_enabled|mysql|umysql|mysql_max_databases|mysql_unlimited|umysql_max_databases)=' "$CPIF"; then
cat "$CPIF" >&2
fail "always_enabled=1 should not add per-user access or database limit custom fields"
fi
cat > "$SETTINGS" <<'EOF'
always_enabled=0
EOF
ALT_MYSQL_TEST_ASSUME_ROOT=1 \
ALT_MYSQL_TEST_ONLY_CUSTOM_PACKAGE_ITEMS=1 \
ALT_MYSQL_CUSTOM_PACKAGE_ITEMS_CONF="$CPIF" \
ALT_MYSQL_SETTINGS_FILE="$SETTINGS" \
bash "$PLUGIN_DIR/scripts/install.sh" >/dev/null
grep -Fq 'mysql_enabled=' "$CPIF" || fail "always_enabled=0 should add mysql_enabled field"
grep -Fq 'mysql=type=text' "$CPIF" || fail "always_enabled=0 should add mysql database limit input"
grep -Fq 'umysql=type=checkbox' "$CPIF" || fail "always_enabled=0 should add unlimited checkbox"
echo "custom_package_items_test: OK"
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
INSTALLER="$PLUGIN_DIR/scripts/setup/custombuild_hooks_install.sh"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
fail() {
echo "FAIL: $*" >&2
exit 1
}
CUSTOMBUILD_ROOT="$TMP_DIR/custombuild" bash "$INSTALLER" install >/dev/null
ROUND_HOOK="$TMP_DIR/custombuild/custom/hooks/roundcube/post/90-alt-mysql-repair.sh"
PMA_HOOK="$TMP_DIR/custombuild/custom/hooks/phpmyadmin/post/90-alt-mysql-repair.sh"
[ -x "$ROUND_HOOK" ] || fail "Roundcube CustomBuild post hook not installed"
[ -x "$PMA_HOOK" ] || fail "phpMyAdmin CustomBuild post hook not installed"
grep -Fq "roundcube_repair_config.sh" "$ROUND_HOOK" || fail "Roundcube hook does not call repair"
grep -Fq "roundcube_health_check.sh" "$ROUND_HOOK" || fail "Roundcube hook does not call health check"
grep -Fq "phpmyadmin_repair.sh" "$PMA_HOOK" || fail "phpMyAdmin hook does not call repair"
grep -Fq "phpmyadmin_health_check.sh" "$PMA_HOOK" || fail "phpMyAdmin hook does not call health check"
CUSTOMBUILD_ROOT="$TMP_DIR/custombuild" bash "$INSTALLER" uninstall >/dev/null
[ ! -e "$ROUND_HOOK" ] || fail "Roundcube CustomBuild post hook not removed"
[ ! -e "$PMA_HOOK" ] || fail "phpMyAdmin CustomBuild post hook not removed"
echo "custombuild_hooks_install_test: OK"
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
INSTALLER="$PLUGIN_DIR/scripts/setup/da_integration_install.sh"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
fail() {
echo "FAIL: $*" >&2
exit 1
}
mkdir -p "$TMP_DIR/custom"
cat > "$TMP_DIR/custom/user_backup_compress_pre.sh" <<'EOF'
#!/bin/bash
echo existing
EOF
chmod 755 "$TMP_DIR/custom/user_backup_compress_pre.sh"
DA_CUSTOM_HOOK_DIR="$TMP_DIR/custom" bash "$INSTALLER" install >/dev/null
[ -f "$TMP_DIR/custom/user_backup_compress_pre.sh" ] || fail "dispatcher not created"
grep -Fq "managed by DirectAdmin MySQL plugin hook dispatcher" "$TMP_DIR/custom/user_backup_compress_pre.sh" \
|| fail "dispatcher marker missing"
[ -f "$TMP_DIR/custom/user_backup_compress_pre.sh.d/00-existing.sh" ] || fail "existing hook not preserved"
[ -L "$TMP_DIR/custom/user_backup_compress_pre.sh.d/10-alt-mysql.sh" ] || fail "alt backup hook link missing"
[ -L "$TMP_DIR/custom/user_restore_post_pre_cleanup.sh.d/10-alt-mysql.sh" ] || fail "alt restore hook link missing"
[ -L "$TMP_DIR/custom/database_create_pre.sh.d/20-alt-mysql-block-native-db.sh" ] || fail "native DB blocker link missing"
DA_CUSTOM_HOOK_DIR="$TMP_DIR/custom" bash "$INSTALLER" uninstall >/dev/null
[ ! -e "$TMP_DIR/custom/user_backup_compress_pre.sh.d/10-alt-mysql.sh" ] || fail "alt backup hook link not removed"
[ -f "$TMP_DIR/custom/user_backup_compress_pre.sh.d/00-existing.sh" ] || fail "preserved existing hook should remain"
echo "da_integration_install_test: OK"
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT="$PLUGIN_DIR/scripts/install_db.sh"
fail() {
echo "FAIL: $*" >&2
exit 1
}
[ -x "$SCRIPT" ] || fail "scripts/install_db.sh is missing or not executable"
[ ! -e "$PLUGIN_DIR/scripts/install-alt-mariadb.sh" ] || fail "old install-alt-mariadb.sh still exists"
grep -Fq 'apply_cli_args "$@"' "$SCRIPT" \
|| fail "install_db.sh does not call CLI argument parser"
grep -Fq 'MARIADB_VERSION="$1"' "$SCRIPT" \
|| fail "install_db.sh does not accept MariaDB version as first argument"
grep -Fq 'PORT="$2"' "$SCRIPT" \
|| fail "install_db.sh does not accept port as second argument"
grep -Fq 'MARIADB_DIR="/usr/local/mariadb-$MARIADB_VERSION"' "$SCRIPT" \
|| fail "install_db.sh does not derive MariaDB directory from chosen version"
grep -Fq 'validate_port' "$SCRIPT" \
|| fail "install_db.sh does not validate the chosen port"
grep -Fq 'rerun_plugin_install_if_available' "$SCRIPT" \
|| fail "install_db.sh does not refresh plugin install.sh after DB install"
grep -Fq 'SKIP_PLUGIN_INSTALL_REFRESH' "$SCRIPT" \
|| fail "install_db.sh does not allow explicitly skipping plugin refresh"
grep -Fq '[[ $# -eq 0 ]]' "$SCRIPT" \
|| fail "install_db.sh does not explicitly show usage when called without arguments"
if grep -Fq 'install-alt-mariadb.sh' "$SCRIPT"; then
fail "install_db.sh still references old install-alt-mariadb.sh name"
fi
set +e
NO_ARGS_OUTPUT="$("$SCRIPT" 2>&1)"
NO_ARGS_STATUS=$?
set -e
[ "$NO_ARGS_STATUS" -eq 2 ] || fail "install_db.sh without arguments should exit with status 2, got $NO_ARGS_STATUS"
printf '%s\n' "$NO_ARGS_OUTPUT" | grep -Fq 'Użycie:' \
|| fail "install_db.sh without arguments does not print usage"
printf '%s\n' "$NO_ARGS_OUTPUT" | grep -Fq '<MARIADB_VERSION> <PORT>' \
|| fail "install_db.sh usage does not show required version and port"
if printf '%s\n' "$NO_ARGS_OUTPUT" | grep -Fq 'Ten skrypt musi zostać uruchomiony jako root'; then
fail "install_db.sh without arguments reaches root check instead of usage"
fi
echo "install_db_cli_test: OK"
+75
View File
@@ -0,0 +1,75 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
COMMON_FILE="$PLUGIN_DIR/scripts/setup/mysql_common.sh"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
fail() {
echo "FAIL: $*" >&2
exit 1
}
mkdir -p "$TMP_DIR/mariadb/bin"
touch "$TMP_DIR/mariadb/bin/mariadb" "$TMP_DIR/mariadb/bin/mariadb-dump" "$TMP_DIR/mariadb/bin/mariadb-admin" "$TMP_DIR/mariadb/bin/mariadb-upgrade"
chmod 755 "$TMP_DIR/mariadb/bin/"*
cat > "$TMP_DIR/alt-mariadb.env" <<EOF
SERVICE_NAME='alt-mariadb-test'
MARIADB_DIR='$TMP_DIR/mariadb'
DATADIR='$TMP_DIR/data'
PORT='4407'
SOCKET='$TMP_DIR/data/mariadb.sock'
ALT_MYSQL_CONF='$TMP_DIR/alt-mysql.conf'
ALT_MY_CNF='$TMP_DIR/alt-my.cnf'
INSTANCE_CNF='$TMP_DIR/alt-mariadb.cnf'
EOF
cat > "$TMP_DIR/alt-mysql.conf" <<EOF
host=
passwd=secret
port=4407
socket=$TMP_DIR/data/mariadb.sock
user=da_admin
EOF
cat > "$TMP_DIR/alt-my.cnf" <<EOF
[client]
user="da_admin"
password="secret"
socket="$TMP_DIR/data/mariadb.sock"
EOF
cat > "$TMP_DIR/alt-mariadb.cnf" <<EOF
[mariadb]
basedir=$TMP_DIR/mariadb
datadir=$TMP_DIR/data
port=4407
socket=$TMP_DIR/data/mariadb.sock
EOF
MYSQL_PLUGIN_METADATA_FILE="$TMP_DIR/alt-mariadb.env"
MYSQL_PLUGIN_CREDENTIALS_FILE="$TMP_DIR/alt-mysql.conf"
MYSQL_PLUGIN_ALT_MY_CNF="$TMP_DIR/alt-my.cnf"
MYSQL_PLUGIN_ALT_INSTANCE_CNF="$TMP_DIR/alt-mariadb.cnf"
# shellcheck source=../scripts/setup/mysql_common.sh
source "$COMMON_FILE"
mysql_load_alt_runtime
[ "$MYSQL_ALT_SERVICE_NAME" = "alt-mariadb-test" ] || fail "service name not loaded"
[ "$MYSQL_ALT_BASEDIR" = "$TMP_DIR/mariadb" ] || fail "basedir not loaded"
[ "$MYSQL_ALT_DATADIR" = "$TMP_DIR/data" ] || fail "datadir not loaded"
[ "$MYSQL_ALT_PORT" = "4407" ] || fail "port not loaded"
[ "$MYSQL_ALT_SOCKET" = "$TMP_DIR/data/mariadb.sock" ] || fail "socket not loaded"
[ "$(mysql_alt_binary mysql)" = "$TMP_DIR/mariadb/bin/mariadb" ] || fail "mysql alias binary not detected"
[ "$(mysql_alt_binary mysqldump)" = "$TMP_DIR/mariadb/bin/mariadb-dump" ] || fail "mysqldump alias binary not detected"
mysql_load_alt_credentials
[ "$MYSQL_USER" = "da_admin" ] || fail "credential user not loaded"
[ "$MYSQL_PORT" = "4407" ] || fail "credential port not loaded"
[ "$MYSQL_SOCKET" = "$TMP_DIR/data/mariadb.sock" ] || fail "credential socket not loaded"
echo "mysql_common_runtime_test: OK"
+117
View File
@@ -0,0 +1,117 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SYNC_SCRIPT="$SCRIPT_DIR/scripts/setup/mysql_host_sync.sh"
fail() {
echo "FAIL: $*" >&2
exit 1
}
assert_contains() {
local file="$1"
local needle="$2"
grep -Fq "$needle" "$file" || fail "Expected $file to contain: $needle"
}
assert_not_contains() {
local file="$1"
local needle="$2"
if grep -Fq "$needle" "$file"; then
fail "Expected $file not to contain: $needle"
fi
}
assert_line_count() {
local expected="$1"
local file="$2"
local needle="$3"
local actual
actual="$(grep -Fxc "$needle" "$file" || true)"
[ "$actual" = "$expected" ] || fail "Expected $expected occurrences of '$needle' in $file, got $actual"
}
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
mkdir -p "$tmpdir/bin" "$tmpdir/csf"
cat > "$tmpdir/bin/mysql" <<'MYSQL'
#!/bin/bash
set -euo pipefail
printf '%s\n' \
"198.51.100.10" \
"203.0.113.20" \
"localhost" \
"bad-host-name"
MYSQL
chmod 755 "$tmpdir/bin/mysql"
cat > "$tmpdir/alt-mysql.conf" <<EOF
host=localhost
port=33033
user=root
password=secret
database=mysql
socket=$tmpdir/mysql.sock
EOF
cat > "$tmpdir/alt-mariadb.cnf" <<'EOF'
[client]
port=33033
[mariadb]
port=33033
bind-address=127.0.0.1
EOF
cat > "$tmpdir/csf/csf.allow" <<'EOF'
# csf.allow
EOF
cat > "$tmpdir/csf/csf.conf" <<'EOF'
TCP_IN = "22,33030:33035,80,33033,443"
TCP6_IN = "22,33033:33033"
TCP_OUT = "22,80,33033,443"
EOF
MYSQL_HOST_SYNC_ASSUME_ROOT=1 \
MYSQL_HOST_SYNC_PLUGIN_SETTINGS="$tmpdir/missing-plugin-settings.conf" \
MYSQL_HOST_SYNC_SKIP_CSF_RELOAD=1 \
MYSQL_HOST_SYNC_SKIP_SERVICE_RESTART=1 \
MYSQL_PLUGIN_CLIENT_BIN_DIR="$tmpdir/bin" \
MYSQL_PLUGIN_CREDENTIALS_FILE="$tmpdir/alt-mysql.conf" \
MYSQL_HOST_SYNC_INSTANCE_CNF="$tmpdir/alt-mariadb.cnf" \
MYSQL_HOST_SYNC_CSF_DIR="$tmpdir/csf" \
MYSQL_HOST_SYNC_CSF_ALLOW="$tmpdir/csf/csf.allow" \
MYSQL_HOST_SYNC_CSF_CONF="$tmpdir/csf/csf.conf" \
MYSQL_HOST_SYNC_MANAGED_ALLOW="$tmpdir/csf/alt-mysql-plugin.allow" \
bash "$SYNC_SCRIPT" >/tmp/mysql_host_sync_test.out
assert_contains "$tmpdir/alt-mariadb.cnf" "bind-address=0.0.0.0"
assert_contains "$tmpdir/csf/csf.allow" "Include $tmpdir/csf/alt-mysql-plugin.allow"
assert_contains "$tmpdir/csf/alt-mysql-plugin.allow" "tcp|in|d=33033|s=198.51.100.10"
assert_contains "$tmpdir/csf/alt-mysql-plugin.allow" "tcp|in|d=33033|s=203.0.113.20"
assert_not_contains "$tmpdir/csf/alt-mysql-plugin.allow" "localhost"
assert_not_contains "$tmpdir/csf/alt-mysql-plugin.allow" "bad-host-name"
assert_contains "$tmpdir/csf/csf.conf" 'TCP_IN = "22,33030:33032,33034:33035,80,443"'
assert_contains "$tmpdir/csf/csf.conf" 'TCP6_IN = "22"'
assert_contains "$tmpdir/csf/csf.conf" 'TCP_OUT = "22,80,33033,443"'
MYSQL_HOST_SYNC_ASSUME_ROOT=1 \
MYSQL_HOST_SYNC_PLUGIN_SETTINGS="$tmpdir/missing-plugin-settings.conf" \
MYSQL_HOST_SYNC_SKIP_CSF_RELOAD=1 \
MYSQL_HOST_SYNC_SKIP_SERVICE_RESTART=1 \
MYSQL_PLUGIN_CLIENT_BIN_DIR="$tmpdir/bin" \
MYSQL_PLUGIN_CREDENTIALS_FILE="$tmpdir/alt-mysql.conf" \
MYSQL_HOST_SYNC_INSTANCE_CNF="$tmpdir/alt-mariadb.cnf" \
MYSQL_HOST_SYNC_CSF_DIR="$tmpdir/csf" \
MYSQL_HOST_SYNC_CSF_ALLOW="$tmpdir/csf/csf.allow" \
MYSQL_HOST_SYNC_CSF_CONF="$tmpdir/csf/csf.conf" \
MYSQL_HOST_SYNC_MANAGED_ALLOW="$tmpdir/csf/alt-mysql-plugin.allow" \
bash "$SYNC_SCRIPT" >/tmp/mysql_host_sync_test_second.out
assert_line_count 1 "$tmpdir/csf/csf.allow" "Include $tmpdir/csf/alt-mysql-plugin.allow"
echo "mysql_host_sync_test: OK"
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT="$PLUGIN_DIR/scripts/setup/mysql_upgrade.sh"
fail() {
echo "FAIL: $*" >&2
exit 1
}
grep -Fq "rollback_upgrade()" "$SCRIPT" \
|| fail "mysql_upgrade.sh does not define rollback_upgrade"
grep -Fq "UPGRADE_COMMITTED=1" "$SCRIPT" \
|| fail "mysql_upgrade.sh does not mark successful upgrades as committed"
grep -Fq 'trap '\''code=$?; rollback_upgrade "$code"'\'' EXIT' "$SCRIPT" \
|| fail "mysql_upgrade.sh does not run rollback on failed exit"
grep -Fq "RESTORED_PREVIOUS_DIR" "$SCRIPT" \
|| fail "mysql_upgrade.sh does not restore previous binary directory"
grep -Fq "RESTORED_INSTANCE_CNF" "$SCRIPT" \
|| fail "mysql_upgrade.sh does not restore previous instance config"
echo "mysql_upgrade_rollback_test: OK"
+104
View File
@@ -0,0 +1,104 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
ARCHIVE="${1:-$(cd "$PLUGIN_DIR/.." && pwd)/1.2.10/alt-mysql.tar.gz}"
fail() {
echo "FAIL: $*" >&2
exit 1
}
[ -s "$ARCHIVE" ] || fail "archive does not exist or is empty: $ARCHIVE"
LIST="$(tar -tzf "$ARCHIVE")"
printf '%s\n' "$LIST" | grep -Eq '^(\./)?plugin\.conf$' \
|| fail "archive does not contain plugin.conf at tar root"
if printf '%s\n' "$LIST" | grep -Eq '^(\./)?alt-mysql/'; then
fail "archive contains parent alt-mysql directory"
fi
if printf '%s\n' "$LIST" | grep -Eiq 'postgres|postgresql|pgsql|phppgadmin'; then
fail "archive contains PostgreSQL/phpPgAdmin leftovers"
fi
if printf '%s\n' "$LIST" | grep -Eq '^(\./)?tests/'; then
fail "archive contains local test suite"
fi
if printf '%s\n' "$LIST" | grep -Eq '(^|/)\._[^/]+$|(^|/)\.DS_Store$|__MACOSX'; then
fail "archive contains macOS AppleDouble or Finder metadata"
fi
printf '%s\n' "$LIST" | grep -Eq '^(\./)?scripts/setup/phpmyadmin_private_install\.sh$' \
|| fail "archive does not contain private phpMyAdmin installer"
printf '%s\n' "$LIST" | grep -Eq '^(\./)?scripts/install_db\.sh$' \
|| fail "archive does not contain install_db.sh at scripts/"
if printf '%s\n' "$LIST" | grep -Eq '(^|/)install-alt-mariadb\.sh$'; then
fail "archive contains old install-alt-mariadb.sh"
fi
CONF="$(tar -xOzf "$ARCHIVE" ./plugin.conf 2>/dev/null || tar -xOzf "$ARCHIVE" plugin.conf)"
printf '%s\n' "$CONF" | grep -Fxq "name=alt-mysql" || fail "archive plugin name is not alt-mysql"
printf '%s\n' "$CONF" | grep -Fxq "id=alt-mysql" || fail "archive plugin id is not alt-mysql"
printf '%s\n' "$CONF" | grep -Fxq "version=1.2.10" || fail "archive plugin version is not 1.2.10"
SETTINGS="$(tar -xOzf "$ARCHIVE" ./plugin-settings.conf 2>/dev/null || tar -xOzf "$ARCHIVE" plugin-settings.conf)"
printf '%s\n' "$SETTINGS" | grep -Fxq "PHPMYADMIN_INSTALL_DIR=/var/www/html/alt-mysql-phpmyadmin" \
|| fail "archive settings do not point to private phpMyAdmin install dir"
printf '%s\n' "$SETTINGS" | grep -Fxq "PHPMYADMIN_URL_PATH=/alt-mysql-phpmyadmin" \
|| fail "archive settings do not point to private phpMyAdmin URL"
printf '%s\n' "$SETTINGS" | grep -Fxq "always_enabled=1" \
|| fail "archive settings do not enable always_enabled by default"
INSTALL_SCRIPT="$(tar -xOzf "$ARCHIVE" ./scripts/install.sh 2>/dev/null || tar -xOzf "$ARCHIVE" scripts/install.sh)"
printf '%s\n' "$INSTALL_SCRIPT" | grep -Fq 'always_enabled()' \
|| fail "archive install script does not read always_enabled"
printf '%s\n' "$INSTALL_SCRIPT" | grep -Fq 'if always_enabled; then' \
|| fail "archive install script does not hide custom package fields when always_enabled is on"
DB_INSTALLER="$(tar -xOzf "$ARCHIVE" ./scripts/install_db.sh 2>/dev/null || tar -xOzf "$ARCHIVE" scripts/install_db.sh)"
printf '%s\n' "$DB_INSTALLER" | grep -Fq 'apply_cli_args "$@"' \
|| fail "archive install_db.sh does not apply positional arguments"
printf '%s\n' "$DB_INSTALLER" | grep -Fq 'MARIADB_VERSION="$1"' \
|| fail "archive install_db.sh does not accept MariaDB version as first argument"
printf '%s\n' "$DB_INSTALLER" | grep -Fq 'PORT="$2"' \
|| fail "archive install_db.sh does not accept TCP port as second argument"
printf '%s\n' "$DB_INSTALLER" | grep -Fq 'rerun_plugin_install_if_available' \
|| fail "archive install_db.sh does not refresh plugin install.sh after DB setup"
printf '%s\n' "$DB_INSTALLER" | grep -Fq 'SKIP_PLUGIN_INSTALL_REFRESH' \
|| fail "archive install_db.sh does not expose a test/maintenance escape hatch for plugin refresh"
printf '%s\n' "$DB_INSTALLER" | grep -Fq '[[ $# -eq 0 ]]' \
|| fail "archive install_db.sh does not show usage when called without arguments"
printf '%s\n' "$DB_INSTALLER" | grep -Fq '<MARIADB_VERSION> <PORT>' \
|| fail "archive install_db.sh usage does not require version and port"
INSTALLER="$(tar -xOzf "$ARCHIVE" ./scripts/setup/phpmyadmin_install.sh 2>/dev/null || tar -xOzf "$ARCHIVE" scripts/setup/phpmyadmin_install.sh)"
printf '%s\n' "$INSTALLER" | grep -Fq "\$cfg['Servers'][\$i]['SignonURL'] = da_mysql_phpmyadmin_signon_url(\$runtimeConfig);" \
|| fail "archive phpMyAdmin installer does not configure SignonURL"
printf '%s\n' "$INSTALLER" | grep -Fq 'SIGNON_URL_PAGE="${PHPMYADMIN_PRIVATE_DIR}/da_login.php"' \
|| fail "archive phpMyAdmin installer does not create SignonURL page"
printf '%s\n' "$INSTALLER" | grep -Fq 'DA_MYSQL_PHPMYADMIN_AUTH' \
|| fail "archive phpMyAdmin installer does not create ticket-based da_login.php"
printf '%s\n' "$INSTALLER" | grep -Fq 'da_mysql_signon_read_auth' \
|| fail "archive phpMyAdmin installer does not isolate phpMyAdmin and SSO sessions"
OPEN_HANDLER="$(tar -xOzf "$ARCHIVE" ./exec/handlers/open_phpmyadmin.php 2>/dev/null || tar -xOzf "$ARCHIVE" exec/handlers/open_phpmyadmin.php)"
printf '%s\n' "$OPEN_HANDLER" | grep -Fq 'phpmyadmin_health_check.sh' \
|| fail "archive open phpMyAdmin handler does not run health check"
printf '%s\n' "$OPEN_HANDLER" | grep -Fq 'phpmyadmin_repair.sh' \
|| fail "archive open phpMyAdmin handler does not run repair before redirect"
printf '%s\n' "$OPEN_HANDLER" | grep -Fq 'HTTP/1.1 302 Found' \
|| fail "archive open phpMyAdmin raw handler does not emit an HTTP redirect"
printf '%s\n' "$OPEN_HANDLER" | grep -Fq 'Location: ' \
|| fail "archive open phpMyAdmin raw handler does not emit a Location header"
if printf '%s\n' "$OPEN_HANDLER" | grep -Fq 'meta http-equiv="refresh"'; then
fail "archive open phpMyAdmin raw handler still uses meta refresh"
fi
if printf '%s\n' "$OPEN_HANDLER" | grep -Fq 'Set-Cookie: '; then
fail "archive open phpMyAdmin handler still tries to pass SSO through DirectAdmin PHP session cookie"
fi
SSO_CLASS="$(tar -xOzf "$ARCHIVE" ./exec/lib/PhpMyAdminSso.php 2>/dev/null || tar -xOzf "$ARCHIVE" exec/lib/PhpMyAdminSso.php)"
printf '%s\n' "$SSO_CLASS" | grep -Fq 'writeLoginTicket' \
|| fail "archive phpMyAdmin SSO class does not write login tickets"
printf '%s\n' "$SSO_CLASS" | grep -Fq '/da_login.php?' \
|| fail "archive phpMyAdmin SSO class does not target da_login.php ticket flow"
echo "package_archive_test: OK"
+149
View File
@@ -0,0 +1,149 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT="$PLUGIN_DIR/scripts/setup/phpmyadmin_install.sh"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
fail() {
echo "FAIL: $*" >&2
exit 1
}
grep -Fq 'PHPMYADMIN_VERSION="${PHPMYADMIN_VERSION:-5.2.3}"' "$SCRIPT" \
|| fail "phpMyAdmin default version is not 5.2.3"
grep -Fq 'PHPMYADMIN_TARBALL_URL="${PHPMYADMIN_TARBALL_URL:-https://files.phpmyadmin.net/phpMyAdmin/5.2.3/phpMyAdmin-5.2.3-all-languages.tar.gz}"' "$SCRIPT" \
|| fail "phpMyAdmin tarball URL is not the explicit 5.2.3 URL"
mkdir -p "$TMP_DIR/phpMyAdmin"
cat > "$TMP_DIR/phpMyAdmin/config.inc.php" <<'PHP'
<?php
$cfg = [];
PHP
mkdir -p "$TMP_DIR/phpMyAdmin-source"
cat > "$TMP_DIR/phpMyAdmin-source/index.php" <<'PHP'
<?php
echo 'private phpMyAdmin';
PHP
PHPMYADMIN_EXISTING_DIR="$TMP_DIR/phpMyAdmin" \
PHPMYADMIN_PRIVATE_DIR="$TMP_DIR/alt-mysql-phpmyadmin" \
PHPMYADMIN_SOURCE_DIR="$TMP_DIR/phpMyAdmin-source" \
PHPMYADMIN_INSTALL_ASSUME_ROOT=1 \
bash "$SCRIPT" >/dev/null
PRIVATE_DIR="$TMP_DIR/alt-mysql-phpmyadmin"
CONFIG_INC="$PRIVATE_DIR/config.inc.php"
SIGNON_SCRIPT="$PRIVATE_DIR/da_signon.php"
SIGNON_URL_PAGE="$PRIVATE_DIR/da_login.php"
TICKET_DIR="$PRIVATE_DIR/runtime/tickets"
SESSION_DIR="$TMP_DIR/sessions"
[ -r "$PRIVATE_DIR/index.php" ] || fail "private phpMyAdmin copy was not installed"
[ -r "$CONFIG_INC" ] || fail "private phpMyAdmin config was not created"
[ -r "$SIGNON_SCRIPT" ] || fail "signon script was not created"
[ -r "$SIGNON_URL_PAGE" ] || fail "phpMyAdmin SignonURL page was not created"
[ -d "$TICKET_DIR" ] || fail "phpMyAdmin ticket directory was not created"
if grep -Fq "config.mysql.inc.php" "$TMP_DIR/phpMyAdmin/config.inc.php"; then
fail "installer modified existing CustomBuild phpMyAdmin config"
fi
SERVER_JSON="$(php -r '
$_SERVER["HTTPS"] = "on";
$_SERVER["HTTP_HOST"] = "panel.example.test";
$cfg=[];
include $argv[1];
echo json_encode($cfg["Servers"][1] ?? []);
' "$CONFIG_INC")"
php -r '
$server = json_decode($argv[1], true);
if (($server["host"] ?? "") !== "127.0.0.1") {
fwrite(STDERR, "host mismatch\n");
exit(2);
}
if ((string)($server["port"] ?? "") !== "33033") {
fwrite(STDERR, "port mismatch\n");
exit(3);
}
if (($server["socket"] ?? "not-empty") !== "") {
fwrite(STDERR, "socket mismatch\n");
exit(4);
}
if (($server["SignonURL"] ?? "") !== "https://panel.example.test/alt-mysql-phpmyadmin/da_login.php") {
fwrite(STDERR, "SignonURL mismatch\n");
exit(5);
}
if (($server["LogoutURL"] ?? "") !== "https://panel.example.test/alt-mysql-phpmyadmin/da_login.php") {
fwrite(STDERR, "LogoutURL mismatch\n");
exit(6);
}
' "$SERVER_JSON" || fail "phpMyAdmin config does not target alt-mariadb TCP endpoint"
grep -Fq 'return [$username, $password];' "$SIGNON_SCRIPT" \
|| fail "signon script should return username/password only"
php -d display_errors=1 -r 'set_error_handler(static function($errno, $errstr) { fwrite(STDERR, $errstr . "\n"); exit(9); }); $cfg=[]; include $argv[1]; include $argv[2];' "$CONFIG_INC" "$SIGNON_SCRIPT" \
|| fail "phpMyAdmin config and signon script cannot be loaded in one PHP process"
mkdir -p "$SESSION_DIR"
cat > "$TICKET_DIR/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef.json" <<'JSON'
{"version":1,"expires_at":4102444800,"auth":{"user":"da_tmp_phpmyadmin_test","password":"secret","db":"user_db"},"route":"/database/structure","db":"user_db"}
JSON
php -d display_errors=1 -d session.save_path="$SESSION_DIR" -r '
$_GET["ticket"] = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
$_COOKIE["DA_MYSQL_PHPMYADMIN"] = "tickettest";
$_SERVER["HTTPS"] = "on";
$_SERVER["HTTP_HOST"] = "panel.example.test";
include $argv[1];
' "$SIGNON_URL_PAGE" >/tmp/altmysql-da-login-test.out 2>/tmp/altmysql-da-login-test.err \
|| fail "da_login.php did not accept a valid ticket"
[ ! -e "$TICKET_DIR/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef.json" ] \
|| fail "da_login.php did not consume the SSO ticket"
php -d session.save_path="$SESSION_DIR" -r '
session_name("DA_MYSQL_PHPMYADMIN");
session_id("tickettest");
session_start();
$auth = $_SESSION["DA_MYSQL_PHPMYADMIN_AUTH"] ?? [];
if (($auth["user"] ?? "") !== "da_tmp_phpmyadmin_test") {
fwrite(STDERR, "session user mismatch\n");
exit(2);
}
if (($auth["password"] ?? "") !== "secret") {
fwrite(STDERR, "session password mismatch\n");
exit(3);
}
if (($auth["db"] ?? "") !== "user_db") {
fwrite(STDERR, "session db mismatch\n");
exit(4);
}
' || fail "da_login.php did not create the phpMyAdmin signon session"
php -d session.save_path="$SESSION_DIR" -r '
session_name("DA_MYSQL_PHPMYADMIN");
session_id("tickettest");
session_start();
$_SESSION["DA_MYSQL_PHPMYADMIN_AUTH"] = [
"user" => "da_tmp_phpmyadmin_test",
"password" => "secret",
"db" => "user_db",
];
session_write_close();
session_name("phpmyadmin");
session_id("pmaactive");
session_start();
$_SESSION["unrelated"] = "active phpMyAdmin session";
$_COOKIE["DA_MYSQL_PHPMYADMIN"] = "tickettest";
include $argv[1];
[$username, $password] = get_login_credentials("ignored");
if ($username !== "da_tmp_phpmyadmin_test") {
fwrite(STDERR, "signon user mismatch with active phpMyAdmin session\n");
exit(2);
}
if ($password !== "secret") {
fwrite(STDERR, "signon password mismatch with active phpMyAdmin session\n");
exit(3);
}
' "$SIGNON_SCRIPT" || fail "signon script cannot read SSO session while phpMyAdmin session is active"
PHPMYADMIN_PRIVATE_DIR="$PRIVATE_DIR" bash "$PLUGIN_DIR/scripts/setup/phpmyadmin_health_check.sh" >/dev/null \
|| fail "phpMyAdmin health check should pass for SignonURL-capable config"
echo "phpmyadmin_install_test: OK"
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
fail() {
echo "FAIL: $*" >&2
exit 1
}
[ "$(basename "$PLUGIN_DIR")" = "alt-mysql" ] || fail "plugin directory name is not alt-mysql"
grep -Fxq "name=alt-mysql" "$PLUGIN_DIR/plugin.conf" || fail "plugin.conf name is not alt-mysql"
grep -Fxq "id=alt-mysql" "$PLUGIN_DIR/plugin.conf" || fail "plugin.conf id is not alt-mysql"
grep -Fxq "version=1.2.10" "$PLUGIN_DIR/plugin.conf" || fail "plugin.conf version is not 1.2.10"
grep -Fq "return '/CMD_PLUGINS/alt-mysql';" "$PLUGIN_DIR/exec/lib/AppContext.php" \
|| fail "AppContext base URL is not alt-mysql"
grep -Fq "\$pluginId = 'alt-mysql';" "$PLUGIN_DIR/exec/lib/DirectAdminUser.php" \
|| fail "DirectAdmin plugin allow/deny id is not alt-mysql"
[ -x "$PLUGIN_DIR/scripts/install_db.sh" ] || fail "install_db.sh is not executable in scripts/"
[ ! -e "$PLUGIN_DIR/scripts/install-alt-mariadb.sh" ] || fail "install-alt-mariadb.sh still exists in plugin scripts/"
[ ! -e "$PLUGIN_DIR/../mysql-installer/install_db.sh" ] || fail "install_db.sh exists outside plugin scripts/"
[ ! -e "$PLUGIN_DIR/../mysql-installer/install-alt-mariadb.sh" ] || fail "install-alt-mariadb.sh still exists outside plugin scripts/"
BAD_REFS="$(
old_plugin_path="/usr/local/directadmin/plugins/"'mysql'
old_cmd_path="/CMD_PLUGINS/"'mysql'
find "$PLUGIN_DIR" -type f \
! -path '*/tests/plugin_identity_test.sh' \
! -path '*/data/*' \
-exec grep -HnE "$old_plugin_path|$old_cmd_path" {} + || true
)"
[ -z "$BAD_REFS" ] || {
printf '%s\n' "$BAD_REFS" >&2
fail "old DirectAdmin plugin path references remain"
}
LEGACY_REFS="$(
legacy_sudoers="/etc/sudoers.d/directadmin-"'mysql'"-plugin"
legacy_cron="/etc/cron.d/"'mysql'"-jobs"
legacy_csf="/etc/csf/"'mysql'"-plugin.allow"
find "$PLUGIN_DIR" -type f \
! -path '*/tests/plugin_identity_test.sh' \
! -path '*/data/*' \
-exec grep -HnE "$legacy_sudoers|$legacy_cron|$legacy_csf" {} + || true
)"
[ -z "$LEGACY_REFS" ] || {
printf '%s\n' "$LEGACY_REFS" >&2
fail "legacy mysql plugin artifact references remain"
}
echo "plugin_identity_test: OK"
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
fail() {
echo "FAIL: $*" >&2
exit 1
}
assert_script_has_restore_rollback() {
local file="$1"
local label="$2"
grep -Fq "backup_alt_database_for_rollback" "$file" \
|| fail "$label does not create a rollback dump before overwrite"
grep -Fq "restore_alt_database_from_rollback" "$file" \
|| fail "$label does not restore rollback dump after failed import"
grep -Fq "trap cleanup_restore_rollback" "$file" \
|| fail "$label does not cleanup rollback dump"
}
assert_script_has_restore_rollback "$PLUGIN_DIR/scripts/restore_job.sh" "restore_job.sh"
assert_script_has_restore_rollback "$PLUGIN_DIR/scripts/da-integration/alt_mysql_restore_payload.sh" "alt_mysql_restore_payload.sh"
assert_script_has_restore_rollback "$PLUGIN_DIR/scripts/da-integration/da_restore_native_staging_to_alt_mysql.sh" "da_restore_native_staging_to_alt_mysql.sh"
echo "restore_rollback_safety_test: OK"
+71
View File
@@ -0,0 +1,71 @@
#!/bin/bash
set -euo pipefail
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT="$PLUGIN_DIR/scripts/setup/roundcube_repair_config.sh"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
fail() {
echo "FAIL: $*" >&2
exit 1
}
mkdir -p "$TMP_DIR/mariadb/bin" "$TMP_DIR/roundcube"
cat > "$TMP_DIR/roundcube/config.inc.php" <<'PHP'
<?php
$config = [];
PHP
cat > "$TMP_DIR/alt-mariadb.env" <<EOF
SERVICE_NAME='alt-mariadb-test'
MARIADB_DIR='$TMP_DIR/mariadb'
DATADIR='$TMP_DIR/data'
PORT='4407'
SOCKET='$TMP_DIR/data/mariadb.sock'
ALT_MYSQL_CONF='$TMP_DIR/alt-mysql.conf'
ALT_MY_CNF='$TMP_DIR/alt-my.cnf'
INSTANCE_CNF='$TMP_DIR/alt-mariadb.cnf'
EOF
cat > "$TMP_DIR/alt-mysql.conf" <<EOF
host=
passwd=secret
port=4407
socket=$TMP_DIR/data/mariadb.sock
user=da_admin
EOF
cat > "$TMP_DIR/plugin-settings.conf" <<EOF
MYSQL_PLUGIN_METADATA_FILE=$TMP_DIR/alt-mariadb.env
MYSQL_PLUGIN_CREDENTIALS_FILE=$TMP_DIR/alt-mysql.conf
MYSQL_PLUGIN_ALT_MY_CNF=$TMP_DIR/alt-my.cnf
ROUNDCUBE_ENABLED=true
ROUNDCUBE_DB_NAME=roundcube
ROUNDCUBE_DB_USER=roundcube
ROUNDCUBE_DB_PASSWORD_FILE=$TMP_DIR/alt-roundcube.conf
ROUNDCUBE_CONFIG_FILE=$TMP_DIR/roundcube/config.inc.php
ROUNDCUBE_CUSTOM_CONFIG_FILE=$TMP_DIR/custombuild/custom/roundcube/config.inc.php
ROUNDCUBE_WRITE_CUSTOMBUILD_CONFIG=true
ROUNDCUBE_ALT_DSN_MODE=tcp
EOF
ROUNDCUBE_SETTINGS_FILE="$TMP_DIR/plugin-settings.conf" bash "$SCRIPT" >/dev/null
grep -Fq "BEGIN DIRECTADMIN MYSQL PLUGIN ROUNDCUBE" "$TMP_DIR/roundcube/config.inc.php" \
|| fail "managed block missing"
grep -Fq "127.0.0.1:4407/roundcube" "$TMP_DIR/roundcube/config.inc.php" \
|| fail "alt endpoint not written"
grep -Fq "BEGIN DIRECTADMIN MYSQL PLUGIN ROUNDCUBE" "$TMP_DIR/custombuild/custom/roundcube/config.inc.php" \
|| fail "managed block missing from CustomBuild Roundcube config"
grep -Fq "127.0.0.1:4407/roundcube" "$TMP_DIR/custombuild/custom/roundcube/config.inc.php" \
|| fail "alt endpoint not written to CustomBuild Roundcube config"
grep -Fq "password=" "$TMP_DIR/alt-roundcube.conf" || fail "password file not created"
ROUNDCUBE_SETTINGS_FILE="$TMP_DIR/plugin-settings.conf" bash "$SCRIPT" >/dev/null
COUNT="$(grep -c "BEGIN DIRECTADMIN MYSQL PLUGIN ROUNDCUBE" "$TMP_DIR/roundcube/config.inc.php")"
[ "$COUNT" = "1" ] || fail "managed block is not idempotent"
CUSTOM_COUNT="$(grep -c "BEGIN DIRECTADMIN MYSQL PLUGIN ROUNDCUBE" "$TMP_DIR/custombuild/custom/roundcube/config.inc.php")"
[ "$CUSTOM_COUNT" = "1" ] || fail "CustomBuild managed block is not idempotent"
echo "roundcube_repair_config_test: OK"
+36
View File
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
require_once dirname(__DIR__) . '/exec/lib/Settings.php';
function assert_true(bool $condition, string $message): void
{
if (!$condition) {
fwrite(STDERR, "FAIL: {$message}\n");
exit(1);
}
}
$tmp = tempnam(sys_get_temp_dir(), 'mysql-plugin-settings-');
if ($tmp === false) {
fwrite(STDERR, "FAIL: cannot create temporary settings file\n");
exit(1);
}
file_put_contents($tmp, <<<CONF
allow_remote_hosts=1
modify_server_configuration=0
MYSQL_PLUGIN_ALLOW_REMOTE_HOSTS=0
MYSQL_PLUGIN_MODIFY_SERVER_CONFIGURATION=0
CONF);
$settings = Settings::load($tmp);
@unlink($tmp);
assert_true($settings->allowRemoteHosts(), 'allow_remote_hosts=1 should enable remote hosts');
assert_true(
$settings->modifyServerConfiguration(),
'allow_remote_hosts=1 should enable server synchronization even when modify_server_configuration=0'
);
echo "settings_remote_hosts_test: OK\n";