diff --git a/docs/superpowers/specs/2026-07-05-native-port-takeover-design.md b/docs/superpowers/specs/2026-07-05-native-port-takeover-design.md new file mode 100644 index 0000000..0c27954 --- /dev/null +++ b/docs/superpowers/specs/2026-07-05-native-port-takeover-design.md @@ -0,0 +1,214 @@ +# 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.