Files
alt-mysql/docs/superpowers/specs/2026-07-05-native-port-takeover-design.md
T

215 lines
11 KiB
Markdown

# 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 <MARIADB_VERSION> [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 <original_value>` (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 <original>` 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.