Skip to content

fix: create DB user with correct host scope for TCP connections to MariaDB#13

Open
amitascra wants to merge 2 commits into
frappe:mainfrom
amitascra:fix/mariadb-create-user-host-scope
Open

fix: create DB user with correct host scope for TCP connections to MariaDB#13
amitascra wants to merge 2 commits into
frappe:mainfrom
amitascra:fix/mariadb-create-user-host-scope

Conversation

@amitascra
Copy link
Copy Markdown

Problem

Frappe's new-site always creates the site DB user as @'localhost'. In MySQL's privilege tables, 'localhost' matches only unix socket connections — TCP connections from 127.0.0.1 are rejected even on the same host. This breaks bench new-site when MariaDB is running in Docker, on a remote host, or over any TCP connection with no local unix socket.

Fixes #4

Root cause

MariaDBManager.create_user() existed at dea8638 with @'localhost' hardcoded, but was never wired into any code path — it was dead code. The actual user creation was always delegated to Frappe's new-site, which also creates @'localhost' unconditionally. The result is a user that cannot connect over TCP.

Solution

  • Added MariaDBManager._grant_host(): returns '%' when no unix socket is detected (TCP mode), 'localhost' when a socket is present.
  • Added MariaDBManager.create_user(): uses the mysql/mariadb CLI (zero new dependencies — MariaDB is already installed) to run CREATE USER IF NOT EXISTS and GRANT with the correct host scope.
  • Wired into Site.create(): after frappe new-site completes, reads the generated site_config.json for db_name and db_password, then calls create_user() to ensure the user exists with @'%' scope for TCP connections. Skipped entirely when a unix socket is in use.

Testing

Added 12 unit tests in tests/test_mariadb_manager.py:

  • _grant_host() returns '%' for TCP, 'localhost' for socket
  • create_user() constructs correct SQL with right host scope for TCP and socket
  • create_user() prefers mariadb binary, falls back to mysql
  • create_user() passes correct -h, -P, --socket flags
  • site.create() calls create_user() after frappe new-site with credentials from site_config.json
  • site.create() skips create_user() when socket is in use
  • site.create() skips create_user() when site_config.json is missing

All 12 new tests pass. No regressions in existing tests.

only when a unix socket is detected — in MySQL's privilege tables
'localhost' matches socket connections exclusively.
"""
return "localhost" if self._detect_socket() else "%"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why implicit?

Copy link
Copy Markdown
Author

@amitascra amitascra May 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right to question this i did some more digging. After reviewing Frappe's new-site code, I realized the current approach is unnecessarily implicit.

Frappe already has a --mariadb-user-host-login-scope option specifically for this purpose:

@click.option( "--mariadb-user-host-login-scope", help=( "Set the mariadb host for the user login scope if you don't want to use the current host as login " "scope which typically is ''@'localhost' - may be used when initializing a user on a remote host." ), )

The post-processing approach with create_user() was implicit because I was trying to work around what I thought was a Frappe limitation, but Frappe already provides the right mechanism.

"""
return "localhost" if self._detect_socket() else "%"

def create_user(self, username: str, password: str, db_name: str) -> None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bench new-site is managed by frappe. how is this managed in the old bench?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Old bench never managed this at all — it always delegated to Frappe completely, and users had to work around the @'localhost' issue manually (e.g., by creating sites locally then dumping/restoring to remote DBs, as seen in frappe/bench#1513).

The correct fix is to use Frappe's built-in --mariadb-user-host-login-scope='%' option when calling frappe new-site for TCP connections, rather than doing post-processing.

I'll update the PR to:

Remove the implicit create_user() post-processing from site.create()

  1. Add --mariadb-user-host-login-scope='%' to the frappe new-site command when no socket is detected
  2. This makes the fix explicit and uses Frappe's intended mechanism
  3. Thanks for the review — this is a much cleaner approach.

amitascra added 2 commits June 1, 2026 01:08
Frappe's new-site always creates the site DB user as @'localhost'.
In MySQL's privilege tables 'localhost' matches only unix socket
connections — TCP connections from 127.0.0.1 are rejected even on
the same host, breaking bench when MariaDB is running in Docker or
over any remote TCP connection.

Fix:
- Add MariaDBManager._grant_host(): returns '%' when no unix socket
  is detected (TCP mode), 'localhost' when a socket is present.
- Add MariaDBManager.create_user(): uses the mysql/mariadb CLI (zero
  new dependencies) to run CREATE USER IF NOT EXISTS and GRANT with
  the correct host scope.
- Wire create_user() into Site.create(): after frappe new-site
  completes, read the generated site_config.json for db_name and
  db_password, then call create_user() to ensure the user exists
  with @'%' scope for TCP connections.

Fixes frappe#4
Use Frappe's built-in --mariadb-user-host-login-scope option instead
of post-processing with create_user(). This is cleaner and more explicit.

Changes:
- Pass --mariadb-user-host-login-scope='%' when no unix socket detected
- Remove implicit create_user() post-processing from site.create()
- Update tests to verify the flag is passed correctly
- Add research-folder/ to .gitignore

Addresses review feedback from @rmehta on PR frappe#13
@amitascra amitascra force-pushed the fix/mariadb-create-user-host-scope branch from e919f9f to 21ba004 Compare May 31, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MariaDBManager.create_user() grants only on @'localhost', breaks TCP connections to MariaDB

2 participants