From 68ce6e8d4617098257b8a47a577a0d4faac0971f Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 10:48:34 +0530 Subject: [PATCH 01/18] feat(cli): configure logrotate during migration --- php/EE/Logrotate/Utils.php | 192 +++++++++++++++++++++++++++++++++++++ php/EE/Runner.php | 1 + 2 files changed, 193 insertions(+) create mode 100644 php/EE/Logrotate/Utils.php diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php new file mode 100644 index 000000000..0f575f3f2 --- /dev/null +++ b/php/EE/Logrotate/Utils.php @@ -0,0 +1,192 @@ +return_code || ! self::command_exists( 'logrotate' ) ) { + \EE::warning( 'Unable to install logrotate. Skipping EasyEngine logrotate setup.' ); + return false; + } + + return true; + } + + /** + * Check whether a command is available in PATH. + * + * @param string $command Command name. + * @return bool + */ + private static function command_exists( $command ) { + + $result = \EE::launch( 'command -v ' . escapeshellarg( $command ) . ' >/dev/null 2>&1', false, true ); + + return 0 === $result->return_code; + } + + /** + * Write a logrotate config if it is missing or stale. + * + * @param string $file Config file path. + * @param string $content Config content. + * @return bool + */ + private static function write_config( $file, $content ) { + + if ( file_exists( $file ) && $content === @file_get_contents( $file ) ) { + return true; + } + + if ( false === @file_put_contents( $file, $content ) ) { + \EE::warning( 'Unable to write EasyEngine logrotate config: ' . $file ); + return false; + } + + @chmod( $file, 0644 ); + + return true; + } + + /** + * Remove old per-site/proxy configs that duplicate the wildcard configs. + */ + private static function cleanup_legacy_configs() { + + $files = glob( self::CONFIG_DIR . '/ee_*' ); + + if ( false === $files ) { + return; + } + + foreach ( $files as $file ) { + $basename = basename( $file ); + + if ( in_array( $basename, [ 'ee_sites', 'ee_nginx_proxy' ], true ) || ! is_file( $file ) ) { + continue; + } + + $contents = @file_get_contents( $file ); + + if ( false === $contents || ! self::is_legacy_config( $contents ) ) { + continue; + } + + if ( ! @unlink( $file ) ) { + \EE::warning( 'Unable to remove legacy EasyEngine logrotate config: ' . $file ); + } + } + } + + /** + * Check whether a config duplicates EasyEngine log rotation. + * + * @param string $contents Config contents. + * @return bool + */ + private static function is_legacy_config( $contents ) { + + return false !== strpos( $contents, '/opt/easyengine/sites/' ) + || false !== strpos( $contents, '/opt/easyengine/services/nginx-proxy/logs/*.log' ); + } + + /** + * Return the site logs config. + * + * @return string + */ + private static function get_sites_config() { + + return <<<'LOGROTATE' +/opt/easyengine/sites/*/logs/nginx/*.log +/opt/easyengine/sites/*/logs/php/*.log { + daily + missingok + rotate 30 + compress + delaycompress + notifempty + copytruncate + create 0640 www-data adm + sharedscripts +} +LOGROTATE + . PHP_EOL; + } + + /** + * Return the nginx-proxy logs config. + * + * @return string + */ + private static function get_nginx_proxy_config() { + + return <<<'LOGROTATE' +/opt/easyengine/services/nginx-proxy/logs/*.log { + daily + missingok + rotate 30 + compress + delaycompress + notifempty + copytruncate + create 0640 www-data adm + sharedscripts +} +LOGROTATE + . PHP_EOL; + } +} diff --git a/php/EE/Runner.php b/php/EE/Runner.php index cc42b2f64..8a686e9f3 100644 --- a/php/EE/Runner.php +++ b/php/EE/Runner.php @@ -174,6 +174,7 @@ private function migrate() { $rsp->add_step( 'ee-docker-image-migrations', 'EE\Migration\Containers::start_container_migration' ); $rsp->add_step( 'ee-update-docker-compose', 'EE\Migration\Containers::update_docker_compose' ); $rsp->add_step( 'ee-update-cron-config', 'EE\Cron\Utils\update_cron_config' ); + $rsp->add_step( 'ee-setup-logrotate', 'EE\Logrotate\Utils::setup_logrotate' ); return $rsp->execute(); } From 8a9a35879be8d325cf49a7b695eeb57080b06a39 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 10:52:47 +0530 Subject: [PATCH 02/18] feat(cli): use configured root for logrotate paths --- php/EE/Logrotate/Utils.php | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 0f575f3f2..775ca0c2e 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -139,8 +139,10 @@ private static function cleanup_legacy_configs() { */ private static function is_legacy_config( $contents ) { - return false !== strpos( $contents, '/opt/easyengine/sites/' ) - || false !== strpos( $contents, '/opt/easyengine/services/nginx-proxy/logs/*.log' ); + $root_dir = rtrim( EE_ROOT_DIR, '/' ); + + return false !== strpos( $contents, $root_dir . '/sites/' ) + || false !== strpos( $contents, $root_dir . '/services/nginx-proxy/logs/*.log' ); } /** @@ -150,9 +152,11 @@ private static function is_legacy_config( $contents ) { */ private static function get_sites_config() { - return <<<'LOGROTATE' -/opt/easyengine/sites/*/logs/nginx/*.log -/opt/easyengine/sites/*/logs/php/*.log { + $root_dir = rtrim( EE_ROOT_DIR, '/' ); + + return << Date: Tue, 7 Jul 2026 10:53:25 +0530 Subject: [PATCH 03/18] feat(cli): keep logrotate setup non-fatal --- php/EE/Logrotate/Utils.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 775ca0c2e..614210f1b 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -16,6 +16,18 @@ class Utils { */ public static function setup_logrotate() { + try { + self::setup_logrotate_config(); + } catch ( \Throwable $e ) { + \EE::warning( 'Unable to setup EasyEngine logrotate config: ' . $e->getMessage() ); + } + } + + /** + * Configure logrotate for EasyEngine site and nginx-proxy logs. + */ + private static function setup_logrotate_config() { + if ( defined( 'IS_DARWIN' ) && IS_DARWIN ) { \EE::debug( 'Skipping logrotate setup on macOS.' ); return; From af7d643f8ee7bdf9935c6ce3f89f1b0cc9808771 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 10:54:13 +0530 Subject: [PATCH 04/18] feat(cli): narrow legacy logrotate cleanup --- php/EE/Logrotate/Utils.php | 79 +++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 614210f1b..e6bc54062 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -133,7 +133,7 @@ private static function cleanup_legacy_configs() { $contents = @file_get_contents( $file ); - if ( false === $contents || ! self::is_legacy_config( $contents ) ) { + if ( false === $contents || ! self::is_legacy_config( $basename, $contents ) ) { continue; } @@ -146,15 +146,84 @@ private static function cleanup_legacy_configs() { /** * Check whether a config duplicates EasyEngine log rotation. * + * @param string $basename Config file basename. * @param string $contents Config contents. * @return bool */ - private static function is_legacy_config( $contents ) { + private static function is_legacy_config( $basename, $contents ) { - $root_dir = rtrim( EE_ROOT_DIR, '/' ); + return self::is_legacy_site_config( $basename, $contents ) + || self::is_legacy_nginx_proxy_config( $basename, $contents ); + } + + /** + * Check whether a config matches old per-site generated configs. + * + * @param string $basename Config file basename. + * @param string $contents Config contents. + * @return bool + */ + private static function is_legacy_site_config( $basename, $contents ) { + + $site = substr( $basename, 3 ); + + if ( ! preg_match( '/^ee_[A-Za-z0-9.-]+$/', $basename ) || false === strpos( $site, '.' ) ) { + return false; + } + + foreach ( self::get_legacy_roots() as $root_dir ) { + $site_path = preg_quote( $root_dir . '/sites/' . $site, '/' ); + + if ( + preg_match( '/' . $site_path . '\/logs\/nginx\/\*\.log/', $contents ) + && preg_match( '/' . $site_path . '\/logs\/php\/\*\.log/', $contents ) + && false !== strpos( $contents, 'docker inspect -f' ) + ) { + return true; + } + } + + return false; + } + + /** + * Check whether a config matches old nginx-proxy generated config. + * + * @param string $basename Config file basename. + * @param string $contents Config contents. + * @return bool + */ + private static function is_legacy_nginx_proxy_config( $basename, $contents ) { + + if ( 'ee_nginx_proxy_logrotate' !== $basename ) { + return false; + } + + foreach ( self::get_legacy_roots() as $root_dir ) { + $proxy_path = $root_dir . '/services/nginx-proxy/logs/*.log'; + + if ( false !== strpos( $contents, $proxy_path ) ) { + return true; + } + } + + return false; + } + + /** + * Return root paths used by current and legacy EE logrotate configs. + * + * @return array + */ + private static function get_legacy_roots() { + + $roots = [ rtrim( EE_ROOT_DIR, '/' ) ]; + + if ( '/opt/easyengine' !== $roots[0] ) { + $roots[] = '/opt/easyengine'; + } - return false !== strpos( $contents, $root_dir . '/sites/' ) - || false !== strpos( $contents, $root_dir . '/services/nginx-proxy/logs/*.log' ); + return $roots; } /** From 021898e9d397f3c5f8c4d781b007ebb0c8736038 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 10:54:39 +0530 Subject: [PATCH 05/18] feat(cli): preserve log ownership during rotation --- php/EE/Logrotate/Utils.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index e6bc54062..471d67ac0 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -245,7 +245,6 @@ private static function get_sites_config() { delaycompress notifempty copytruncate - create 0640 www-data adm sharedscripts } LOGROTATE @@ -270,7 +269,6 @@ private static function get_nginx_proxy_config() { delaycompress notifempty copytruncate - create 0640 www-data adm sharedscripts } LOGROTATE From ea32612d9b0facb57cda035e13ae873ac9ee344c Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 10:55:21 +0530 Subject: [PATCH 06/18] feat(cli): log logrotate config updates --- php/EE/Logrotate/Utils.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 471d67ac0..413efaa96 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -109,6 +109,7 @@ private static function write_config( $file, $content ) { } @chmod( $file, 0644 ); + \EE::debug( 'EasyEngine logrotate config written: ' . $file ); return true; } @@ -137,7 +138,9 @@ private static function cleanup_legacy_configs() { continue; } - if ( ! @unlink( $file ) ) { + if ( @unlink( $file ) ) { + \EE::debug( 'Removed legacy EasyEngine logrotate config: ' . $file ); + } else { \EE::warning( 'Unable to remove legacy EasyEngine logrotate config: ' . $file ); } } From daffdd7fa2264e662ede483e1a217e00c306aa8a Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 10:55:51 +0530 Subject: [PATCH 07/18] perf(cli): avoid redundant logrotate lookup --- php/EE/Logrotate/Utils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 413efaa96..08ecf4efd 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -69,7 +69,7 @@ private static function ensure_logrotate_installed() { \EE::log( 'Installing logrotate.' ); $result = \EE::launch( 'DEBIAN_FRONTEND=noninteractive apt-get install -y logrotate', false, true ); - if ( 0 !== $result->return_code || ! self::command_exists( 'logrotate' ) ) { + if ( 0 !== $result->return_code ) { \EE::warning( 'Unable to install logrotate. Skipping EasyEngine logrotate setup.' ); return false; } From 268760f2047e29017bead7a221e89d28e68b6ac1 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 10:56:37 +0530 Subject: [PATCH 08/18] feat(cli): rotate EasyEngine CLI logs --- php/EE/Logrotate/Utils.php | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 08ecf4efd..26fa89de7 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -8,6 +8,7 @@ class Utils { const CONFIG_DIR = '/etc/logrotate.d'; + const EE_LOG_CONFIG_FILE = '/etc/logrotate.d/ee_cli'; const SITES_CONFIG_FILE = '/etc/logrotate.d/ee_sites'; const NGINX_PROXY_CONFIG_FILE = '/etc/logrotate.d/ee_nginx_proxy'; @@ -42,10 +43,11 @@ private static function setup_logrotate_config() { return; } - $sites_written = self::write_config( self::SITES_CONFIG_FILE, self::get_sites_config() ); - $proxy_written = self::write_config( self::NGINX_PROXY_CONFIG_FILE, self::get_nginx_proxy_config() ); + $ee_log_written = self::write_config( self::EE_LOG_CONFIG_FILE, self::get_ee_log_config() ); + $sites_written = self::write_config( self::SITES_CONFIG_FILE, self::get_sites_config() ); + $proxy_written = self::write_config( self::NGINX_PROXY_CONFIG_FILE, self::get_nginx_proxy_config() ); - if ( $sites_written && $proxy_written ) { + if ( $ee_log_written && $sites_written && $proxy_written ) { self::cleanup_legacy_configs(); } } @@ -128,7 +130,7 @@ private static function cleanup_legacy_configs() { foreach ( $files as $file ) { $basename = basename( $file ); - if ( in_array( $basename, [ 'ee_sites', 'ee_nginx_proxy' ], true ) || ! is_file( $file ) ) { + if ( in_array( $basename, [ 'ee_cli', 'ee_sites', 'ee_nginx_proxy' ], true ) || ! is_file( $file ) ) { continue; } @@ -229,6 +231,30 @@ private static function get_legacy_roots() { return $roots; } + /** + * Return the EasyEngine CLI logs config. + * + * @return string + */ + private static function get_ee_log_config() { + + $root_dir = rtrim( EE_ROOT_DIR, '/' ); + + return << Date: Tue, 7 Jul 2026 10:57:27 +0530 Subject: [PATCH 09/18] feat(cli): validate logrotate configs --- php/EE/Logrotate/Utils.php | 42 +++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 26fa89de7..c6bbe069a 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -47,7 +47,7 @@ private static function setup_logrotate_config() { $sites_written = self::write_config( self::SITES_CONFIG_FILE, self::get_sites_config() ); $proxy_written = self::write_config( self::NGINX_PROXY_CONFIG_FILE, self::get_nginx_proxy_config() ); - if ( $ee_log_written && $sites_written && $proxy_written ) { + if ( $ee_log_written && $sites_written && $proxy_written && self::validate_configs( self::get_config_files() ) ) { self::cleanup_legacy_configs(); } } @@ -116,6 +116,46 @@ private static function write_config( $file, $content ) { return true; } + /** + * Validate generated logrotate configs. + * + * @param array $files Config files. + * @return bool + */ + private static function validate_configs( array $files ) { + + $is_valid = true; + + foreach ( $files as $file ) { + $result = \EE::launch( 'logrotate -d ' . escapeshellarg( $file ), false, true ); + + if ( 0 === $result->return_code ) { + \EE::debug( 'EasyEngine logrotate config validated: ' . $file ); + continue; + } + + $message = trim( $result->stderr ?: $result->stdout ); + \EE::warning( 'Unable to validate EasyEngine logrotate config: ' . $file . ( $message ? '. ' . $message : '' ) ); + $is_valid = false; + } + + return $is_valid; + } + + /** + * Return generated EasyEngine logrotate config files. + * + * @return array + */ + private static function get_config_files() { + + return [ + self::EE_LOG_CONFIG_FILE, + self::SITES_CONFIG_FILE, + self::NGINX_PROXY_CONFIG_FILE, + ]; + } + /** * Remove old per-site/proxy configs that duplicate the wildcard configs. */ From 6707e3363370251320110f2faf035048b9a544ca Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 11:13:21 +0530 Subject: [PATCH 10/18] feat(cli): avoid logrotate validation deadlocks --- php/EE/Logrotate/Utils.php | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index c6bbe069a..7e495f8fd 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -124,22 +124,18 @@ private static function write_config( $file, $content ) { */ private static function validate_configs( array $files ) { - $is_valid = true; + $command = 'logrotate -d ' . implode( ' ', array_map( 'escapeshellarg', $files ) ) . ' 2>&1'; + $result = \EE::launch( $command, false, true ); - foreach ( $files as $file ) { - $result = \EE::launch( 'logrotate -d ' . escapeshellarg( $file ), false, true ); - - if ( 0 === $result->return_code ) { - \EE::debug( 'EasyEngine logrotate config validated: ' . $file ); - continue; - } - - $message = trim( $result->stderr ?: $result->stdout ); - \EE::warning( 'Unable to validate EasyEngine logrotate config: ' . $file . ( $message ? '. ' . $message : '' ) ); - $is_valid = false; + if ( 0 === $result->return_code ) { + \EE::debug( 'EasyEngine logrotate configs validated.' ); + return true; } - return $is_valid; + $message = trim( $result->stdout ?: $result->stderr ); + \EE::warning( 'Unable to validate EasyEngine logrotate configs.' . ( $message ? ' ' . $message : '' ) ); + + return false; } /** From 7e1206ee62b01895748594f5d4bf3195e8d18410 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 11:14:13 +0530 Subject: [PATCH 11/18] feat(cli): avoid silencing logrotate filesystem errors --- php/EE/Logrotate/Utils.php | 40 ++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 7e495f8fd..7b6c44eb0 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -38,9 +38,11 @@ private static function setup_logrotate_config() { return; } - if ( ! is_dir( self::CONFIG_DIR ) && ! @mkdir( self::CONFIG_DIR, 0755, true ) ) { - \EE::warning( 'Unable to create logrotate config directory: ' . self::CONFIG_DIR ); - return; + if ( ! is_dir( self::CONFIG_DIR ) ) { + if ( ! is_writable( dirname( self::CONFIG_DIR ) ) || ! mkdir( self::CONFIG_DIR, 0755, true ) ) { + \EE::warning( 'Unable to create logrotate config directory: ' . self::CONFIG_DIR ); + return; + } } $ee_log_written = self::write_config( self::EE_LOG_CONFIG_FILE, self::get_ee_log_config() ); @@ -101,16 +103,29 @@ private static function command_exists( $command ) { */ private static function write_config( $file, $content ) { - if ( file_exists( $file ) && $content === @file_get_contents( $file ) ) { + if ( file_exists( $file ) && is_readable( $file ) && $content === file_get_contents( $file ) ) { return true; } - if ( false === @file_put_contents( $file, $content ) ) { + if ( file_exists( $file ) && ! is_writable( $file ) ) { + \EE::warning( 'EasyEngine logrotate config is not writable: ' . $file ); + return false; + } + + if ( ! file_exists( $file ) && ! is_writable( dirname( $file ) ) ) { + \EE::warning( 'EasyEngine logrotate config directory is not writable: ' . dirname( $file ) ); + return false; + } + + if ( false === file_put_contents( $file, $content ) ) { \EE::warning( 'Unable to write EasyEngine logrotate config: ' . $file ); return false; } - @chmod( $file, 0644 ); + if ( ! chmod( $file, 0644 ) ) { + \EE::warning( 'Unable to set permissions on EasyEngine logrotate config: ' . $file ); + } + \EE::debug( 'EasyEngine logrotate config written: ' . $file ); return true; @@ -170,13 +185,22 @@ private static function cleanup_legacy_configs() { continue; } - $contents = @file_get_contents( $file ); + if ( ! is_readable( $file ) ) { + continue; + } + + $contents = file_get_contents( $file ); if ( false === $contents || ! self::is_legacy_config( $basename, $contents ) ) { continue; } - if ( @unlink( $file ) ) { + if ( ! is_writable( dirname( $file ) ) ) { + \EE::warning( 'Unable to remove legacy EasyEngine logrotate config, directory is not writable: ' . $file ); + continue; + } + + if ( unlink( $file ) ) { \EE::debug( 'Removed legacy EasyEngine logrotate config: ' . $file ); } else { \EE::warning( 'Unable to remove legacy EasyEngine logrotate config: ' . $file ); From 3320de184fb55b109659ac794294242e7efa806b Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 11:44:17 +0530 Subject: [PATCH 12/18] feat(cli): tolerate logrotate dry-run warnings --- php/EE/Logrotate/Utils.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 7b6c44eb0..ebe4b6638 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -147,12 +147,29 @@ private static function validate_configs( array $files ) { return true; } - $message = trim( $result->stdout ?: $result->stderr ); + $message = trim( $result->stdout ); + + if ( ! self::has_validation_error( $message ) ) { + \EE::debug( 'EasyEngine logrotate validation returned a non-zero exit code without errors.' . ( $message ? ' ' . $message : '' ) ); + return true; + } + \EE::warning( 'Unable to validate EasyEngine logrotate configs.' . ( $message ? ' ' . $message : '' ) ); return false; } + /** + * Check whether logrotate dry-run output contains a real validation error. + * + * @param string $output Dry-run output. + * @return bool + */ + private static function has_validation_error( $output ) { + + return (bool) preg_match( '/(^|\n)\s*(error:|syntax error|bad |unknown |unexpected |duplicate log entry|.*not found|.*permission denied)/i', $output ); + } + /** * Return generated EasyEngine logrotate config files. * From 4b289013e6871dff4ea71d292b97013d336287eb Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 13:04:25 +0530 Subject: [PATCH 13/18] feat(cli): force rotate logs after setup --- php/EE/Logrotate/Utils.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index ebe4b6638..397bf4145 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -51,6 +51,7 @@ private static function setup_logrotate_config() { if ( $ee_log_written && $sites_written && $proxy_written && self::validate_configs( self::get_config_files() ) ) { self::cleanup_legacy_configs(); + self::force_rotate_configs( self::get_config_files() ); } } @@ -170,6 +171,28 @@ private static function has_validation_error( $output ) { return (bool) preg_match( '/(^|\n)\s*(error:|syntax error|bad |unknown |unexpected |duplicate log entry|.*not found|.*permission denied)/i', $output ); } + /** + * Force rotate EasyEngine logs once after setup or update. + * + * @param array $files Config files. + * @return bool + */ + private static function force_rotate_configs( array $files ) { + + $command = 'logrotate -f ' . implode( ' ', array_map( 'escapeshellarg', $files ) ) . ' 2>&1'; + $result = \EE::launch( $command, false, true ); + + if ( 0 === $result->return_code ) { + \EE::debug( 'EasyEngine logs force rotated.' ); + return true; + } + + $message = trim( $result->stdout ); + \EE::warning( 'Unable to force rotate EasyEngine logs.' . ( $message ? ' ' . $message : '' ) ); + + return false; + } + /** * Return generated EasyEngine logrotate config files. * From 9f45a50ae30966fd7ae1fa49caae079492929aee Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 13:28:32 +0530 Subject: [PATCH 14/18] fix(cli): invoke logrotate with one config --- php/EE/Logrotate/Utils.php | 54 ++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 397bf4145..00ef7c3bc 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -140,24 +140,29 @@ private static function write_config( $file, $content ) { */ private static function validate_configs( array $files ) { - $command = 'logrotate -d ' . implode( ' ', array_map( 'escapeshellarg', $files ) ) . ' 2>&1'; - $result = \EE::launch( $command, false, true ); + $is_valid = true; - if ( 0 === $result->return_code ) { - \EE::debug( 'EasyEngine logrotate configs validated.' ); - return true; - } + foreach ( $files as $file ) { + $command = 'logrotate -d ' . escapeshellarg( $file ) . ' 2>&1'; + $result = \EE::launch( $command, false, true ); - $message = trim( $result->stdout ); + if ( 0 === $result->return_code ) { + \EE::debug( 'EasyEngine logrotate config validated: ' . $file ); + continue; + } - if ( ! self::has_validation_error( $message ) ) { - \EE::debug( 'EasyEngine logrotate validation returned a non-zero exit code without errors.' . ( $message ? ' ' . $message : '' ) ); - return true; - } + $message = trim( $result->stdout ); - \EE::warning( 'Unable to validate EasyEngine logrotate configs.' . ( $message ? ' ' . $message : '' ) ); + if ( ! self::has_validation_error( $message ) ) { + \EE::debug( 'EasyEngine logrotate validation returned a non-zero exit code without errors for ' . $file . ( $message ? '. ' . $message : '' ) ); + continue; + } - return false; + \EE::warning( 'Unable to validate EasyEngine logrotate config: ' . $file . ( $message ? '. ' . $message : '' ) ); + $is_valid = false; + } + + return $is_valid; } /** @@ -179,18 +184,23 @@ private static function has_validation_error( $output ) { */ private static function force_rotate_configs( array $files ) { - $command = 'logrotate -f ' . implode( ' ', array_map( 'escapeshellarg', $files ) ) . ' 2>&1'; - $result = \EE::launch( $command, false, true ); + $is_rotated = true; - if ( 0 === $result->return_code ) { - \EE::debug( 'EasyEngine logs force rotated.' ); - return true; - } + foreach ( $files as $file ) { + $command = 'logrotate -f ' . escapeshellarg( $file ) . ' 2>&1'; + $result = \EE::launch( $command, false, true ); - $message = trim( $result->stdout ); - \EE::warning( 'Unable to force rotate EasyEngine logs.' . ( $message ? ' ' . $message : '' ) ); + if ( 0 === $result->return_code ) { + \EE::debug( 'EasyEngine logs force rotated: ' . $file ); + continue; + } - return false; + $message = trim( $result->stdout ); + \EE::warning( 'Unable to force rotate EasyEngine logs for config: ' . $file . ( $message ? '. ' . $message : '' ) ); + $is_rotated = false; + } + + return $is_rotated; } /** From 651f6d2b277da56796c4818e327785e7f42f6a5f Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 13:28:56 +0530 Subject: [PATCH 15/18] fix(cli): report logrotate cleanup directory --- php/EE/Logrotate/Utils.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 00ef7c3bc..0fdff0c61 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -245,8 +245,10 @@ private static function cleanup_legacy_configs() { continue; } - if ( ! is_writable( dirname( $file ) ) ) { - \EE::warning( 'Unable to remove legacy EasyEngine logrotate config, directory is not writable: ' . $file ); + $directory = dirname( $file ); + + if ( ! is_writable( $directory ) ) { + \EE::warning( 'Unable to remove legacy EasyEngine logrotate config, directory is not writable: ' . $directory ); continue; } From a6230c49ff316014fd18e86857eb89e94f99f3de Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 13:43:30 +0530 Subject: [PATCH 16/18] fix(cli): fail on logrotate validation errors --- php/EE/Logrotate/Utils.php | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 0fdff0c61..19139f270 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -152,12 +152,6 @@ private static function validate_configs( array $files ) { } $message = trim( $result->stdout ); - - if ( ! self::has_validation_error( $message ) ) { - \EE::debug( 'EasyEngine logrotate validation returned a non-zero exit code without errors for ' . $file . ( $message ? '. ' . $message : '' ) ); - continue; - } - \EE::warning( 'Unable to validate EasyEngine logrotate config: ' . $file . ( $message ? '. ' . $message : '' ) ); $is_valid = false; } @@ -165,17 +159,6 @@ private static function validate_configs( array $files ) { return $is_valid; } - /** - * Check whether logrotate dry-run output contains a real validation error. - * - * @param string $output Dry-run output. - * @return bool - */ - private static function has_validation_error( $output ) { - - return (bool) preg_match( '/(^|\n)\s*(error:|syntax error|bad |unknown |unexpected |duplicate log entry|.*not found|.*permission denied)/i', $output ); - } - /** * Force rotate EasyEngine logs once after setup or update. * From 8e27b1ad37ef49b80043cc631b33683331df0cd8 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 13:44:00 +0530 Subject: [PATCH 17/18] docs(cli): clarify logrotate setup scope --- php/EE/Logrotate/Utils.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 19139f270..06936b4bb 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -13,7 +13,7 @@ class Utils { const NGINX_PROXY_CONFIG_FILE = '/etc/logrotate.d/ee_nginx_proxy'; /** - * Configure logrotate for EasyEngine site and nginx-proxy logs. + * Configure logrotate for EasyEngine CLI, site, and nginx-proxy logs. */ public static function setup_logrotate() { @@ -25,7 +25,7 @@ public static function setup_logrotate() { } /** - * Configure logrotate for EasyEngine site and nginx-proxy logs. + * Configure logrotate for EasyEngine CLI, site, and nginx-proxy logs. */ private static function setup_logrotate_config() { From 45c2c19f28952cd2b6d1d8cf29c64c394edba46a Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 7 Jul 2026 14:08:40 +0530 Subject: [PATCH 18/18] Fix grammar Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- php/EE/Logrotate/Utils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/EE/Logrotate/Utils.php b/php/EE/Logrotate/Utils.php index 06936b4bb..c9747ee71 100644 --- a/php/EE/Logrotate/Utils.php +++ b/php/EE/Logrotate/Utils.php @@ -20,7 +20,7 @@ public static function setup_logrotate() { try { self::setup_logrotate_config(); } catch ( \Throwable $e ) { - \EE::warning( 'Unable to setup EasyEngine logrotate config: ' . $e->getMessage() ); + \EE::warning( 'Unable to set up EasyEngine logrotate config: ' . $e->getMessage() ); } }