From c2414937725ee361f2e9c8604792e2bde6fa4f9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Sep 2026 08:33:34 +0000 Subject: [PATCH 1/2] Initial plan From e55de209c13d9a83b210e79a35044d2c46e1f206 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Sep 2026 08:45:15 +0000 Subject: [PATCH 2/2] =?UTF-8?q?Fix=20db=20commands=20failing=20on=20MariaD?= =?UTF-8?q?B=2011.x=20=E2=80=94=20use=20mariadb/mariadb-dump/mariadb-admin?= =?UTF-8?q?=20binaries=20(#122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: peterjaap <431360+peterjaap@users.noreply.github.com> --- cmd/magebox/check.go | 13 ++++++--- cmd/magebox/db.go | 50 ++++++++++++++++++++++++--------- internal/docker/compose.go | 31 +++++++++++++++++--- internal/docker/compose_test.go | 47 +++++++++++++++++++++++++++++++ internal/project/lifecycle.go | 12 +++++--- 5 files changed, 128 insertions(+), 25 deletions(-) diff --git a/cmd/magebox/check.go b/cmd/magebox/check.go index 1efb27c..de0f3da 100644 --- a/cmd/magebox/check.go +++ b/cmd/magebox/check.go @@ -631,17 +631,22 @@ func checkDatabaseConnection(p *platform.Platform, cfg *config.Config) checkResu var serviceName string var port string + var dbType, dbVersion string if cfg.Services.HasMySQL() { - serviceName = fmt.Sprintf("mysql%s", strings.ReplaceAll(cfg.Services.MySQL.Version, ".", "")) - switch cfg.Services.MySQL.Version { + dbType = "mysql" + dbVersion = cfg.Services.MySQL.Version + serviceName = fmt.Sprintf("mysql%s", strings.ReplaceAll(dbVersion, ".", "")) + switch dbVersion { case "8.4": port = "33084" default: port = "33080" } } else if cfg.Services.HasMariaDB() { - serviceName = fmt.Sprintf("mariadb%s", strings.ReplaceAll(cfg.Services.MariaDB.Version, ".", "")) + dbType = "mariadb" + dbVersion = cfg.Services.MariaDB.Version + serviceName = fmt.Sprintf("mariadb%s", strings.ReplaceAll(dbVersion, ".", "")) port = "33106" } @@ -658,7 +663,7 @@ func checkDatabaseConnection(p *platform.Platform, cfg *config.Config) checkResu // Check if database exists dockerCtrl := docker.NewDockerController(composeFile) - if dockerCtrl.DatabaseExists(serviceName, cfg.Name) { + if dockerCtrl.DatabaseExists(serviceName, cfg.Name, docker.DBClientBin(dbType, dbVersion)) { return checkResult{ name: "Database", status: "ok", diff --git a/cmd/magebox/db.go b/cmd/magebox/db.go index b1e362e..287d088 100644 --- a/cmd/magebox/db.go +++ b/cmd/magebox/db.go @@ -140,6 +140,30 @@ type dbInfo struct { Port int // e.g., 33080 } +// isMariaDB11Plus returns true when this is a MariaDB 11.0 or newer container, +// which removed the mysql/mysqldump/mysqladmin symlinks. +func (d *dbInfo) isMariaDB11Plus() bool { + return docker.DBClientBin(d.Type, d.Version) == "mariadb" +} + +// clientBin returns the SQL client binary name for this container. +func (d *dbInfo) clientBin() string { + return docker.DBClientBin(d.Type, d.Version) +} + +// dumpBin returns the dump binary name for this container. +func (d *dbInfo) dumpBin() string { + return docker.DBDumpBin(d.Type, d.Version) +} + +// adminBin returns the admin binary name for this container. +func (d *dbInfo) adminBin() string { + if d.isMariaDB11Plus() { + return "mariadb-admin" + } + return "mysqladmin" +} + // getDbInfo extracts database connection info from project config func getDbInfo(cfg *config.Config) (*dbInfo, error) { if cfg.Services.MySQL != nil && cfg.Services.MySQL.Enabled { @@ -216,7 +240,7 @@ func runDbImport(cmd *cobra.Command, args []string) error { // Create database if it doesn't exist createCmd := exec.Command("docker", "exec", db.ContainerName, - "mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", + db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", dbName)) createCmd.Stderr = os.Stderr if err := createCmd.Run(); err != nil { @@ -242,7 +266,7 @@ func runDbImport(cmd *cobra.Command, args []string) error { // Use docker exec directly with container name importCmd := exec.Command("docker", "exec", "-i", db.ContainerName, - "mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, dbName) + db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, dbName) // Handle gzip compressed files if strings.HasSuffix(sqlFile, ".gz") { @@ -311,7 +335,7 @@ func runDbExport(cmd *cobra.Command, args []string) error { // Use docker exec directly with container name // --no-tablespaces: Skip TABLESPACE statements (avoids permission issues on import) exportCmd := exec.Command("docker", "exec", db.ContainerName, - "mysqldump", "-uroot", "-p"+docker.DefaultDBRootPassword, "--no-tablespaces", dbName) + db.dumpBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "--no-tablespaces", dbName) file, err := os.Create(outputFile) if err != nil { @@ -352,7 +376,7 @@ func runDbShell(cmd *cobra.Command, args []string) error { // Use docker exec directly with container name shellCmd := exec.Command("docker", "exec", "-it", db.ContainerName, - "mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, dbName) + db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, dbName) shellCmd.Stdin = os.Stdin shellCmd.Stdout = os.Stdout shellCmd.Stderr = os.Stderr @@ -385,7 +409,7 @@ func runDbCreate(cmd *cobra.Command, args []string) error { // Check if database already exists checkCmd := exec.Command("docker", "exec", db.ContainerName, - "mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", + db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", fmt.Sprintf("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '%s'", dbName)) output, err := checkCmd.Output() if err == nil && strings.Contains(string(output), dbName) { @@ -396,7 +420,7 @@ func runDbCreate(cmd *cobra.Command, args []string) error { // Create database fmt.Print("Creating database... ") createCmd := exec.Command("docker", "exec", db.ContainerName, - "mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", + db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", dbName)) createCmd.Stderr = os.Stderr @@ -447,7 +471,7 @@ func runDbDrop(cmd *cobra.Command, args []string) error { fmt.Println() fmt.Print("Dropping database... ") dropCmd := exec.Command("docker", "exec", db.ContainerName, - "mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", + db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", fmt.Sprintf("DROP DATABASE IF EXISTS `%s`", dbName)) dropCmd.Stderr = os.Stderr @@ -500,7 +524,7 @@ func runDbReset(cmd *cobra.Command, args []string) error { // Drop database fmt.Print("Dropping database... ") dropCmd := exec.Command("docker", "exec", db.ContainerName, - "mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", + db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", fmt.Sprintf("DROP DATABASE IF EXISTS `%s`", dbName)) dropCmd.Stderr = os.Stderr @@ -513,7 +537,7 @@ func runDbReset(cmd *cobra.Command, args []string) error { // Create database fmt.Print("Creating database... ") createCmd := exec.Command("docker", "exec", db.ContainerName, - "mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", + db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", fmt.Sprintf("CREATE DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", dbName)) createCmd.Stderr = os.Stderr @@ -568,7 +592,7 @@ func runDbTop(cmd *cobra.Command, args []string) error { fmt.Printf("Monitoring %s (Ctrl+C to stop)\n\n", cli.Highlight(db.ContainerName)) topCmd := exec.Command("docker", "exec", "-it", db.ContainerName, - "mysqladmin", "-uroot", "-p"+docker.DefaultDBRootPassword, + db.adminBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "processlist", "--sleep=2", "--verbose") topCmd.Stdin = os.Stdin topCmd.Stdout = os.Stdout @@ -640,7 +664,7 @@ func runDbSnapshotCreate(cmd *cobra.Command, args []string) error { // Create gzipped dump dumpCmd := exec.Command("docker", "exec", db.ContainerName, - "mysqldump", "-uroot", "-p"+docker.DefaultDBRootPassword, + db.dumpBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "--no-tablespaces", "--single-transaction", dbName) // Create output file with gzip compression @@ -728,7 +752,7 @@ func runDbSnapshotRestore(cmd *cobra.Command, args []string) error { // Drop and recreate database fmt.Print("Resetting database... ") resetCmd := exec.Command("docker", "exec", db.ContainerName, - "mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", + db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e", fmt.Sprintf("DROP DATABASE IF EXISTS `%s`; CREATE DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", dbName, dbName)) resetCmd.Stderr = os.Stderr if err := resetCmd.Run(); err != nil { @@ -757,7 +781,7 @@ func runDbSnapshotRestore(cmd *cobra.Command, args []string) error { // Import into database importCmd := exec.Command("docker", "exec", "-i", db.ContainerName, - "mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, dbName) + db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, dbName) importCmd.Stdin = gzReader importCmd.Stderr = os.Stderr diff --git a/internal/docker/compose.go b/internal/docker/compose.go index 316f95e..9e4b57f 100644 --- a/internal/docker/compose.go +++ b/internal/docker/compose.go @@ -118,6 +118,29 @@ const ( DefaultRabbitMQPass = "guest" ) +// DBClientBin returns the SQL client binary name for the given database type and version. +// MariaDB 11.0+ removed the mysql/mysqldump/mysqladmin symlinks; use the native binary names instead. +func DBClientBin(dbType, version string) string { + if dbType == "mariadb" { + parts := strings.SplitN(version, ".", 2) + if major, err := strconv.Atoi(parts[0]); err == nil && major >= 11 { + return "mariadb" + } + } + return "mysql" +} + +// DBDumpBin returns the dump binary name for the given database type and version. +func DBDumpBin(dbType, version string) string { + if dbType == "mariadb" { + parts := strings.SplitN(version, ".", 2) + if major, err := strconv.Atoi(parts[0]); err == nil && major >= 11 { + return "mariadb-dump" + } + } + return "mysqldump" +} + // ComposeGenerator generates Docker Compose configurations for global services type ComposeGenerator struct { platform *platform.Platform @@ -1011,16 +1034,16 @@ func (c *DockerController) ExecSilent(serviceName string, command ...string) err } // CreateDatabase creates a database in the MySQL/MariaDB service -func (c *DockerController) CreateDatabase(serviceName, dbName string) error { +func (c *DockerController) CreateDatabase(serviceName, dbName, clientBin string) error { cmd := buildComposeCmd(c.composeFile, "exec", "-T", serviceName, - "mysql", "-uroot", "-p"+DefaultDBRootPassword, "-e", fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s`", dbName)) + clientBin, "-uroot", "-p"+DefaultDBRootPassword, "-e", fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s`", dbName)) return cmd.Run() } // DatabaseExists checks if a database exists -func (c *DockerController) DatabaseExists(serviceName, dbName string) bool { +func (c *DockerController) DatabaseExists(serviceName, dbName, clientBin string) bool { cmd := buildComposeCmd(c.composeFile, "exec", "-T", serviceName, - "mysql", "-uroot", "-p"+DefaultDBRootPassword, "-e", fmt.Sprintf("SHOW DATABASES LIKE '%s'", dbName)) + clientBin, "-uroot", "-p"+DefaultDBRootPassword, "-e", fmt.Sprintf("SHOW DATABASES LIKE '%s'", dbName)) output, err := cmd.Output() return err == nil && strings.Contains(string(output), dbName) } diff --git a/internal/docker/compose_test.go b/internal/docker/compose_test.go index 77ea444..337e74c 100644 --- a/internal/docker/compose_test.go +++ b/internal/docker/compose_test.go @@ -1391,3 +1391,50 @@ func TestGenerateGlobalServices_SharedSearchContainer(t *testing.T) { t.Errorf("elasticsearch Ports = %v, want [9500:9200]", es.Ports) } } + +func TestDBClientBin(t *testing.T) { + tests := []struct { + dbType string + version string + want string + }{ + {"mysql", "8.0", "mysql"}, + {"mysql", "8.4", "mysql"}, + {"mysql", "5.7", "mysql"}, + {"mariadb", "10.4", "mysql"}, + {"mariadb", "10.6", "mysql"}, + {"mariadb", "10.11", "mysql"}, + {"mariadb", "11.0", "mariadb"}, + {"mariadb", "11.4", "mariadb"}, + {"mariadb", "12.0", "mariadb"}, + } + for _, tt := range tests { + t.Run(tt.dbType+"-"+tt.version, func(t *testing.T) { + got := DBClientBin(tt.dbType, tt.version) + if got != tt.want { + t.Errorf("DBClientBin(%q, %q) = %q, want %q", tt.dbType, tt.version, got, tt.want) + } + }) + } +} + +func TestDBDumpBin(t *testing.T) { + tests := []struct { + dbType string + version string + want string + }{ + {"mysql", "8.0", "mysqldump"}, + {"mariadb", "10.6", "mysqldump"}, + {"mariadb", "11.0", "mariadb-dump"}, + {"mariadb", "11.4", "mariadb-dump"}, + } + for _, tt := range tests { + t.Run(tt.dbType+"-"+tt.version, func(t *testing.T) { + got := DBDumpBin(tt.dbType, tt.version) + if got != tt.want { + t.Errorf("DBDumpBin(%q, %q) = %q, want %q", tt.dbType, tt.version, got, tt.want) + } + }) + } +} diff --git a/internal/project/lifecycle.go b/internal/project/lifecycle.go index 3e1840a..33ba775 100644 --- a/internal/project/lifecycle.go +++ b/internal/project/lifecycle.go @@ -518,11 +518,15 @@ func (m *Manager) ensureDatabase(cfg *config.Config) error { dockerController := docker.NewDockerController(m.composeGen.ComposeFilePath()) // Determine service name (version dots are removed in docker-compose service names) - var serviceName string + var serviceName, dbType, dbVersion string if cfg.Services.HasMySQL() { - serviceName = fmt.Sprintf("mysql%s", strings.ReplaceAll(cfg.Services.MySQL.Version, ".", "")) + dbType = "mysql" + dbVersion = cfg.Services.MySQL.Version + serviceName = fmt.Sprintf("mysql%s", strings.ReplaceAll(dbVersion, ".", "")) } else if cfg.Services.HasMariaDB() { - serviceName = fmt.Sprintf("mariadb%s", strings.ReplaceAll(cfg.Services.MariaDB.Version, ".", "")) + dbType = "mariadb" + dbVersion = cfg.Services.MariaDB.Version + serviceName = fmt.Sprintf("mariadb%s", strings.ReplaceAll(dbVersion, ".", "")) } if serviceName == "" { @@ -535,7 +539,7 @@ func (m *Manager) ensureDatabase(cfg *config.Config) error { } // Create database (use sanitized name - hyphens replaced with underscores) - return dockerController.CreateDatabase(serviceName, cfg.DatabaseName()) + return dockerController.CreateDatabase(serviceName, cfg.DatabaseName(), docker.DBClientBin(dbType, dbVersion)) } // getStartedServices returns a list of started service names