33 KiB
Multi-Database Installation Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Refactor alt-mysql so one DirectAdmin plugin can install, discover, secure, operate, back up, restore, and expose multiple MariaDB versions as separate local instances.
Architecture: Introduce an explicit MariaDB instance registry. The existing /usr/local/directadmin/conf/alt-mariadb.env becomes the registry/index file, while every installed version gets its own per-instance env file, systemd unit, datadir, socket, pid file, and server config. /usr/local/directadmin/conf/alt-mysql.conf and /usr/local/directadmin/conf/alt-my.cnf remain shared credential-only files; runtime code appends the selected instance socket/port explicitly for every operation.
Tech Stack: Bash, PHP 8-compatible plugin code, mysqli, DirectAdmin plugin handlers, systemd units, MariaDB binary tarballs, CSF allow-file integration, DirectAdmin backup/restore hooks.
Current State Summary
The plugin currently assumes one alternative MariaDB instance:
scripts/install_db.shwrites one/usr/local/directadmin/conf/alt-mariadb.env, one/etc/alt-mariadb.cnf, onealt-mariadbsystemd service, one datadir, one socket, one port, and one shared client defaults file that also contains the socket.scripts/setup/mysql_common.shresolves a single runtime and every shell integration uses that runtime.exec/bootstrap.phpcreates oneMySQLServicefrom one credential file.exec/lib/MySQLService.phpstores metadata tables inside that one MariaDB instance and all database/user/host operations run against it.exec/handlers/index.phplists databases from one instance, prints one server version, and creates databases without a version selector.BackupQueueService,backup_job.sh,restore_job.sh, DA backup/restore hooks, phpMyAdmin SSO, and CSF host sync all assume thatDB_NAMEis enough to identify the target.
This must change before multiple versions are safe. Without an instance identifier, a backup, restore, deletion, phpMyAdmin login, or host-rule sync can target the wrong MariaDB instance.
Target Runtime Model
Instance Identity
Use one stable instance id per MariaDB series. The id is the version string accepted by the installer, for example:
10.1111.411.8
For shell-safe names, derive a slug by replacing unsupported characters with _ only where needed:
- systemd unit:
alt-mariadb-10.11.service - config file:
/etc/alt-mariadb-10.11.cnf - datadir:
/var/lib/alt-mariadb-10.11 - socket:
/var/lib/alt-mariadb-10.11/mariadb.sock - pid file:
/var/lib/alt-mariadb-10.11/mariadb.pid - log dir:
/var/log/alt-mariadb-10.11 - runtime dir:
/run/alt-mariadb-10.11 - binary dir:
/usr/local/mariadb-10.11
The plan intentionally allows only one active instance per version series. Installing 11.4 twice should fail with a clear message unless the existing 11.4 instance is being repaired.
Shared Credentials
Keep credentials shared across all alternative MariaDB instances:
/usr/local/directadmin/conf/alt-mysql.conf
host=
user=da_admin
passwd=<shared secret>
database=mysql
/usr/local/directadmin/conf/alt-my.cnf
[client]
user="da_admin"
password="<shared secret>"
Do not store port= or socket= in the shared client file. Every call must pass --socket="$INSTANCE_SOCKET" or --protocol=TCP --host=127.0.0.1 --port="$INSTANCE_PORT" explicitly. This avoids silently connecting to the first installed version when the user selected another version.
On first install, initialize the shared credential files from the current DirectAdmin native MySQL credentials, as the existing script does. On every later install, detect that /usr/local/directadmin/conf/alt-mariadb.env already exists and reuse /usr/local/directadmin/conf/alt-mysql.conf and /usr/local/directadmin/conf/alt-my.cnf; do not read or overwrite credentials from /usr/local/directadmin/conf/mysql.conf.
The shared password must be applied to:
- the shared admin user from
alt-mysql.confon every installed instance, root@localhoston every installed instance,root@127.0.0.1where the account exists or is created.
Registry Files
Keep /usr/local/directadmin/conf/alt-mariadb.env as the canonical registry/index file.
Example:
# alt-mysql MariaDB instance registry.
ALT_MARIADB_REGISTRY_VERSION='2'
ALT_MARIADB_DEFAULT_INSTANCE='10.11'
ALT_MARIADB_INSTANCES='10.11 11.4'
ALT_MARIADB_DIR='/usr/local/directadmin/conf/alt-mariadb.d'
ALT_MYSQL_CONF='/usr/local/directadmin/conf/alt-mysql.conf'
ALT_MY_CNF='/usr/local/directadmin/conf/alt-my.cnf'
Store each instance in /usr/local/directadmin/conf/alt-mariadb.d/<instance-id>.env.
Example:
INSTANCE_ID='11.4'
MARIADB_VERSION='11.4'
MARIADB_RELEASE='11.4.8'
SERVICE_NAME='alt-mariadb-11.4'
MARIADB_DIR='/usr/local/mariadb-11.4'
DATADIR='/var/lib/alt-mariadb-11.4'
PORT='3020'
SOCKET='/var/lib/alt-mariadb-11.4/mariadb.sock'
PID_FILE='/var/lib/alt-mariadb-11.4/mariadb.pid'
INSTANCE_CNF='/etc/alt-mariadb-11.4.cnf'
SERVICE_FILE='/etc/systemd/system/alt-mariadb-11.4.service'
LOG_DIR='/var/log/alt-mariadb-11.4'
RUN_DIR='/run/alt-mariadb-11.4'
SYSTEM_USER='mysql'
SYSTEM_GROUP='mysql'
BIND_ADDRESS='127.0.0.1'
ALT_MYSQL_CONF='/usr/local/directadmin/conf/alt-mysql.conf'
ALT_MY_CNF='/usr/local/directadmin/conf/alt-my.cnf'
Migration rule for existing single-instance installs:
- If
alt-mariadb.envcontainsSERVICE_NAME=,MARIADB_VERSION=,PORT=, andSOCKET=, treat it as legacy format. - Create
/usr/local/directadmin/conf/alt-mariadb.d/<MARIADB_VERSION>.envfrom those values. - Rewrite
alt-mariadb.envas the registry/index file. - Keep a timestamped backup of the legacy file before rewriting it.
Data And Metadata Model
Metadata Tables
Keep metadata tables inside every MariaDB instance. This is the safest model because each MariaDB instance can operate independently and there is no single metadata database that breaks the whole fleet.
Add instance_id to every plugin metadata table whose rows refer to user databases or roles:
ALTER TABLE da_plugin_database_owners
ADD COLUMN instance_id VARCHAR(32) NOT NULL DEFAULT '';
ALTER TABLE da_plugin_grant_profiles
ADD COLUMN instance_id VARCHAR(32) NOT NULL DEFAULT '';
ALTER TABLE da_plugin_access_hosts
ADD COLUMN instance_id VARCHAR(32) NOT NULL DEFAULT '';
ALTER TABLE da_plugin_sso_accounts
ADD COLUMN instance_id VARCHAR(32) NOT NULL DEFAULT '';
Then migrate primary keys:
da_plugin_database_owners: primary key becomes(instance_id, db_name).da_plugin_grant_profiles: primary key becomes(instance_id, db_name, role_name).da_plugin_access_hosts: primary key becomes(instance_id, role_name, host_pattern).da_plugin_sso_accounts: primary key becomes(instance_id, role_name, host_pattern).
For a fresh instance, create the schema with instance_id already populated. For a migrated single-instance schema, fill instance_id with the instance id currently being loaded.
Database Name Uniqueness
Recommended first implementation: database names remain globally unique across all plugin-managed instances.
Reasoning:
- It avoids ambiguous DirectAdmin restore behavior.
- It keeps CMS credential changes simple.
- It avoids accidental
phpMyAdmin, backup, restore, and delete actions against the wrong version. - It keeps existing user-facing workflows mostly unchanged.
Implementation rule:
- Before creating a database on selected version
X, query every installed instance and reject the name if it exists anywhere. - Later, if duplicate names across versions are required, all forms, job files, URLs, payload manifests, and locks already need
instance_id; this plan prepares that path, but the first production-safe step should enforce global uniqueness.
Installer Refactor
Required Behavior
scripts/install_db.sh remains the entry point:
./scripts/install_db.sh 11.4 3020
It must:
- require both version and port,
- derive datadir as
/var/lib/alt-mariadb-$version, - derive socket as
/var/lib/alt-mariadb-$version/mariadb.sock, - derive pid file as
/var/lib/alt-mariadb-$version/mariadb.pid, - derive config file as
/etc/alt-mariadb-$version.cnf, - derive service name as
alt-mariadb-$version, - reuse shared root/admin password from
alt-mysql.confif the registry already exists, - write or update only the selected instance env file,
- update
ALT_MARIADB_INSTANCESin the registry without removing existing instances, - run non-interactive secure-install SQL for the selected instance,
- run
scripts/install.shafter successful install unlessSKIP_PLUGIN_INSTALL_REFRESH=1.
Non-Interactive Secure Installation
After the instance starts and after shared admin/root credentials are set, run a function equivalent to mariadb-secure-installation without prompts.
The SQL must be version-tolerant:
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db = 'test' OR Db LIKE 'test\\_%';
FLUSH PRIVILEGES;
For anonymous users and remote root accounts:
- Prefer
mysql.global_privwhen it exists. - Fall back to
mysql.userwhenmysql.global_privdoes not exist. - Remove anonymous users.
- Remove
rootaccounts whose host is notlocalhost,127.0.0.1, or::1. - Keep local
rootaccounts and set their password to the shared secret.
The secure SQL function must log the actions but never print the password.
Installer Tests
Add tests before implementation:
-
tests/install_db_multi_instance_test.sh- validates derived paths for
11.4 3020, - validates service name
alt-mariadb-11.4, - validates no default
/var/lib/mariadb_data, - validates registry update functions exist,
- validates secure-install function exists and references anonymous users, test database, and remote root cleanup.
- validates derived paths for
-
tests/registry_migration_test.sh- feeds a legacy
alt-mariadb.env, - expects a registry file plus
alt-mariadb.d/<version>.env, - expects the original file backup to be created.
- feeds a legacy
PHP Runtime Refactor
New Classes
Create exec/lib/MariaDbInstance.php.
Responsibilities:
- hold
instanceId,version,release,serviceName,basedir,datadir,port,socket,pidFile,instanceCnf,serviceFile,logDir,runDir, - expose
clientCredentials(array $sharedCredentials): array, - expose
label(): stringfor UI display.
Create exec/lib/MariaDbInstanceRegistry.php.
Responsibilities:
- load
/usr/local/directadmin/conf/alt-mariadb.env, - migrate legacy single-instance env format,
- load all
/usr/local/directadmin/conf/alt-mariadb.d/*.env, - return
all(): array, - return
count(): int, - return
default(): MariaDbInstance, - return
find(string $instanceId): MariaDbInstance, - validate user-submitted instance ids.
Create exec/lib/MySQLServiceFactory.php.
Responsibilities:
- receive
Settings, shared credentials, and registry, - create a
MySQLServicefor a selected instance, - create services for all instances,
- expose fleet-level helpers such as
databaseExistsAnywhere()andlistDatabasesForUserAcrossInstances().
Existing Class Changes
Modify exec/bootstrap.php:
- load shared credentials once from
alt-mysql.conf, - load instance registry,
- construct
MySQLServiceFactory, - construct default
MySQLServicefor old handlers that are still single-instance during transition, - pass registry and factory into
AppContext.
Modify exec/lib/AppContext.php:
- add
public MariaDbInstanceRegistry $instances, - add
public MySQLServiceFactory $mysqlFactory, - keep
public MySQLService $mysqlas the default instance for backward compatibility during the refactor.
Modify exec/lib/MySQLService.php:
- accept a
MariaDbInstancein the constructor, - use selected instance socket/port in connection credentials,
- add
instanceId(): string, - make metadata schema instance-aware,
- scope owner/grant/host/sso metadata queries by
instance_id.
PHP Tests
Add:
tests/instance_registry_test.phptests/mysql_service_factory_test.phptests/multi_instance_metadata_schema_test.php
Test cases:
- registry loads two instances from fixture env files,
- legacy env migrates to registry model,
- invalid instance id is rejected,
- factory builds credentials with shared user/password but per-instance socket/port,
ensureMetadataSchema()creates instance-aware metadata definitions,databaseExistsAnywhere()checks every instance.
Shell Runtime Refactor
scripts/setup/mysql_common.sh
Refactor the shell helper so every script can select an instance.
New contract:
MYSQL_ALT_INSTANCE_ID="${MYSQL_ALT_INSTANCE_ID:-}"
mysql_load_alt_runtime "$MYSQL_ALT_INSTANCE_ID"
If no instance id is provided:
- use
ALT_MARIADB_DEFAULT_INSTANCE, - if only one instance exists, use that instance,
- if more than one instance exists and no default is configured, fail with a clear message.
New functions:
mysql_registry_filemysql_instance_dirmysql_list_instancesmysql_default_instancemysql_validate_instance_idmysql_load_instance_envmysql_instance_socket "$id"mysql_instance_port "$id"mysql_instance_basedir "$id"mysql_instance_service "$id"mysql_base_args_for_instance "$id"
Keep existing wrappers like mysql_alt_socket and mysql_alt_port, but make them call the selected runtime.
Shell Tests
Update:
tests/mysql_common_runtime_test.shtests/mysql_host_sync_test.shtests/backup_queue_service_test.shtests/restore_rollback_safety_test.sh
Add:
tests/mysql_common_multi_runtime_test.sh
Test cases:
- loading a selected instance returns the selected socket/port,
- loading without instance id uses default when only one instance exists,
- loading without instance id fails when multiple instances exist and no default is set,
- binary detection prefers the selected instance basedir,
- generated mysql arguments include shared credentials plus selected socket.
UI And User Workflow
Main Page
Modify exec/handlers/index.php.
When registry count is 1:
- keep the current layout,
- keep showing the single server version.
When registry count is greater than 1:
- do not show a single global MariaDB version in the header,
- add a table column
Wersja MariaDB, - show each database row with its instance version,
- include hidden
instance_idfields in row actions: phpMyAdmin login, backup, manage privileges, delete.
Create Database Form
When registry count is greater than 1:
- show a required select field
Wersja MariaDB, - options come from the registry, sorted by version,
- default option is
ALT_MARIADB_DEFAULT_INSTANCE, - create the database through the selected instance service.
When registry count is 1:
- do not show the selector,
- create in the only/default instance.
Validation:
- reject unknown instance id,
- reject creating a database name that already exists in any plugin-managed instance,
- in advanced mode, list existing users only from the selected instance.
Manage, Delete, Privileges, phpMyAdmin
Every handler that references a database must accept instance_id:
index.phpmodify_privileges.phpassign_database.phpchange_password.phpdelete_databases.phpdelete_users.phpmanage_hosts.phpopen_phpmyadmin.phpupload_backup.php
For GET links, include instance_id in the query. For POST forms, include it as hidden input and validate it through MariaDbInstanceRegistry.
UI Tests
Extend PHP tests or add handler-focused fixtures:
- one instance: no version selector, no version column, global version visible,
- two instances: version selector visible, version column visible, global version hidden,
- create database posts selected
instance_id, - row actions include selected
instance_id.
phpMyAdmin Integration
Current private phpMyAdmin SSO assumes one endpoint. Refactor ticket payloads to include instance routing.
Ticket payload should include:
{
"version": 2,
"instance_id": "11.4",
"db": "user_db",
"auth": {
"host": "localhost",
"port": 3020,
"socket": "/var/lib/alt-mariadb-11.4/mariadb.sock",
"user": "da_tmp_phpmyadmin_x",
"password": "..."
}
}
The generated phpMyAdmin da_login.php must:
- read the ticket,
- use the ticket socket/port instead of a single default
alt-my.cnfendpoint, - still use the shared credential file only for bootstrap/admin operations,
- reject tickets with unknown
instance_id.
PhpMyAdminSso must:
- find the requested database and instance,
- create temporary SSO users in that selected instance only,
- write the selected instance id into the ticket.
Tests:
- opening phpMyAdmin for a database on
11.4writes a ticket withinstance_id=11.4, - generated signon script uses ticket endpoint,
- unknown instance id fails.
Backup And Restore
Queue Jobs
Every job file must include:
INSTANCE_ID='11.4'
DB_NAME='user_db'
Lock files must include instance id:
data/lock/11.4__user_db.lock
This prevents a backup on 10.11/user_db from blocking or being confused with 11.4/user_db if duplicate database names are later allowed. Even while global uniqueness is enforced, this makes the queue future-proof.
Backup Job
Modify BackupQueueService::queueBackupJob():
- accept
instance_id, - validate that the database exists in that instance,
- write
INSTANCE_IDinto the job env.
Modify scripts/backup_job.sh:
- read
INSTANCE_ID, - call
mysql_load_alt_runtime "$INSTANCE_ID", - log the selected instance id and version,
- write backup sidecar metadata next to the SQL dump.
Sidecar example:
{
"source": "alt-mysql",
"instance_id": "11.4",
"mariadb_version": "11.4",
"database": "user_db",
"created_at": "2026-06-16T20:00:00+02:00"
}
Restore Job
Modify restore queue methods:
- accept target
instance_id, - validate target database in that instance,
- write
INSTANCE_IDinto job env.
Modify scripts/restore_job.sh:
- read
INSTANCE_ID, - call
mysql_load_alt_runtime "$INSTANCE_ID", - perform rollback backup in the selected instance,
- restore only into the selected instance.
For external backups without plugin sidecar metadata:
- the restore form must require the target database, and if more than one instance exists it must also require the target MariaDB version,
- no payload conversion is required,
- restore uses the selected target instance.
For local backups created by the plugin:
- default restore target should be the source instance from sidecar metadata,
- allow restoring into another version only when
ALLOW_RESTORE_TO_OTHER_DATABASE=trueand the target database already exists or the workflow explicitly creates it.
DirectAdmin Backup/Restore Hooks
Modify:
scripts/da-integration/alt_mysql_user_backup_compress_pre.shscripts/da-integration/alt_mysql_restore_payload.shscripts/da-integration/da_restore_native_staging_to_alt_mysql.sh
Backup hook:
- iterate every installed instance,
- dump user databases from each instance,
- write manifest entries with
instance_id,mariadb_version,socket,port,database,sha256, and relative file path, - dump metadata tables per instance.
Restore hook with plugin payload:
- read
instance_idfrom manifest, - if that instance exists, restore there,
- if it does not exist, use a configurable fallback:
- default: fail with a clear message,
- optional setting: restore to
ALT_MARIADB_DEFAULT_INSTANCE.
Restore hook for external/native backups without plugin payload:
- continue the staging flow through native DirectAdmin MariaDB,
- migrate staged databases to
ALT_MARIADB_DEFAULT_INSTANCEby default, - add setting
DA_ALT_MYSQL_EXTERNAL_RESTORE_INSTANCE=defaultwhere value can be a concrete instance id such as11.4, - preserve database name, user name, and password as currently required.
Tests:
- backup hook manifest contains two databases on two versions,
- payload restore uses manifest-selected instance,
- external restore without payload uses configured fallback instance,
- restore fails clearly when manifest references a missing instance and no fallback is enabled.
Remote Hosts And CSF
mysql_host_sync.sh currently syncs one port and one config. Refactor it to iterate all active instances:
- for every installed instance, update bind-address according to
allow_remote_hosts, - restart only services whose config changed,
- write CSF allow rules for every active instance port and every allowed remote host,
- keep using a separate managed file such as
/etc/csf/alt-mysql-plugin.allow, - include comments that identify instance id and port.
Example managed CSF line:
tcp|in|d=3020|s=203.0.113.10 # alt-mysql 11.4 user_db_user
Tests:
- one host and two instances produce two CSF entries,
- disabling remote hosts removes managed entries for every instance,
- config rewrite changes all instance configs from
127.0.0.1to configured bind address.
Upgrade Workflow
scripts/setup/mysql_upgrade.sh must become instance-aware.
Required CLI:
TARGET_MARIADB_VERSION=11.8 SOURCE_INSTANCE_ID=11.4 ./scripts/setup/mysql_upgrade.sh
Behavior:
- load source instance from registry,
- install target binaries to
/usr/local/mariadb-11.8, - keep the same datadir only when performing an in-place major upgrade,
- if target version implies new instance id, update registry atomically,
- maintain rollback of binaries and config,
- run
mariadb-upgradeagainst the selected socket, - update per-instance env file and registry only after health checks pass.
Recommended first implementation:
- support patch-level upgrades within the same instance id first,
- support cross-series upgrade only as a separate, explicit task after multi-instance installation is stable.
Uninstall And Health Checks
Modify scripts/uninstall.sh:
- support uninstalling one instance by id,
- refuse to remove shared credential files while any instance remains,
- remove shared credential files only when the last instance is removed and the user explicitly confirms.
Modify scripts/setup/alt_mysql_health_check.sh:
- with no args, check every registered instance,
- with
INSTANCE_ID=11.4, check only that instance, - validate service active, socket exists, shared credential login works, metadata schema exists.
Add tests:
- health check iterates two fixtures,
- uninstall of one instance leaves shared credentials and other instance registry entry,
- uninstall of last instance removes registry only when explicit cleanup flag is set.
Implementation Tasks
Task 1: Add Registry Fixtures And Failing Tests
Files:
- Create:
tests/fixtures/legacy-alt-mariadb.env - Create:
tests/fixtures/alt-mariadb.env - Create:
tests/fixtures/alt-mariadb.d/10.11.env - Create:
tests/fixtures/alt-mariadb.d/11.4.env - Create:
tests/instance_registry_test.php - Create:
tests/mysql_common_multi_runtime_test.sh
Steps:
- Add fixture env files matching the formats in this plan.
- Write
instance_registry_test.phpto assert two instances load and legacy migration is detected. - Write
mysql_common_multi_runtime_test.shto assert selected instance socket/port. - Run the tests and confirm they fail because registry classes/functions do not exist.
Expected red failures:
Class "MariaDbInstanceRegistry" not foundmysql_list_instances: command not found
Task 2: Implement PHP Registry
Files:
- Create:
exec/lib/MariaDbInstance.php - Create:
exec/lib/MariaDbInstanceRegistry.php - Modify:
exec/bootstrap.php - Modify:
tests/instance_registry_test.php
Steps:
- Implement strict env parsing with only expected keys.
- Reject instance ids outside
^[0-9]+\\.[0-9]+$. - Load index file and per-instance env files.
- Implement legacy single-instance detection and migration helper.
- Add bootstrap autoload entries.
- Run
php tests/instance_registry_test.phpand confirm pass. - Run
php -l exec/lib/MariaDbInstance.php exec/lib/MariaDbInstanceRegistry.php.
Task 3: Implement Shell Registry Runtime
Files:
- Modify:
scripts/setup/mysql_common.sh - Test:
tests/mysql_common_multi_runtime_test.sh - Test:
tests/mysql_common_runtime_test.sh
Steps:
- Add registry path variables.
- Add instance list/default/validation functions.
- Refactor
mysql_load_alt_runtimeto accept an optional instance id. - Keep old wrappers for compatibility.
- Run shell tests and
bash -n scripts/setup/mysql_common.sh.
Task 4: Refactor Installer For Per-Version Instances
Files:
- Modify:
scripts/install_db.sh - Create:
tests/install_db_multi_instance_test.sh - Create:
tests/registry_migration_test.sh
Steps:
- Change derived paths to
/var/lib/alt-mariadb-$version,/etc/alt-mariadb-$version.cnf, andalt-mariadb-$version. - Add first-install credential initialization.
- Add later-install credential reuse from existing registry.
- Add registry writer and per-instance env writer.
- Add legacy registry migration.
- Add non-interactive secure-install SQL function.
- Add tests for path derivation, credential reuse, registry update, and secure SQL markers.
- Run installer tests and
bash -n scripts/install_db.sh.
Task 5: Make MySQLService Instance-Aware
Files:
- Modify:
exec/lib/MySQLService.php - Create:
exec/lib/MySQLServiceFactory.php - Modify:
exec/bootstrap.php - Modify:
exec/lib/AppContext.php - Create:
tests/mysql_service_factory_test.php - Create:
tests/multi_instance_metadata_schema_test.php
Steps:
- Add
MariaDbInstanceconstructor argument toMySQLService. - Scope metadata schema by
instance_id. - Add factory methods for selected and all instances.
- Add fleet-level database uniqueness check.
- Update bootstrap and context.
- Run PHP tests and
php -lfor touched files.
Task 6: Refactor Main UI And Create Database Flow
Files:
- Modify:
exec/handlers/index.php - Modify:
exec/handlers/create_database.php - Modify:
user/enhanced.css - Modify:
user/evolution.css - Add/modify handler tests according to current test style.
Steps:
- Aggregate database rows across all instances.
- Hide global MariaDB version when more than one instance exists.
- Add version column only when more than one instance exists.
- Add version selector to create form only when more than one instance exists.
- Validate selected instance id on create.
- Enforce global database-name uniqueness across all instances.
- Keep one-instance UI unchanged.
Task 7: Pass Instance Id Through All Database Actions
Files:
- Modify:
exec/handlers/modify_privileges.php - Modify:
exec/handlers/assign_database.php - Modify:
exec/handlers/change_password.php - Modify:
exec/handlers/delete_databases.php - Modify:
exec/handlers/delete_users.php - Modify:
exec/handlers/manage_hosts.php - Modify:
exec/handlers/open_phpmyadmin.php - Modify:
exec/handlers/upload_backup.php
Steps:
- Add hidden
instance_idto every POST form. - Add
instance_idquery param to every database action URL. - Validate every submitted instance id through registry.
- Use selected
MySQLServicefrom factory. - Reject actions where the selected database does not exist in the selected instance.
Task 8: Refactor phpMyAdmin SSO
Files:
- Modify:
exec/lib/PhpMyAdminSso.php - Modify:
scripts/setup/phpmyadmin_install.sh - Modify:
scripts/setup/phpmyadmin_private_install.sh - Modify:
scripts/setup/phpmyadmin_health_check.sh - Modify:
tests/phpmyadmin_install_test.sh
Steps:
- Add
instance_idto SSO ticket payload. - Include selected socket/port in ticket auth data.
- Make
da_login.phpuse ticket endpoint, not a singleton socket from sharedalt-my.cnf. - Reject unknown instance ids.
- Test signon against fixture ticket for
11.4.
Task 9: Refactor Backup And Restore Queue
Files:
- Modify:
exec/lib/BackupQueueService.php - Modify:
scripts/backup_job.sh - Modify:
scripts/restore_job.sh - Modify:
scripts/worker.sh - Modify:
tests/backup_queue_service_test.sh - Modify:
tests/restore_rollback_safety_test.sh
Steps:
- Add
INSTANCE_IDto job env files. - Include
INSTANCE_IDin lock paths. - Load selected runtime in backup and restore jobs.
- Write backup sidecar metadata with instance id/version.
- Default local restore to source instance when sidecar exists.
- Require selected target instance for external uploads when more than one instance exists.
Task 10: Refactor DirectAdmin Backup/Restore Hooks
Files:
- Modify:
scripts/da-integration/alt_mysql_user_backup_compress_pre.sh - Modify:
scripts/da-integration/alt_mysql_restore_payload.sh - Modify:
scripts/da-integration/da_restore_native_staging_to_alt_mysql.sh - Modify:
scripts/setup/da_integration_install.sh - Add/modify DA integration tests.
Steps:
- Iterate all installed instances during backup.
- Add instance metadata to payload manifest.
- Restore payload entries to their source instance when present.
- Add setting
DA_ALT_MYSQL_EXTERNAL_RESTORE_INSTANCE. - Restore external backups without payload to configured/default instance.
- Preserve database name, user name, and password.
Task 11: Refactor Remote Host Sync And CSF
Files:
- Modify:
scripts/setup/mysql_host_sync.sh - Modify:
plugin-settings.conf - Modify:
tests/mysql_host_sync_test.sh
Steps:
- Iterate active instances.
- Rewrite bind-address per instance config.
- Generate managed CSF allow entries for every instance port and allowed host.
- Restart only changed services.
- Test two ports with one remote host.
Task 12: Refactor Upgrade, Health Check, And Uninstall
Files:
- Modify:
scripts/setup/mysql_upgrade.sh - Modify:
scripts/setup/alt_mysql_health_check.sh - Modify:
scripts/uninstall.sh - Modify:
tests/mysql_upgrade_rollback_test.sh
Steps:
- Add instance selection to upgrade.
- Keep rollback per selected instance.
- Run
mariadb-upgradethrough selected socket. - Health-check every instance by default.
- Uninstall one instance without removing shared credentials.
Task 13: Packaging And Versioning
Files:
- Modify:
plugin.conf - Modify:
tests/plugin_identity_test.sh - Modify:
tests/package_archive_test.sh
Steps:
- Bump plugin version for the implementation release.
- Ensure package excludes tests and macOS metadata.
- Ensure archive contains new PHP classes and no stale single-instance installer names.
- Run the full validation command.
- Build
alt-mysql.tar.gzwithout the parent directory.
Full Validation Command
Run after implementation:
set -euo pipefail
find scripts -type f -name '*.sh' -print0 | sort -z | xargs -0 -n1 bash -n
find exec -type f -name '*.php' -print0 | sort -z | xargs -0 -n1 php -l
bash tests/plugin_identity_test.sh
php tests/instance_registry_test.php
php tests/mysql_service_factory_test.php
php tests/multi_instance_metadata_schema_test.php
bash tests/mysql_common_runtime_test.sh
bash tests/mysql_common_multi_runtime_test.sh
bash tests/install_db_cli_test.sh
bash tests/install_db_multi_instance_test.sh
bash tests/registry_migration_test.sh
php tests/always_enabled_test.php
bash tests/custom_package_items_test.sh
bash tests/da_integration_install_test.sh
bash tests/custombuild_hooks_install_test.sh
bash tests/roundcube_repair_config_test.sh
bash tests/mysql_host_sync_test.sh
php tests/settings_remote_hosts_test.php
bash tests/phpmyadmin_install_test.sh
bash tests/restore_rollback_safety_test.sh
bash tests/mysql_upgrade_rollback_test.sh
bash tests/backup_queue_service_test.sh
bash tests/package_archive_test.sh
Rollout Strategy
- Release registry support while still operating one instance.
- Migrate existing single-instance installs into the registry format.
- Install a second instance on a staging VM with a distinct port.
- Verify UI database creation on both versions.
- Verify phpMyAdmin SSO per version.
- Verify plugin backup/restore per version.
- Verify DirectAdmin backup/restore payload with two versions.
- Verify external DirectAdmin restore without payload goes to configured default instance.
- Enable remote hosts and verify CSF rules for every active instance port.
Production Acceptance Criteria
- Installing
./scripts/install_db.sh 10.11 3011creates/var/lib/alt-mariadb-10.11,/var/lib/alt-mariadb-10.11/mariadb.sock, andalt-mariadb-10.11.service. - Installing
./scripts/install_db.sh 11.4 3020does not overwrite the10.11registry, datadir, service, socket, config, or credentials. /usr/local/directadmin/conf/alt-mysql.confand/usr/local/directadmin/conf/alt-my.cnfare shared and contain the same admin/root secret for all instances.- Every installed instance has had anonymous users removed, test database removed, remote root accounts removed, and local root/admin password set.
- With one instance, the UI remains close to the current UI and shows the server version.
- With more than one instance, the UI shows a database-version column, shows a version selector during database creation, and does not show one global server version.
- Backup, restore, delete, privilege editing, host management, and phpMyAdmin login all use the selected
instance_id. - External DirectAdmin restores without plugin payload restore to the configured/default alt instance and preserve database name, user name, and password.
- CSF rules open every active alt MariaDB port only for the hosts defined in the plugin.