From ec120074ecde34d3cbb9c87849ee1032d88057ef Mon Sep 17 00:00:00 2001 From: root Date: Wed, 28 Jan 2026 18:23:56 +0200 Subject: [PATCH] Fix upgrade safe.directory handling --- VERSION | 2 +- .../Commands/Jabali/UpgradeCommand.php | 59 +++++++++++++++--- install.sh | 4 ++ tests/Unit/UpgradeCommandTest.php | 61 +++++++++++++++++++ 4 files changed, 118 insertions(+), 8 deletions(-) diff --git a/VERSION b/VERSION index 3845b18..a82c333 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -VERSION=0.9-rc12 +VERSION=0.9-rc13 diff --git a/app/Console/Commands/Jabali/UpgradeCommand.php b/app/Console/Commands/Jabali/UpgradeCommand.php index 0021e89..ba6e4f9 100644 --- a/app/Console/Commands/Jabali/UpgradeCommand.php +++ b/app/Console/Commands/Jabali/UpgradeCommand.php @@ -46,6 +46,7 @@ class UpgradeCommand extends Command $this->line("Current version: {$currentVersion}"); try { + $this->configureGitSafeDirectory(); $this->ensureGitRepository(); // Fetch from remote without merging $this->executeCommandOrFail('git fetch origin main'); @@ -87,6 +88,7 @@ class UpgradeCommand extends Command // Step 1: Check git status $this->info('[1/9] Checking repository status...'); try { + $this->configureGitSafeDirectory(); $this->ensureGitRepository(); $statusResult = $this->executeCommand('git status --porcelain'); if ($statusResult['exitCode'] !== 0) { @@ -260,7 +262,7 @@ class UpgradeCommand extends Command return 'unknown'; } - private function executeCommand(string $command, int $timeout = 600): array + protected function executeCommand(string $command, int $timeout = 600): array { $process = Process::fromShellCommandline($command, $this->basePath, $this->getCommandEnvironment()); $process->setTimeout($timeout); @@ -273,7 +275,7 @@ class UpgradeCommand extends Command ]; } - private function executeCommandOrFail(string $command, int $timeout = 600): string + protected function executeCommandOrFail(string $command, int $timeout = 600): string { $result = $this->executeCommand($command, $timeout); if ($result['exitCode'] !== 0) { @@ -283,7 +285,7 @@ class UpgradeCommand extends Command return $result['output']; } - private function getCommandEnvironment(): array + protected function getCommandEnvironment(): array { $path = getenv('PATH') ?: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'; @@ -293,7 +295,7 @@ class UpgradeCommand extends Command ]; } - private function ensureCommandAvailable(string $command): void + protected function ensureCommandAvailable(string $command): void { $result = $this->executeCommand("command -v {$command}"); if ($result['exitCode'] !== 0 || $result['output'] === '') { @@ -301,13 +303,56 @@ class UpgradeCommand extends Command } } - private function ensureGitRepository(): void + protected function ensureGitRepository(): void { if (! File::isDirectory($this->basePath.'/.git')) { throw new Exception('Not a git repository.'); } } + protected function configureGitSafeDirectory(): void + { + foreach ($this->getSafeDirectoryCommands() as $command) { + $this->executeCommand($command); + } + } + + protected function getSafeDirectoryCommands(): array + { + $directory = $this->basePath; + $commands = [ + "git config --global --add safe.directory {$directory}", + ]; + + if (! $this->isRunningAsRoot()) { + return $commands; + } + + $commands[] = "git config --system --add safe.directory {$directory}"; + + if ($this->commandExists('sudo') && $this->userExists('www-data')) { + $commands[] = "sudo -u www-data git config --global --add safe.directory {$directory}"; + } + + return $commands; + } + + protected function commandExists(string $command): bool + { + $result = $this->executeCommand("command -v {$command}"); + + return $result['exitCode'] === 0 && $result['output'] !== ''; + } + + protected function userExists(string $user): bool + { + if (function_exists('posix_getpwnam')) { + return posix_getpwnam($user) !== false; + } + + return $this->executeCommand("id -u {$user}")['exitCode'] === 0; + } + private function getChangedFiles(string $from, string $to): array { if ($from === $to) { @@ -385,7 +430,7 @@ class UpgradeCommand extends Command return false; } - private function restartServices(): void + protected function restartServices(): void { try { Artisan::call('queue:restart'); @@ -415,7 +460,7 @@ class UpgradeCommand extends Command } } - private function isRunningAsRoot(): bool + protected function isRunningAsRoot(): bool { if (function_exists('posix_geteuid')) { return posix_geteuid() === 0; diff --git a/install.sh b/install.sh index 9595753..14d2cb1 100755 --- a/install.sh +++ b/install.sh @@ -764,6 +764,10 @@ clone_jabali() { git clone "$JABALI_REPO" "$JABALI_DIR" chown -R $JABALI_USER:$JABALI_USER "$JABALI_DIR" + # Prevent git safe.directory issues for upgrades run as root or www-data + git config --system --add safe.directory "$JABALI_DIR" 2>/dev/null || true + sudo -u $JABALI_USER git config --global --add safe.directory "$JABALI_DIR" 2>/dev/null || true + # Read version from cloned VERSION file if [[ -f "$JABALI_DIR/VERSION" ]]; then source "$JABALI_DIR/VERSION" diff --git a/tests/Unit/UpgradeCommandTest.php b/tests/Unit/UpgradeCommandTest.php index 386547c..764fde5 100644 --- a/tests/Unit/UpgradeCommandTest.php +++ b/tests/Unit/UpgradeCommandTest.php @@ -80,6 +80,36 @@ class UpgradeCommandTest extends TestCase $command->exposesShouldRunMigrations(['app/Models/User.php'], false) ); } + + public function test_safe_directory_commands_for_non_root(): void + { + $command = new SafeDirectoryUpgradeCommand(false, false, false); + + $this->assertSame([ + 'git config --global --add safe.directory '.base_path(), + ], $command->exposesSafeDirectoryCommands()); + } + + public function test_safe_directory_commands_for_root_without_www_data(): void + { + $command = new SafeDirectoryUpgradeCommand(true, false, false); + + $this->assertSame([ + 'git config --global --add safe.directory '.base_path(), + 'git config --system --add safe.directory '.base_path(), + ], $command->exposesSafeDirectoryCommands()); + } + + public function test_safe_directory_commands_for_root_with_www_data(): void + { + $command = new SafeDirectoryUpgradeCommand(true, true, true); + + $this->assertSame([ + 'git config --global --add safe.directory '.base_path(), + 'git config --system --add safe.directory '.base_path(), + 'sudo -u www-data git config --global --add safe.directory '.base_path(), + ], $command->exposesSafeDirectoryCommands()); + } } class TestableUpgradeCommand extends UpgradeCommand @@ -99,3 +129,34 @@ class TestableUpgradeCommand extends UpgradeCommand return $this->shouldRunMigrations($changedFiles, $force); } } + +class SafeDirectoryUpgradeCommand extends UpgradeCommand +{ + public function __construct( + private bool $runningAsRoot, + private bool $sudoExists, + private bool $wwwDataExists + ) { + parent::__construct(); + } + + public function exposesSafeDirectoryCommands(): array + { + return $this->getSafeDirectoryCommands(); + } + + protected function isRunningAsRoot(): bool + { + return $this->runningAsRoot; + } + + protected function commandExists(string $command): bool + { + return $command === 'sudo' && $this->sudoExists; + } + + protected function userExists(string $user): bool + { + return $user === 'www-data' && $this->wwwDataExists; + } +}