From f00188cf1b689112a14ed303168b681b2a8da354 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Mon, 28 Sep 2026 19:21:52 +0530 Subject: [PATCH 1/2] fix(create): roll back only the database and user this create made `ee site create --type=php --with-db` dropped another site's database or user when its create failed: `--dbname=` (even `--dbname=mysql`), `--dbuser=`, or the default name `str_replace(['.','-'],'_',site)` colliding (`a-b.test`, `a.b.test`, `a_b.test`). A failed `--ssl=custom` check also removed a pre-existing webroot. - The rollback (`catch_clean()` and the signal handler) now drops the database and user only when this run created them on the global db. - The global db names go through site-command's `reserve_global_db_names()` before anything is created: a taken `--dbname`/`--dbuser` is refused, a taken default name gets a numeric suffix, and the default is cut to 64 characters. - `check_site_name_conflicts()` refuses a site whose webroot already exists or whose docker volumes or compose project another site uses, before the database step. - Level 0 lasts until the webroot exists, so an early failure keeps a directory this run did not create. Needs the site-command functions of the same change. --- src/PHP.php | 49 +++++++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/PHP.php b/src/PHP.php index 3e71c95..9b3d648 100644 --- a/src/PHP.php +++ b/src/PHP.php @@ -53,6 +53,11 @@ class PHP extends EE_Site_Command { */ private $force; + /** + * @var bool $created_global_db Whether this run created the site's database and user on the global db. + */ + private $created_global_db = false; + public function __construct() { parent::__construct(); @@ -242,6 +247,8 @@ public function create( $args, $assoc_args ) { \EE::confirm( sprintf( 'EEv4 does not support PHP %s. Continue with PHP %s?', $old_version, $this->site_data['php_version'] ) ); } + \EE\Site\Utils\check_site_name_conflicts( $this->site_data['site_url'], $this->site_data['site_fs_path'] ); + if ( $this->cache_type && ! $local_cache ) { \EE\Service\Utils\init_global_container( GLOBAL_REDIS ); } @@ -251,10 +258,7 @@ public function create( $args, $assoc_args ) { $this->site_data['db_host'] = ''; if ( ! empty( $assoc_args['with-db'] ) ) { $this->site_data['app_sub_type'] = 'mysql'; - $this->site_data['db_name'] = \EE\Utils\get_flag_value( $assoc_args, 'dbname', str_replace( [ - '.', - '-' - ], '_', $this->site_data['site_url'] ) ); + $this->site_data['db_name'] = \EE\Utils\get_flag_value( $assoc_args, 'dbname', \EE\Site\Utils\get_default_db_name( $this->site_data['site_url'] ) ); $this->site_data['db_host'] = \EE\Utils\get_flag_value( $assoc_args, 'dbhost', GLOBAL_DB ); $this->site_data['db_port'] = '3306'; $this->site_data['db_user'] = \EE\Utils\get_flag_value( $assoc_args, 'dbuser', $this->create_site_db_user( $this->site_data['site_url'] ) ); @@ -272,11 +276,13 @@ public function create( $args, $assoc_args ) { if ( GLOBAL_DB === $this->site_data['db_host'] ) { \EE\Service\Utils\init_global_container( GLOBAL_DB ); + $this->site_data['db_name'] = \EE\Site\Utils\reserve_global_db_names( $this->site_data['db_name'], $this->site_data['db_user'], ! empty( $assoc_args['dbname'] ) ); try { $user_data = \EE\Site\Utils\create_user_in_db( GLOBAL_DB, $this->site_data['db_name'], $this->site_data['db_user'], $this->site_data['db_password'] ); if ( ! $user_data ) { throw new \Exception( sprintf( 'Could not create user %s. Please check logs.', $this->site_data['db_user'] ) ); } + $this->created_global_db = true; } catch ( \Exception $e ) { $this->catch_clean( $e ); } @@ -792,7 +798,6 @@ private function maybe_verify_remote_db_connection() { */ private function create_site( $assoc_args ) { - $this->level = 1; try { if ( 'inherit' === $this->site_data['site_ssl'] ) { $this->check_parent_site_certs( $this->site_data['site_url'] ); @@ -950,6 +955,24 @@ public function reload( $args, $assoc_args, $whitelisted_containers = [], $reloa } + /** + * Database and user a failed create may drop: only the ones this run created. + * + * @return array + */ + private function get_rollback_db_data() { + + if ( ! $this->created_global_db ) { + return []; + } + + return [ + 'db_host' => $this->site_data['db_host'], + 'db_user' => $this->site_data['db_user'], + 'db_name' => $this->site_data['db_name'], + ]; + } + /** * Catch and clean exceptions. * @@ -959,12 +982,7 @@ private function catch_clean( $e ) { \EE\Utils\delem_log( 'site cleanup start' ); \EE::warning( $e->getMessage() ); \EE::warning( 'Initiating clean-up.' ); - $db_data = ( empty( $this->site_data['db_host'] ) || 'db' === $this->site_data['db_host'] ) ? [] : [ - 'db_host' => $this->site_data['db_host'], - 'db_user' => $this->site_data['db_user'], - 'db_name' => $this->site_data['db_name'], - ]; - $this->delete_site( $this->level, $this->site_data['site_url'], $this->site_data['site_fs_path'], $db_data ); + $this->delete_site( $this->level, $this->site_data['site_url'], $this->site_data['site_fs_path'], $this->get_rollback_db_data() ); \EE\Utils\delem_log( 'site cleanup end' ); \EE::log( 'Report bugs here: https://github.com/EasyEngine/site-type-php' ); exit( 1 ); @@ -975,13 +993,8 @@ private function catch_clean( $e ) { */ protected function rollback() { \EE::warning( 'Exiting gracefully after rolling back. This may take some time.' ); - if ( $this->level > 0 ) { - $db_data = ( empty( $this->site_data['db_host'] ) || 'db' === $this->site_data['db_host'] ) ? [] : [ - 'db_host' => $this->site_data['db_host'], - 'db_user' => $this->site_data['db_user'], - 'db_name' => $this->site_data['db_name'], - ]; - $this->delete_site( $this->level, $this->site_data['site_url'], $this->site_data['site_fs_path'], $db_data ); + if ( $this->level > 0 || $this->created_global_db ) { + $this->delete_site( $this->level, $this->site_data['site_url'], $this->site_data['site_fs_path'], $this->get_rollback_db_data() ); } \EE::success( 'Rollback complete. Exiting now.' ); exit( 1 ); From 7ad0e991f411481237a55697a2f59f4d9a4bc997 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Mon, 28 Sep 2026 20:26:44 +0530 Subject: [PATCH 2/2] fix(create): keep the rollback level and database flag right when a signal interrupts a step Signals are handled only between statements of the site-type file, never inside the site-command helpers it calls. So a signal during `create_user_in_db()` was handled before `$this->created_global_db = true` ran, and the rollback left the new database and user behind. The flag is now set in the same statement as the call. For the same reason the level is raised to 1 just before `create_site_root()`, not after it. At level 0 an interrupted create, or a failed `chown` inside it, would keep the webroot it had just made, and every retry would then be refused with "Webroot directory already exists". --- src/PHP.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/PHP.php b/src/PHP.php index 9b3d648..61b7e1c 100644 --- a/src/PHP.php +++ b/src/PHP.php @@ -278,11 +278,11 @@ public function create( $args, $assoc_args ) { \EE\Service\Utils\init_global_container( GLOBAL_DB ); $this->site_data['db_name'] = \EE\Site\Utils\reserve_global_db_names( $this->site_data['db_name'], $this->site_data['db_user'], ! empty( $assoc_args['dbname'] ) ); try { - $user_data = \EE\Site\Utils\create_user_in_db( GLOBAL_DB, $this->site_data['db_name'], $this->site_data['db_user'], $this->site_data['db_password'] ); - if ( ! $user_data ) { + // One statement: a signal is only handled after it, so rollback() always sees a created database. + $this->created_global_db = false !== ( $user_data = \EE\Site\Utils\create_user_in_db( GLOBAL_DB, $this->site_data['db_name'], $this->site_data['db_user'], $this->site_data['db_password'] ) ); + if ( ! $this->created_global_db ) { throw new \Exception( sprintf( 'Could not create user %s. Please check logs.', $this->site_data['db_user'] ) ); } - $this->created_global_db = true; } catch ( \Exception $e ) { $this->catch_clean( $e ); } @@ -803,6 +803,8 @@ private function create_site( $assoc_args ) { $this->check_parent_site_certs( $this->site_data['site_url'] ); } + // Raised before the call: a signal during it is only handled once it returns. + $this->level = 1; \EE\Site\Utils\create_site_root( $this->site_data['site_fs_path'], $this->site_data['site_url'] ); $this->level = 2;