# Design: Native MariaDB Port Takeover for alt-mariadb on Port 3306 Date: 2026-07-05 Status: Approved by user, ready for implementation planning ## Goal Let `install_db.sh` install `alt-mariadb` on the standard MySQL/MariaDB port (3306) by first moving DirectAdmin's existing CustomBuild-managed native MariaDB off that port, so that any tool or script that assumes the default port can reach `alt-mariadb` without custom port configuration. ## Background `install_db.sh` currently requires `alt-mariadb`'s port as a mandatory CLI argument (defaulting nowhere, e.g. `install_db.sh 10.11 33033`), and its own `validate_paths()` explicitly rejects a port that matches DirectAdmin's native engine's port. Native MariaDB (installed and managed by CustomBuild) occupies 3306 today. Earlier in this project's design work, a different port-3306 proposal was considered and rejected: pointing DirectAdmin's own `mysql.conf` *at* `alt-mariadb` itself. That failed because DirectAdmin's legacy-code-base license enforces a hard MariaDB-version ceiling (10.6) at login time, independent of CustomBuild settings, and `alt-mariadb` intentionally runs newer versions. This design is different and does not hit that problem: DirectAdmin keeps talking to the *same* native engine, on the *same* version — only the TCP port that engine listens on changes. The freed port 3306 then goes to `alt-mariadb`. The user confirmed empirically, on the actual target server, that setting `mysql_inst=no` in CustomBuild's `options.conf` and then changing the native port in `/etc/my.cnf` survives a `./build rewrite_confs` run — i.e., once MySQL/MariaDB is marked as externally-managed, CustomBuild's config-regeneration sweep no longer touches it. This is the key fact that makes a manual, persistent port change safe here (CustomBuild's per-service `rewrite_confs` logic is gated on it considering that service "managed"). ## Scope - Applies only to a **fresh `alt-mariadb` install** (`install_db.sh`'s normal use case) — not to migrating an already-running `alt-mariadb` instance to a new port. Native MariaDB is always already running at the time `install_db.sh` runs (DirectAdmin itself is already installed), so the "live service surgery" risk is entirely on the native side, never the `alt-mariadb` side. - Runs only on **AlmaLinux 8 or 9**. CloudLinux, Rocky, CentOS, plain RHEL, and AlmaLinux outside 8/9 are explicitly refused before anything is touched — a stricter, separate check from `install_db.sh`'s own permissive `detect_os()`, because this script touches a live, DA-critical service rather than just installing an additional one. - This is a materially one-way decision: `mysql_inst=no` has no clean, officially documented path back to CustomBuild-managed MySQL once other things start depending on the new arrangement. The design rolls back cleanly on failure *during the takeover itself*, but is not meant to be casually reversed afterward. ## CLI change to `install_db.sh` `PORT` becomes optional. Usage becomes `install_db.sh [PORT]`: - `install_db.sh 10.11` — `PORT` defaults to `3306`, triggering the native takeover. - `install_db.sh 10.11 3306` — same as above; the trigger is the *resolved* port value, not whether it was typed explicitly. - `install_db.sh 10.11 33033` — explicit non-3306 port, no takeover, today's behavior is unchanged. `apply_cli_args` changes from requiring exactly 2 arguments to accepting 1 or 2 (still requires at least `MARIADB_VERSION`). Usage text and examples must reflect the new optional-port form. ## Architecture A new, independent script, `scripts/setup/native_mariadb_port_takeover.sh`, holds all logic for moving native MariaDB off 3306. `install_db.sh` calls it once, early in `main()`, only when the resolved `PORT` is `3306` — before `load_da_credentials`, so that by the time DA's credentials are read, `mysql.conf` already reflects the *new* native port and the existing "alt port must differ from DA's port" check in `validate_paths()` passes naturally with no changes needed there. This mirrors how every other multi-step, risk-bearing operation in this plugin is isolated into its own file (`da_restore_native_staging_to_alt_mysql.sh`, `alt_mysql_native_backup_restore.sh`, `mysql_upgrade.sh`) rather than growing the already large `install_db.sh` further, and keeps this logic independently testable with fake command stubs. ## Configuration Top-of-file variables in the new script, in the same "hand-editable config block" convention already used by `install_db.sh` (e.g. `MARIADB_TARBALL_URL`): - `NATIVE_MY_CNF` (default `/etc/my.cnf`) - `NATIVE_MY_CNF_D` (default `/etc/my.cnf.d`) - `NATIVE_MYSQL_CONF` (default `/usr/local/directadmin/conf/mysql.conf`) - `NEW_NATIVE_PORT` (default `3307`) ## Sequencing ### Step 0: OS guard Read `/etc/os-release`. Require `ID=almalinux` and `VERSION_ID` starting with `8` or `9`. Anything else: `die()` immediately, before any other action. ### Step 1: Baseline check Read DA's current native port/socket/credentials from `NATIVE_MYSQL_CONF` (reuse the `mysql.conf` parsing convention already used elsewhere in this plugin). Confirm the native engine is actually reachable right now using those credentials — abort before touching anything if it isn't (this script must not appear to "fix" an unrelated pre-existing problem). Also read the *current* `mysql_inst=` value from CustomBuild's `options.conf` and record it (`mariadb` or `mysql`) — this is what rollback restores. ### Step 2: Idempotency check If the current native port (from Step 1) is already something other than 3306, log "already done" and exit 0. Nothing further to do. ### Step 3: Preflight the new port Confirm `NEW_NATIVE_PORT` is not already bound by anything (reuse `install_db.sh`'s `port_is_listening` pattern). Refuse before making any change if it is. ### Step 4: Disable CustomBuild's MySQL management Run `da build set mysql_inst no` — DirectAdmin's supported CLI, not a raw edit of `options.conf`. Skip if Step 1 already found it set to `no` (idempotency). ### Step 5: Locate, back up, and rewrite the active `port=` directive(s) Scan `NATIVE_MY_CNF` and every `NATIVE_MY_CNF_D/*.cnf` for a `port=` line inside a `[mysqld]`/`[mariadb]`/`[server]` section (later-loaded `my.cnf.d` files override `my.cnf`'s own value for the same directive — both must be checked, not just one). Back up every file before editing it (timestamped copy, matching `install_db.sh`'s `backup_existing_file` convention). If no explicit `port=` line exists anywhere (implicit default), add one to `[mysqld]` in `NATIVE_MY_CNF`. If matches exist, rewrite each to `NEW_NATIVE_PORT`. ### Step 6: Update `mysql.conf` Rewrite only the `port=` field in `NATIVE_MYSQL_CONF` to `NEW_NATIVE_PORT`, via a targeted line replacement — not full-file regeneration — so `user=`/`passwd=`/`host=`/`socket=` are left untouched exactly as they were. ### Step 7: Restart native MariaDB Restart the native MariaDB service (reuse `mysql_service_name()`-style detection). Verify it is listening on `NEW_NATIVE_PORT` and specifically **not** on 3306. ### Step 8: Restart DirectAdmin Restart the `directadmin` service so DA's core picks up the new port immediately, rather than relying on an unconfirmed lazy-reload behavior. This causes a brief (seconds-long) panel interruption — expected, not a failure. ### Step 9: Verify DA's own connectivity Confirm DA's credentials (from the now-updated `mysql.conf`) can connect to the native engine on `NEW_NATIVE_PORT`. This is the real pass/fail gate for the whole operation. Only if Step 9 succeeds does `install_db.sh` continue installing `alt-mariadb`, now targeting port 3306 (already free). ## Error handling and rollback Any failure at Step 4 or later triggers rollback of everything done so far, in reverse order, before the script aborts non-zero: - Restore every file backed up in Step 5 from its timestamped copy. - Run `da build set mysql_inst ` (the value captured in Step 1) to restore CustomBuild management. - Attempt to restart native MariaDB back on 3306; verify it's reachable again via the *original* credentials/port. - If DirectAdmin was already restarted (Step 8+), restart it again so it re-picks-up the reverted config, then verify reachability. The script logs clearly what it reverted and always exits non-zero on any rollback path. `install_db.sh` must not proceed to installing `alt-mariadb` if this script exits non-zero, for any reason. One exception: if Step 9 fails only because DirectAdmin itself won't restart cleanly (unrelated to the port change), the script should say so explicitly rather than reporting it as a takeover failure. ## Testing Matching this repo's established convention (plain bash test scripts under `tests/`, fake command stubs via `PATH`/env-var injection, no external test framework): a new `tests/native_mariadb_port_takeover_test.sh` fakes `da`, `systemctl`, and the `mysql`/`mariadb` client binaries as stub scripts that log their invocations, plus fixture copies of `/etc/my.cnf`, `/etc/my.cnf.d/*.cnf`, and `mysql.conf` under a temp directory (all overridable via the script's own env vars, same pattern as `alt_mysql_native_backup_restore.sh`'s tests). Scenarios: - Happy path: native on 3306 → ends up on 3307, `mysql.conf` updated, exit 0. - Idempotency: native already on 3307 → no-op, exit 0, no file changes. - OS guard: non-AlmaLinux or AlmaLinux outside 8/9 → refuses immediately, no files touched. - Preflight rejection: `NEW_NATIVE_PORT` already in use → refuses before any change. - Rollback: simulate failure at each of Steps 5 through 9 in turn → confirm every backed-up file is restored and `da build set mysql_inst ` is called with the correct original value in each case. ## Out of scope - Migrating an already-running `alt-mariadb` instance to a new port (not needed — this server has no `alt-mariadb` installed yet). - Any OS other than AlmaLinux 8/9. - A documented path back to CustomBuild-managed MySQL/MariaDB after this runs. - Changing `/etc/services` or CSF/firewall rules (native MariaDB is not intended to be remotely reachable; out of scope unless that changes). ## Risks / open items for implementation planning - The exact `[mysqld]`/`[mariadb]`/`[server]` section name in use on this server's `/etc/my.cnf.d/*.cnf` files should be confirmed empirically during implementation (grep the actual files) rather than assumed, since MariaDB accepts more than one section name for the same server-level directives. - `da build set mysql_inst no` and its reverse are assumed to be synchronous, side-effect-free writes to `options.conf` with no network/build side effects; this should be confirmed empirically (e.g. by diffing `options.conf` before/after and confirming no other files change) during the first implementation task, consistent with how this project has handled other empirically-verified-not-documented DirectAdmin behaviors.