@@ -363,6 +363,162 @@ private function syncReleasesAndVersionsToGitHubRepo(string $versionsLocation, b
363363 }
364364 }
365365
366+ /**
367+ * Sync install.sh, docker-compose, and env files to GitHub repository via PR
368+ */
369+ private function syncFilesToGitHubRepo (array $ files , bool $ nightly = false ): bool
370+ {
371+ $ envLabel = $ nightly ? 'NIGHTLY ' : 'PRODUCTION ' ;
372+ $ this ->info ("Syncing $ envLabel files to GitHub repository... " );
373+ try {
374+ $ timestamp = time ();
375+ $ tmpDir = sys_get_temp_dir ().'/coolify-cdn-files- ' .$ timestamp ;
376+ $ branchName = 'update-files- ' .$ timestamp ;
377+
378+ // Clone the repository
379+ $ this ->info ('Cloning coolify-cdn repository... ' );
380+ $ output = [];
381+ exec ('gh repo clone coollabsio/coolify-cdn ' .escapeshellarg ($ tmpDir ).' 2>&1 ' , $ output , $ returnCode );
382+ if ($ returnCode !== 0 ) {
383+ $ this ->error ('Failed to clone repository: ' .implode ("\n" , $ output ));
384+
385+ return false ;
386+ }
387+
388+ // Create feature branch
389+ $ this ->info ('Creating feature branch... ' );
390+ $ output = [];
391+ exec ('cd ' .escapeshellarg ($ tmpDir ).' && git checkout -b ' .escapeshellarg ($ branchName ).' 2>&1 ' , $ output , $ returnCode );
392+ if ($ returnCode !== 0 ) {
393+ $ this ->error ('Failed to create branch: ' .implode ("\n" , $ output ));
394+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
395+
396+ return false ;
397+ }
398+
399+ // Copy each file to its target path in the CDN repo
400+ $ copiedFiles = [];
401+ foreach ($ files as $ sourceFile => $ targetPath ) {
402+ if (! file_exists ($ sourceFile )) {
403+ $ this ->warn ("Source file not found, skipping: $ sourceFile " );
404+
405+ continue ;
406+ }
407+
408+ $ destPath = "$ tmpDir/ $ targetPath " ;
409+ $ destDir = dirname ($ destPath );
410+
411+ if (! is_dir ($ destDir )) {
412+ if (! mkdir ($ destDir , 0755 , true )) {
413+ $ this ->error ("Failed to create directory: $ destDir " );
414+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
415+
416+ return false ;
417+ }
418+ }
419+
420+ if (copy ($ sourceFile , $ destPath ) === false ) {
421+ $ this ->error ("Failed to copy $ sourceFile to $ destPath " );
422+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
423+
424+ return false ;
425+ }
426+
427+ $ copiedFiles [] = $ targetPath ;
428+ $ this ->info ("Copied: $ targetPath " );
429+ }
430+
431+ if (empty ($ copiedFiles )) {
432+ $ this ->warn ('No files were copied. Nothing to commit. ' );
433+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
434+
435+ return true ;
436+ }
437+
438+ // Stage all copied files
439+ $ this ->info ('Staging changes... ' );
440+ $ output = [];
441+ $ stageCmd = 'cd ' .escapeshellarg ($ tmpDir ).' && git add ' .implode (' ' , array_map ('escapeshellarg ' , $ copiedFiles )).' 2>&1 ' ;
442+ exec ($ stageCmd , $ output , $ returnCode );
443+ if ($ returnCode !== 0 ) {
444+ $ this ->error ('Failed to stage changes: ' .implode ("\n" , $ output ));
445+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
446+
447+ return false ;
448+ }
449+
450+ // Check for changes
451+ $ this ->info ('Checking for changes... ' );
452+ $ statusOutput = [];
453+ exec ('cd ' .escapeshellarg ($ tmpDir ).' && git status --porcelain 2>&1 ' , $ statusOutput , $ returnCode );
454+ if ($ returnCode !== 0 ) {
455+ $ this ->error ('Failed to check repository status: ' .implode ("\n" , $ statusOutput ));
456+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
457+
458+ return false ;
459+ }
460+
461+ if (empty (array_filter ($ statusOutput ))) {
462+ $ this ->info ('All files are already up to date. No changes to commit. ' );
463+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
464+
465+ return true ;
466+ }
467+
468+ // Commit changes
469+ $ commitMessage = "Update $ envLabel files (install.sh, docker-compose, env) - " .date ('Y-m-d H:i:s ' );
470+ $ output = [];
471+ exec ('cd ' .escapeshellarg ($ tmpDir ).' && git commit -m ' .escapeshellarg ($ commitMessage ).' 2>&1 ' , $ output , $ returnCode );
472+ if ($ returnCode !== 0 ) {
473+ $ this ->error ('Failed to commit changes: ' .implode ("\n" , $ output ));
474+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
475+
476+ return false ;
477+ }
478+
479+ // Push to remote
480+ $ this ->info ('Pushing branch to remote... ' );
481+ $ output = [];
482+ exec ('cd ' .escapeshellarg ($ tmpDir ).' && git push origin ' .escapeshellarg ($ branchName ).' 2>&1 ' , $ output , $ returnCode );
483+ if ($ returnCode !== 0 ) {
484+ $ this ->error ('Failed to push branch: ' .implode ("\n" , $ output ));
485+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
486+
487+ return false ;
488+ }
489+
490+ // Create pull request
491+ $ this ->info ('Creating pull request... ' );
492+ $ prTitle = "Update $ envLabel files - " .date ('Y-m-d H:i:s ' );
493+ $ fileList = implode ("\n- " , $ copiedFiles );
494+ $ prBody = "Automated update of $ envLabel files: \n- $ fileList " ;
495+ $ prCommand = 'gh pr create --repo coollabsio/coolify-cdn --title ' .escapeshellarg ($ prTitle ).' --body ' .escapeshellarg ($ prBody ).' --base main --head ' .escapeshellarg ($ branchName ).' 2>&1 ' ;
496+ $ output = [];
497+ exec ($ prCommand , $ output , $ returnCode );
498+
499+ // Clean up
500+ exec ('rm -rf ' .escapeshellarg ($ tmpDir ));
501+
502+ if ($ returnCode !== 0 ) {
503+ $ this ->error ('Failed to create PR: ' .implode ("\n" , $ output ));
504+
505+ return false ;
506+ }
507+
508+ $ this ->info ('Pull request created successfully! ' );
509+ if (! empty ($ output )) {
510+ $ this ->info ('PR URL: ' .implode ("\n" , $ output ));
511+ }
512+ $ this ->info ('Files synced: ' .count ($ copiedFiles ));
513+
514+ return true ;
515+ } catch (\Throwable $ e ) {
516+ $ this ->error ('Error syncing files to GitHub: ' .$ e ->getMessage ());
517+
518+ return false ;
519+ }
520+ }
521+
366522 /**
367523 * Sync versions.json to GitHub repository via PR
368524 */
@@ -581,11 +737,130 @@ public function handle()
581737 $ versions_location = "$ parent_dir/other/nightly/ $ versions " ;
582738 }
583739 if (! $ only_template && ! $ only_version && ! $ only_github_releases && ! $ only_github_versions ) {
740+ $ envLabel = $ nightly ? 'NIGHTLY ' : 'PRODUCTION ' ;
741+ $ this ->info ("About to sync $ envLabel files to BunnyCDN and create a GitHub PR for coolify-cdn. " );
742+ $ this ->newLine ();
743+
744+ // Build file mapping for diff
584745 if ($ nightly ) {
585- $ this ->info ('About to sync files NIGHTLY (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN. ' );
746+ $ fileMapping = [
747+ $ compose_file_location => 'docker/nightly/docker-compose.yml ' ,
748+ $ compose_file_prod_location => 'docker/nightly/docker-compose.prod.yml ' ,
749+ $ production_env_location => 'environment/nightly/.env.production ' ,
750+ $ upgrade_script_location => 'scripts/nightly/upgrade.sh ' ,
751+ $ install_script_location => 'scripts/nightly/install.sh ' ,
752+ ];
586753 } else {
587- $ this ->info ('About to sync files PRODUCTION (docker-compose.yml, docker-compose.prod.yml, upgrade.sh, install.sh, etc) to BunnyCDN. ' );
754+ $ fileMapping = [
755+ $ compose_file_location => 'docker/docker-compose.yml ' ,
756+ $ compose_file_prod_location => 'docker/docker-compose.prod.yml ' ,
757+ $ production_env_location => 'environment/.env.production ' ,
758+ $ upgrade_script_location => 'scripts/upgrade.sh ' ,
759+ $ install_script_location => 'scripts/install.sh ' ,
760+ ];
761+ }
762+
763+ // BunnyCDN file mapping (local file => CDN URL path)
764+ $ bunnyFileMapping = [
765+ $ compose_file_location => "$ bunny_cdn/ $ bunny_cdn_path/ $ compose_file " ,
766+ $ compose_file_prod_location => "$ bunny_cdn/ $ bunny_cdn_path/ $ compose_file_prod " ,
767+ $ production_env_location => "$ bunny_cdn/ $ bunny_cdn_path/ $ production_env " ,
768+ $ upgrade_script_location => "$ bunny_cdn/ $ bunny_cdn_path/ $ upgrade_script " ,
769+ $ install_script_location => "$ bunny_cdn/ $ bunny_cdn_path/ $ install_script " ,
770+ ];
771+
772+ $ diffTmpDir = sys_get_temp_dir ().'/coolify-cdn-diff- ' .time ();
773+ @mkdir ($ diffTmpDir , 0755 , true );
774+ $ hasChanges = false ;
775+
776+ // Diff against BunnyCDN
777+ $ this ->info ('Fetching files from BunnyCDN to compare... ' );
778+ foreach ($ bunnyFileMapping as $ localFile => $ cdnUrl ) {
779+ if (! file_exists ($ localFile )) {
780+ $ this ->warn ('Local file not found: ' .$ localFile );
781+
782+ continue ;
783+ }
784+
785+ $ fileName = basename ($ cdnUrl );
786+ $ remoteTmp = "$ diffTmpDir/bunny- $ fileName " ;
787+
788+ try {
789+ $ response = Http::timeout (10 )->get ($ cdnUrl );
790+ if ($ response ->successful ()) {
791+ file_put_contents ($ remoteTmp , $ response ->body ());
792+ $ diffOutput = [];
793+ exec ('diff -u ' .escapeshellarg ($ remoteTmp ).' ' .escapeshellarg ($ localFile ).' 2>&1 ' , $ diffOutput , $ diffCode );
794+ if ($ diffCode !== 0 ) {
795+ $ hasChanges = true ;
796+ $ this ->newLine ();
797+ $ this ->info ("--- BunnyCDN: $ bunny_cdn_path/ $ fileName " );
798+ $ this ->info ("+++ Local: $ fileName " );
799+ foreach ($ diffOutput as $ line ) {
800+ if (str_starts_with ($ line , '--- ' ) || str_starts_with ($ line , '+++ ' )) {
801+ continue ;
802+ }
803+ $ this ->line ($ line );
804+ }
805+ }
806+ } else {
807+ $ this ->info ("NEW on BunnyCDN: $ bunny_cdn_path/ $ fileName (HTTP {$ response ->status ()}) " );
808+ $ hasChanges = true ;
809+ }
810+ } catch (\Throwable $ e ) {
811+ $ this ->warn ("Could not fetch $ cdnUrl: {$ e ->getMessage ()}" );
812+ }
588813 }
814+
815+ // Diff against GitHub coolify-cdn repo
816+ $ this ->newLine ();
817+ $ this ->info ('Fetching coolify-cdn repo to compare... ' );
818+ $ output = [];
819+ exec ('gh repo clone coollabsio/coolify-cdn ' .escapeshellarg ("$ diffTmpDir/repo " ).' -- --depth 1 2>&1 ' , $ output , $ returnCode );
820+
821+ if ($ returnCode === 0 ) {
822+ foreach ($ fileMapping as $ localFile => $ cdnPath ) {
823+ $ remotePath = "$ diffTmpDir/repo/ $ cdnPath " ;
824+ if (! file_exists ($ localFile )) {
825+ continue ;
826+ }
827+ if (! file_exists ($ remotePath )) {
828+ $ this ->info ("NEW on GitHub: $ cdnPath (does not exist in coolify-cdn yet) " );
829+ $ hasChanges = true ;
830+
831+ continue ;
832+ }
833+
834+ $ diffOutput = [];
835+ exec ('diff -u ' .escapeshellarg ($ remotePath ).' ' .escapeshellarg ($ localFile ).' 2>&1 ' , $ diffOutput , $ diffCode );
836+ if ($ diffCode !== 0 ) {
837+ $ hasChanges = true ;
838+ $ this ->newLine ();
839+ $ this ->info ("--- GitHub: $ cdnPath " );
840+ $ this ->info ("+++ Local: $ cdnPath " );
841+ foreach ($ diffOutput as $ line ) {
842+ if (str_starts_with ($ line , '--- ' ) || str_starts_with ($ line , '+++ ' )) {
843+ continue ;
844+ }
845+ $ this ->line ($ line );
846+ }
847+ }
848+ }
849+ } else {
850+ $ this ->warn ('Could not fetch coolify-cdn repo for diff. ' );
851+ }
852+
853+ exec ('rm -rf ' .escapeshellarg ($ diffTmpDir ));
854+
855+ if (! $ hasChanges ) {
856+ $ this ->newLine ();
857+ $ this ->info ('No differences found. All files are already up to date. ' );
858+
859+ return ;
860+ }
861+
862+ $ this ->newLine ();
863+
589864 $ confirmed = confirm ('Are you sure you want to sync? ' );
590865 if (! $ confirmed ) {
591866 return ;
@@ -692,7 +967,34 @@ public function handle()
692967 $ pool ->purge ("$ bunny_cdn/ $ bunny_cdn_path/ $ upgrade_script " ),
693968 $ pool ->purge ("$ bunny_cdn/ $ bunny_cdn_path/ $ install_script " ),
694969 ]);
695- $ this ->info ('All files uploaded & purged... ' );
970+ $ this ->info ('All files uploaded & purged to BunnyCDN. ' );
971+ $ this ->newLine ();
972+
973+ // Sync files to GitHub CDN repository via PR
974+ $ this ->info ('Creating GitHub PR for coolify-cdn repository... ' );
975+ if ($ nightly ) {
976+ $ files = [
977+ $ compose_file_location => 'docker/nightly/docker-compose.yml ' ,
978+ $ compose_file_prod_location => 'docker/nightly/docker-compose.prod.yml ' ,
979+ $ production_env_location => 'environment/nightly/.env.production ' ,
980+ $ upgrade_script_location => 'scripts/nightly/upgrade.sh ' ,
981+ $ install_script_location => 'scripts/nightly/install.sh ' ,
982+ ];
983+ } else {
984+ $ files = [
985+ $ compose_file_location => 'docker/docker-compose.yml ' ,
986+ $ compose_file_prod_location => 'docker/docker-compose.prod.yml ' ,
987+ $ production_env_location => 'environment/.env.production ' ,
988+ $ upgrade_script_location => 'scripts/upgrade.sh ' ,
989+ $ install_script_location => 'scripts/install.sh ' ,
990+ ];
991+ }
992+
993+ $ githubSuccess = $ this ->syncFilesToGitHubRepo ($ files , $ nightly );
994+ $ this ->newLine ();
995+ $ this ->info ('=== Summary === ' );
996+ $ this ->info ('BunnyCDN sync: Complete ' );
997+ $ this ->info ('GitHub PR: ' .($ githubSuccess ? 'Created ' : 'Failed ' ));
696998 } catch (\Throwable $ e ) {
697999 $ this ->error ('Error: ' .$ e ->getMessage ());
6981000 }
0 commit comments