diff --git a/DOCKER_COMPOSE_SETUP.md b/DOCKER_COMPOSE_SETUP.md index 57d80ef1..b87cd215 100644 --- a/DOCKER_COMPOSE_SETUP.md +++ b/DOCKER_COMPOSE_SETUP.md @@ -63,7 +63,12 @@ This setup uses `serversideup/php:8.4-fpm-nginx` as the base image and is design docker compose exec loops php artisan passport:keys ``` -8. **Create admin user:** +8. **Ensure boot-time environment:** + ```bash + docker compose exec loops php artisan app:ensure-boottime + ``` + +9. **Create admin user:** ```bash docker compose exec loops php artisan create-admin-account ``` diff --git a/app/Console/Commands/ComposerPostInstallCommand.php b/app/Console/Commands/ComposerPostInstallCommand.php index 5b503eeb..53a80293 100644 --- a/app/Console/Commands/ComposerPostInstallCommand.php +++ b/app/Console/Commands/ComposerPostInstallCommand.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Services\BootstrapService; use App\Services\SettingsFileService; use Illuminate\Console\Command; use Illuminate\Support\Facades\Cache; @@ -33,6 +34,7 @@ public function handle() Cache::forget('version_check_result'); app(SettingsFileService::class)->flush(); + BootstrapService::ensureBoottimeEnvironment(); $this->info('Post-install tasks completed successfully.'); } catch (\Exception $e) { diff --git a/app/Console/Commands/EnsureBoottimeEnvCommand.php b/app/Console/Commands/EnsureBoottimeEnvCommand.php new file mode 100644 index 00000000..acd43463 --- /dev/null +++ b/app/Console/Commands/EnsureBoottimeEnvCommand.php @@ -0,0 +1,40 @@ +info('All boot-time environment checks passed.'); + + return Command::SUCCESS; + } catch (\RuntimeException $e) { + $this->error($e->getMessage()); + + return Command::FAILURE; + } + } +} diff --git a/app/Services/BootstrapService.php b/app/Services/BootstrapService.php new file mode 100644 index 00000000..fefa4c6f --- /dev/null +++ b/app/Services/BootstrapService.php @@ -0,0 +1,89 @@ +environment('production') && ! file_exists($filePath)) { + throw new RuntimeException( + "OAuth key file {$filePath} is missing. Please generate OAuth keys." + ); + } + + $permissions = self::getPermissions($filePath); + + $isSafe = ($permissions === '600' || $permissions === '660'); + + if ($isSafe) { + return; + } + + $fixed = @chmod($filePath, 0660); + + if ($fixed) { + return; + } + + throw new RuntimeException( + "File {$filePath} has bad permissions ({$permissions}). "."Should be 600 or 660. Run this command: chmod 660 {$filePath}" + ); + } + + protected static function getPermissions(string $filePath): string + { + $permissionNumber = fileperms($filePath) & 0777; + + return decoct($permissionNumber); + } + + protected static function formatPerms(int $perms): string + { + return sprintf('%04o', $perms); + } +}