From a8d6bbe16e309e13367d4c03b489fd9c4a903ed6 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 28 Jan 2026 18:46:06 +0200 Subject: [PATCH] Harden permissions for sqlite runtime --- VERSION | 2 +- .../Commands/Jabali/UpgradeCommand.php | 30 ++++++++++++++ install.sh | 13 +++++++ tests/Unit/UpgradeCommandTest.php | 39 +++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index a82c333..c6569d2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -VERSION=0.9-rc13 +VERSION=0.9-rc14 diff --git a/app/Console/Commands/Jabali/UpgradeCommand.php b/app/Console/Commands/Jabali/UpgradeCommand.php index ba6e4f9..d097ab7 100644 --- a/app/Console/Commands/Jabali/UpgradeCommand.php +++ b/app/Console/Commands/Jabali/UpgradeCommand.php @@ -90,6 +90,7 @@ class UpgradeCommand extends Command try { $this->configureGitSafeDirectory(); $this->ensureGitRepository(); + $this->ensureWritableStorage(); $statusResult = $this->executeCommand('git status --porcelain'); if ($statusResult['exitCode'] !== 0) { throw new Exception($statusResult['output'] ?: 'Unable to read git status.'); @@ -317,6 +318,13 @@ class UpgradeCommand extends Command } } + protected function ensureWritableStorage(): void + { + foreach ($this->getWritableStorageCommands() as $command) { + $this->executeCommand($command); + } + } + protected function getSafeDirectoryCommands(): array { $directory = $this->basePath; @@ -337,6 +345,28 @@ class UpgradeCommand extends Command return $commands; } + protected function getWritableStorageCommands(): array + { + if (! $this->isRunningAsRoot() || ! $this->userExists('www-data')) { + return []; + } + + $paths = [ + $this->basePath.'/database', + $this->basePath.'/storage', + $this->basePath.'/bootstrap/cache', + ]; + + $escapedPaths = array_map('escapeshellarg', $paths); + $pathList = implode(' ', $escapedPaths); + + return [ + "chgrp -R www-data {$pathList}", + "chmod -R g+rwX {$pathList}", + "find {$pathList} -type d -exec chmod g+s {} +", + ]; + } + protected function commandExists(string $command): bool { $result = $this->executeCommand("command -v {$command}"); diff --git a/install.sh b/install.sh index 14d2cb1..cc70ecb 100755 --- a/install.sh +++ b/install.sh @@ -768,6 +768,19 @@ clone_jabali() { 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 + # Ensure runtime directories stay writable for PHP-FPM (default: www-data) + if id www-data &>/dev/null; then + chown -R $JABALI_USER:www-data \ + "$JABALI_DIR/database" \ + "$JABALI_DIR/storage" \ + "$JABALI_DIR/bootstrap/cache" 2>/dev/null || true + chmod -R g+rwX \ + "$JABALI_DIR/database" \ + "$JABALI_DIR/storage" \ + "$JABALI_DIR/bootstrap/cache" 2>/dev/null || true + find "$JABALI_DIR/database" "$JABALI_DIR/storage" "$JABALI_DIR/bootstrap/cache" -type d -exec chmod g+s {} + 2>/dev/null || true + fi + # 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 764fde5..2c7884c 100644 --- a/tests/Unit/UpgradeCommandTest.php +++ b/tests/Unit/UpgradeCommandTest.php @@ -110,6 +110,40 @@ class UpgradeCommandTest extends TestCase 'sudo -u www-data git config --global --add safe.directory '.base_path(), ], $command->exposesSafeDirectoryCommands()); } + + public function test_writable_storage_commands_for_non_root(): void + { + $command = new SafeDirectoryUpgradeCommand(false, true, true); + + $this->assertSame([], $command->exposesWritableStorageCommands()); + } + + public function test_writable_storage_commands_without_www_data(): void + { + $command = new SafeDirectoryUpgradeCommand(true, true, false); + + $this->assertSame([], $command->exposesWritableStorageCommands()); + } + + public function test_writable_storage_commands_for_root_with_www_data(): void + { + $command = new SafeDirectoryUpgradeCommand(true, true, true); + + $paths = [ + base_path().'/database', + base_path().'/storage', + base_path().'/bootstrap/cache', + ]; + + $escaped = array_map('escapeshellarg', $paths); + $list = implode(' ', $escaped); + + $this->assertSame([ + "chgrp -R www-data {$list}", + "chmod -R g+rwX {$list}", + "find {$list} -type d -exec chmod g+s {} +", + ], $command->exposesWritableStorageCommands()); + } } class TestableUpgradeCommand extends UpgradeCommand @@ -145,6 +179,11 @@ class SafeDirectoryUpgradeCommand extends UpgradeCommand return $this->getSafeDirectoryCommands(); } + public function exposesWritableStorageCommands(): array + { + return $this->getWritableStorageCommands(); + } + protected function isRunningAsRoot(): bool { return $this->runningAsRoot;