From 6f13dd2030a493bcd4cbd1f01a3e99d9b1d8b172 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 31 Jan 2026 12:30:24 +1030 Subject: [PATCH 01/10] Fix #425 - Add boot-time OAuth key permission check - Add BootstrapService with ensureBoottimeEnvironment() for startup checks - Auto-fix OAuth key permissions to 660 if possible - Throw fatal error with clear instructions if chmod fails - Extensible for future environment checks --- app/Providers/AppServiceProvider.php | 2 ++ app/Services/BootstrapService.php | 50 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 app/Services/BootstrapService.php diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e2711f35..08cfe55d 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -40,6 +40,8 @@ public function register(): void */ public function boot(): void { + \App\Services\BootstrapService::ensureBoottimeEnvironment(); + RateLimiter::for('api', function (Request $request) { $enabled = config('loops.api.rate_limits.enabled'); if (! $enabled) { diff --git a/app/Services/BootstrapService.php b/app/Services/BootstrapService.php new file mode 100644 index 00000000..17279042 --- /dev/null +++ b/app/Services/BootstrapService.php @@ -0,0 +1,50 @@ + Date: Sat, 31 Jan 2026 16:53:52 +1030 Subject: [PATCH 02/10] Update BootstrapService.php --- app/Services/BootstrapService.php | 62 ++++++++++++++++--------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/app/Services/BootstrapService.php b/app/Services/BootstrapService.php index 17279042..d499fb08 100644 --- a/app/Services/BootstrapService.php +++ b/app/Services/BootstrapService.php @@ -1,50 +1,54 @@ Date: Sat, 31 Jan 2026 16:55:00 +1030 Subject: [PATCH 03/10] Update BootstrapService.php --- app/Services/BootstrapService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/BootstrapService.php b/app/Services/BootstrapService.php index d499fb08..7e91b308 100644 --- a/app/Services/BootstrapService.php +++ b/app/Services/BootstrapService.php @@ -35,7 +35,7 @@ protected static function checkOneFile(string $filePath): void return; } - $fixed = @chmod($filePath, 0660); // Try to fix it by setting to 660 + $fixed = @chmod($filePath, 0660); // Try to change the chmod to 660 if ($fixed) { return; From e41de43f40b6ffb0610e1a983d24dfb5c52e1839 Mon Sep 17 00:00:00 2001 From: Shlee Date: Sat, 31 Jan 2026 16:55:25 +1030 Subject: [PATCH 04/10] Update BootstrapService.php --- app/Services/BootstrapService.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Services/BootstrapService.php b/app/Services/BootstrapService.php index 7e91b308..840ec66a 100644 --- a/app/Services/BootstrapService.php +++ b/app/Services/BootstrapService.php @@ -15,11 +15,11 @@ protected static function checkOAuthKeyPermissions(): void $privateKeyPath = storage_path('oauth-private.key'); $publicKeyPath = storage_path('oauth-public.key'); - self::checkOneFile($privateKeyPath); - self::checkOneFile($publicKeyPath); + self::checkOAuthFile($privateKeyPath); + self::checkOAuthFile($publicKeyPath); } - protected static function checkOneFile(string $filePath): void + protected static function checkOAuthFile(string $filePath): void { if (!file_exists($filePath)) { throw new RuntimeException( From 0c83c5c7a00d449b58659287a0c46f3dfb447b64 Mon Sep 17 00:00:00 2001 From: Shlee Date: Sat, 31 Jan 2026 17:01:35 +1030 Subject: [PATCH 05/10] Update BootstrapService.php --- app/Services/BootstrapService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/BootstrapService.php b/app/Services/BootstrapService.php index 840ec66a..24d5ce00 100644 --- a/app/Services/BootstrapService.php +++ b/app/Services/BootstrapService.php @@ -21,7 +21,7 @@ protected static function checkOAuthKeyPermissions(): void protected static function checkOAuthFile(string $filePath): void { - if (!file_exists($filePath)) { + if (app()->environment('production') && ! file_exists($filePath)) { throw new RuntimeException( "OAuth key file {$filePath} is missing. Please generate OAuth keys." ); From b89469adb91483a7e4eb4790a909464ce378a049 Mon Sep 17 00:00:00 2001 From: Shlee Date: Sat, 31 Jan 2026 17:05:34 +1030 Subject: [PATCH 06/10] Update BootstrapService.php --- app/Services/BootstrapService.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/Services/BootstrapService.php b/app/Services/BootstrapService.php index 24d5ce00..b489243d 100644 --- a/app/Services/BootstrapService.php +++ b/app/Services/BootstrapService.php @@ -21,6 +21,10 @@ protected static function checkOAuthKeyPermissions(): void protected static function checkOAuthFile(string $filePath): void { + if (app()->runningInConsole()) { + return; + } + if (app()->environment('production') && ! file_exists($filePath)) { throw new RuntimeException( "OAuth key file {$filePath} is missing. Please generate OAuth keys." From 1db6d4db7f8957e03afd1525d5b5b61b924c74e2 Mon Sep 17 00:00:00 2001 From: Shlee Date: Sat, 31 Jan 2026 17:05:55 +1030 Subject: [PATCH 07/10] Update BootstrapService.php --- app/Services/BootstrapService.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Services/BootstrapService.php b/app/Services/BootstrapService.php index b489243d..ce0e4bf9 100644 --- a/app/Services/BootstrapService.php +++ b/app/Services/BootstrapService.php @@ -7,6 +7,11 @@ class BootstrapService { public static function ensureBoottimeEnvironment(): void { + + if (app()->runningInConsole()) { + return; + } + self::checkOAuthKeyPermissions(); } @@ -21,10 +26,6 @@ protected static function checkOAuthKeyPermissions(): void protected static function checkOAuthFile(string $filePath): void { - if (app()->runningInConsole()) { - return; - } - if (app()->environment('production') && ! file_exists($filePath)) { throw new RuntimeException( "OAuth key file {$filePath} is missing. Please generate OAuth keys." From 4f754d42ee02e7c43e1810effb2159ececa34f59 Mon Sep 17 00:00:00 2001 From: Shlee Date: Sat, 31 Jan 2026 17:18:01 +1030 Subject: [PATCH 08/10] Update AppServiceProvider.php --- app/Providers/AppServiceProvider.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 08cfe55d..e2711f35 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -40,8 +40,6 @@ public function register(): void */ public function boot(): void { - \App\Services\BootstrapService::ensureBoottimeEnvironment(); - RateLimiter::for('api', function (Request $request) { $enabled = config('loops.api.rate_limits.enabled'); if (! $enabled) { From d6d331327eb26ecbde3cc9d7948b3bbaff855447 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 1 Feb 2026 13:31:54 +1030 Subject: [PATCH 09/10] moved check to composer --- DOCKER_COMPOSE_SETUP.md | 7 ++- .../Commands/ComposerPostInstallCommand.php | 2 + .../Commands/EnsureBoottimeEnvCommand.php | 40 +++++++++++++++ app/Services/BootstrapService.php | 50 +++++++++++++++---- 4 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 app/Console/Commands/EnsureBoottimeEnvCommand.php 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 index ce0e4bf9..f29c0de0 100644 --- a/app/Services/BootstrapService.php +++ b/app/Services/BootstrapService.php @@ -1,25 +1,49 @@ runningInConsole()) { return; - } + } - self::checkOAuthKeyPermissions(); + $perms = fileperms($path) & 0777; + + if ($perms === 0755) { + return; + } + + if (@chmod($path, 0755)) { + return; + } + + throw new RuntimeException( + "Avatar temp directory \"{$path}\" has incorrect permissions (" . self::formatPerms($perms) . "). " . + "Expected 0755. Please run: chmod 755 {$path}" + ); } protected static function checkOAuthKeyPermissions(): void { $privateKeyPath = storage_path('oauth-private.key'); $publicKeyPath = storage_path('oauth-public.key'); - + self::checkOAuthFile($privateKeyPath); self::checkOAuthFile($publicKeyPath); } @@ -33,15 +57,15 @@ protected static function checkOAuthFile(string $filePath): void } $permissions = self::getPermissions($filePath); - + $isSafe = ($permissions === '600' || $permissions === '660'); - + if ($isSafe) { return; } - $fixed = @chmod($filePath, 0660); // Try to change the chmod to 660 - + $fixed = @chmod($filePath, 0660); + if ($fixed) { return; } @@ -53,7 +77,13 @@ protected static function checkOAuthFile(string $filePath): void protected static function getPermissions(string $filePath): string { - $permissionNumber = fileperms($filePath) & 0777;// fileperms() returns a number with extra info we don't need & 0777 removes that extra info, leaving just the permissions - return decoct($permissionNumber); // Convert to a readable string like "644" or "600" (decoct converts decimal to octal) + $permissionNumber = fileperms($filePath) & 0777; + + return decoct($permissionNumber); + } + + protected static function formatPerms(int $perms): string + { + return sprintf('%04o', $perms); } } From 0f36991bb76f96d0733afd525788889bfc02a3d7 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 1 Feb 2026 13:35:38 +1030 Subject: [PATCH 10/10] pint --- app/Services/BootstrapService.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Services/BootstrapService.php b/app/Services/BootstrapService.php index f29c0de0..fefa4c6f 100644 --- a/app/Services/BootstrapService.php +++ b/app/Services/BootstrapService.php @@ -34,7 +34,7 @@ protected static function ensureAvatarTempDirectory(): void } throw new RuntimeException( - "Avatar temp directory \"{$path}\" has incorrect permissions (" . self::formatPerms($perms) . "). " . + "Avatar temp directory \"{$path}\" has incorrect permissions (".self::formatPerms($perms).'). '. "Expected 0755. Please run: chmod 755 {$path}" ); } @@ -71,7 +71,7 @@ protected static function checkOAuthFile(string $filePath): void } throw new RuntimeException( - "File {$filePath} has bad permissions ({$permissions}). " . "Should be 600 or 660. Run this command: chmod 660 {$filePath}" + "File {$filePath} has bad permissions ({$permissions}). "."Should be 600 or 660. Run this command: chmod 660 {$filePath}" ); }