Skip to content

Bug: Multiple MySQL Configuration Issues in Tracker Deployer #410

@josecelano

Description

@josecelano

Overview

Three related MySQL configuration bugs were discovered during the Hetzner demo deployment (#405). All three stem from the same area of the codebase (MySQL setup in the deployer) and are best fixed together.

Bug 1 — MySQL DSN hardcoded in tracker.toml with no URL-encoding: The tracker configuration template builds the MySQL DSN by interpolating raw credential values directly into the URL string. If the password contains URL-reserved characters the DSN becomes malformed and the tracker fails to connect. Additionally, the DSN (including the plaintext password) ends up in a mounted config file rather than an environment variable, violating the project's secret-handling convention.

Bug 2 — MySQL root password is not user-configurable: The deployer silently derives the MySQL root password as {app_password}_root (hardcoded in src/application/services/rendering/docker_compose.rs). Users have no way to supply their own root password via the environment configuration JSON.

Bug 3 — No validation that MySQL app username is not "root": The MySQL Docker image reserves the root username for the built-in administrator account. If a user supplies "root" as the app DB username in their environment JSON, Docker will refuse to initialize the database. The domain type MysqlConfig validates for an empty username but not for this reserved value.

Specification

See detailed specification: docs/issues/410-bug-multiple-mysql-configuration-issues.md

Implementation Plan

Phase 1: Reject reserved MySQL username (Bug 3)

  • In MysqlConfigError (mysql.rs): add ReservedUsername variant
  • Add help() arm for ReservedUsername with actionable fix instructions
  • In MysqlConfig::new(): add if username == "root" guard returning Err(MysqlConfigError::ReservedUsername)
  • Add unit test it_should_reject_root_as_username

Phase 2: Make root password configurable (Bug 2)

  • schemas/environment-config.json: add optional root_password string to the MySQL database object
  • MysqlConfig (mysql.rs): add optional root_password field; update constructor and accessor
  • Deserialization/config-loading path: thread the optional field through to the application layer
  • create_mysql_contexts (docker_compose.rs): replace format!("{password}_root") with user-supplied value or a randomly generated password

Phase 3: Move DSN to env var override and add URL-encoding (Bug 1)

  • Add percent-encoding to Cargo.toml
  • TrackerServiceConfig (env/context.rs): add optional database path field
  • EnvContext::new_with_mysql: percent-encode username and password, build the full DSN string
  • TrackerContext (tracker_config/context.rs): remove MysqlTemplateConfig and the MySQL branch that builds it
  • templates/docker-compose/.env.tera: add TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH inside {%- if mysql %}
  • templates/docker-compose/docker-compose.yml.tera: inject the new env var into the tracker service environment: section
  • templates/tracker/tracker.toml.tera: remove the MySQL path = line; add a comment

Phase 4: Tests

  • mysql.rs: add it_should_reject_root_as_username unit test
  • env/context.rs: add test that new_with_mysql produces a correctly percent-encoded DSN for a password containing special characters
  • env/context.rs: add test that new (SQLite) leaves the database path field as None
  • tracker_config/context.rs: remove or update the test that referenced mysql_password on MysqlTemplateConfig
  • Run cargo test to verify all tests pass

Phase 5: Linting and pre-commit

  • Run linters: cargo run --bin linter all
  • Run pre-commit: ./scripts/pre-commit.sh

Acceptance Criteria

Note for Contributors: These criteria define what the PR reviewer will check. Use this as your pre-review checklist before submitting the PR to minimize back-and-forth iterations.

Quality Checks:

  • Pre-commit checks pass: ./scripts/pre-commit.sh

Bug 3:

  • MysqlConfig::new() returns Err(MysqlConfigError::ReservedUsername) when username is "root"
  • MysqlConfigError::ReservedUsername has a help() message with an actionable fix
  • A unit test for the reserved username rejection exists and passes

Bug 2:

  • The environment configuration JSON schema accepts an optional root_password field in the MySQL database object
  • When root_password is provided in the env JSON it is used as MYSQL_ROOT_PASSWORD in the rendered .env
  • When root_password is omitted, a randomly generated password is used — it is not derived from the app password
  • create_mysql_contexts no longer contains format!("{password}_root")

Bug 1:

  • The rendered tracker.toml for a MySQL deployment does not contain the database password
  • The rendered .env file contains TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH with a correctly percent-encoded DSN when MySQL is configured
  • The rendered .env file does not contain TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH when SQLite is configured
  • The rendered docker-compose.yml injects TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH into the tracker service environment when MySQL is configured
  • A MySQL password containing URL-reserved characters (e.g. @, +, /) produces a valid, correctly encoded DSN in the .env file
  • A MySQL password with only alphanumeric characters is rendered unchanged
  • MysqlTemplateConfig no longer exists in tracker_config/context.rs
  • cargo machete reports no unused dependencies

Related

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingrust

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions