Fix upgrade safe.directory handling
This commit is contained in:
@@ -46,6 +46,7 @@ class UpgradeCommand extends Command
|
|||||||
$this->line("Current version: <info>{$currentVersion}</info>");
|
$this->line("Current version: <info>{$currentVersion}</info>");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
$this->configureGitSafeDirectory();
|
||||||
$this->ensureGitRepository();
|
$this->ensureGitRepository();
|
||||||
// Fetch from remote without merging
|
// Fetch from remote without merging
|
||||||
$this->executeCommandOrFail('git fetch origin main');
|
$this->executeCommandOrFail('git fetch origin main');
|
||||||
@@ -87,6 +88,7 @@ class UpgradeCommand extends Command
|
|||||||
// Step 1: Check git status
|
// Step 1: Check git status
|
||||||
$this->info('[1/9] Checking repository status...');
|
$this->info('[1/9] Checking repository status...');
|
||||||
try {
|
try {
|
||||||
|
$this->configureGitSafeDirectory();
|
||||||
$this->ensureGitRepository();
|
$this->ensureGitRepository();
|
||||||
$statusResult = $this->executeCommand('git status --porcelain');
|
$statusResult = $this->executeCommand('git status --porcelain');
|
||||||
if ($statusResult['exitCode'] !== 0) {
|
if ($statusResult['exitCode'] !== 0) {
|
||||||
@@ -260,7 +262,7 @@ class UpgradeCommand extends Command
|
|||||||
return 'unknown';
|
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 = Process::fromShellCommandline($command, $this->basePath, $this->getCommandEnvironment());
|
||||||
$process->setTimeout($timeout);
|
$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);
|
$result = $this->executeCommand($command, $timeout);
|
||||||
if ($result['exitCode'] !== 0) {
|
if ($result['exitCode'] !== 0) {
|
||||||
@@ -283,7 +285,7 @@ class UpgradeCommand extends Command
|
|||||||
return $result['output'];
|
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';
|
$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}");
|
$result = $this->executeCommand("command -v {$command}");
|
||||||
if ($result['exitCode'] !== 0 || $result['output'] === '') {
|
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')) {
|
if (! File::isDirectory($this->basePath.'/.git')) {
|
||||||
throw new Exception('Not a git repository.');
|
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
|
private function getChangedFiles(string $from, string $to): array
|
||||||
{
|
{
|
||||||
if ($from === $to) {
|
if ($from === $to) {
|
||||||
@@ -385,7 +430,7 @@ class UpgradeCommand extends Command
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function restartServices(): void
|
protected function restartServices(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
Artisan::call('queue:restart');
|
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')) {
|
if (function_exists('posix_geteuid')) {
|
||||||
return posix_geteuid() === 0;
|
return posix_geteuid() === 0;
|
||||||
|
|||||||
@@ -764,6 +764,10 @@ clone_jabali() {
|
|||||||
git clone "$JABALI_REPO" "$JABALI_DIR"
|
git clone "$JABALI_REPO" "$JABALI_DIR"
|
||||||
chown -R $JABALI_USER:$JABALI_USER "$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
|
# Read version from cloned VERSION file
|
||||||
if [[ -f "$JABALI_DIR/VERSION" ]]; then
|
if [[ -f "$JABALI_DIR/VERSION" ]]; then
|
||||||
source "$JABALI_DIR/VERSION"
|
source "$JABALI_DIR/VERSION"
|
||||||
|
|||||||
@@ -80,6 +80,36 @@ class UpgradeCommandTest extends TestCase
|
|||||||
$command->exposesShouldRunMigrations(['app/Models/User.php'], false)
|
$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
|
class TestableUpgradeCommand extends UpgradeCommand
|
||||||
@@ -99,3 +129,34 @@ class TestableUpgradeCommand extends UpgradeCommand
|
|||||||
return $this->shouldRunMigrations($changedFiles, $force);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user