Skip to content

Commit 3ff6c0a

Browse files
committed
Use --skip-grant-tables to reset MySQL root password
When the data directory already exists (e.g. pre-installed MySQL on GitHub Actions), the root password is unknown. Neither blank password, auth_socket, nor sudo can authenticate. Fix by starting MySQL with --skip-grant-tables when an existing data directory is detected, resetting the root password, then restarting normally. https://claude.ai/code/session_01CsyRwSkRxBcQoaQFVkMQsJ
1 parent ce30b9a commit 3ff6c0a

File tree

1 file changed

+60
-27
lines changed

1 file changed

+60
-27
lines changed

cmd/sqlc-test-setup/main.go

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -290,62 +290,95 @@ func startMySQL() error {
290290
log.Println("stopping any existing mysql service")
291291
_ = exec.Command("sudo", "service", "mysql", "stop").Run()
292292
_ = exec.Command("sudo", "mysqladmin", "shutdown").Run()
293+
// Give MySQL time to fully shut down
294+
time.Sleep(2 * time.Second)
295+
296+
if err := ensureMySQLDirs(); err != nil {
297+
return err
298+
}
293299

294300
// Check if data directory already exists and has been initialized
301+
needsPasswordReset := false
295302
if mysqlInitialized() {
296303
log.Println("mysql data directory already initialized, skipping initialization")
304+
// Existing data dir may have an unknown root password (e.g. pre-installed
305+
// MySQL on GitHub Actions). We'll need to use --skip-grant-tables to reset it.
306+
needsPasswordReset = true
297307
} else {
298308
log.Println("initializing mysql data directory")
299309
if err := run("sudo", "mysqld", "--initialize-insecure", "--user=mysql"); err != nil {
300310
return fmt.Errorf("mysqld --initialize-insecure: %w", err)
301311
}
302312
}
303313

304-
// Ensure the run directory exists for the socket/pid file
314+
if needsPasswordReset {
315+
// Start with --skip-grant-tables to reset the unknown root password.
316+
if err := startMySQLDaemon("--skip-grant-tables"); err != nil {
317+
return err
318+
}
319+
320+
log.Println("resetting root password via --skip-grant-tables")
321+
resetSQL := "FLUSH PRIVILEGES; ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'mysecretpassword';"
322+
if err := run("mysql", "-u", "root", "-e", resetSQL); err != nil {
323+
return fmt.Errorf("resetting mysql root password: %w", err)
324+
}
325+
326+
// Restart without --skip-grant-tables
327+
log.Println("restarting mysql normally")
328+
if err := run("sudo", "mysqladmin", "-u", "root", "-pmysecretpassword", "shutdown"); err != nil {
329+
// If mysqladmin fails, try killing the process directly
330+
_ = run("sudo", "pkill", "-f", "mysqld")
331+
}
332+
time.Sleep(2 * time.Second)
333+
334+
if err := startMySQLDaemon(); err != nil {
335+
return err
336+
}
337+
} else {
338+
// Fresh initialization — start normally and set password
339+
if err := startMySQLDaemon(); err != nil {
340+
return err
341+
}
342+
343+
log.Println("setting mysql root password")
344+
alterSQL := "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'mysecretpassword'; FLUSH PRIVILEGES;"
345+
if err := run("mysql", "-u", "root", "-e", alterSQL); err != nil {
346+
return fmt.Errorf("setting mysql root password: %w", err)
347+
}
348+
}
349+
350+
return verifyMySQL()
351+
}
352+
353+
// ensureMySQLDirs creates the directories MySQL needs at runtime.
354+
func ensureMySQLDirs() error {
305355
if err := run("sudo", "mkdir", "-p", "/var/run/mysqld"); err != nil {
306356
return fmt.Errorf("creating /var/run/mysqld: %w", err)
307357
}
308358
if err := run("sudo", "chown", "mysql:mysql", "/var/run/mysqld"); err != nil {
309359
return fmt.Errorf("chowning /var/run/mysqld: %w", err)
310360
}
361+
return nil
362+
}
311363

312-
log.Println("starting mysql via mysqld_safe")
313-
// mysqld_safe runs in the foreground, so we launch it in the background
314-
cmd := exec.Command("sudo", "mysqld_safe", "--user=mysql")
364+
// startMySQLDaemon starts mysqld_safe in the background and waits for it to
365+
// accept connections. Extra args (e.g. "--skip-grant-tables") are appended.
366+
func startMySQLDaemon(extraArgs ...string) error {
367+
args := append([]string{"mysqld_safe", "--user=mysql"}, extraArgs...)
368+
log.Printf("starting mysql via mysqld_safe %v", extraArgs)
369+
cmd := exec.Command("sudo", args...)
315370
cmd.Stdout = os.Stderr
316371
cmd.Stderr = os.Stderr
317372
if err := cmd.Start(); err != nil {
318373
return fmt.Errorf("starting mysqld_safe: %w", err)
319374
}
320375

321-
// Wait for MySQL to become ready
322376
log.Println("waiting for mysql to accept connections")
323377
if err := waitForMySQL(30 * time.Second); err != nil {
324378
return fmt.Errorf("mysql did not start in time: %w", err)
325379
}
326380
log.Println("mysql is accepting connections")
327-
328-
// Set root password.
329-
// The debconf-based install may configure auth_socket plugin which only
330-
// works via Unix socket. We need caching_sha2_password for TCP access.
331-
log.Println("configuring mysql root password for TCP access")
332-
if err := run("mysql", "-h", "127.0.0.1", "-u", "root", "-pmysecretpassword", "-e", "SELECT 1;"); err == nil {
333-
log.Println("mysql root password already set to expected value, skipping")
334-
} else {
335-
log.Println("setting mysql root password with caching_sha2_password plugin")
336-
alterSQL := "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'mysecretpassword'; FLUSH PRIVILEGES;"
337-
// Try via socket without sudo (works when password is blank)
338-
if err := run("mysql", "-u", "root", "-e", alterSQL); err != nil {
339-
// Try via socket with sudo (needed when auth_socket plugin is
340-
// active, since it requires the OS user to match the MySQL user)
341-
log.Println("retrying with sudo (auth_socket requires OS root user)")
342-
if err := run("sudo", "mysql", "-u", "root", "-e", alterSQL); err != nil {
343-
return fmt.Errorf("setting mysql root password: %w", err)
344-
}
345-
}
346-
}
347-
348-
return verifyMySQL()
381+
return nil
349382
}
350383

351384
// mysqlReady checks if MySQL is running and accepting connections with the expected password.

0 commit comments

Comments
 (0)