Skip to content

Commit 8aa5cd5

Browse files
shleeableYour Name
andauthored
Fix #425 - Add boot-time permission checks - OAuth key and Avatar-temp (#426)
* 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 * Update BootstrapService.php * Update BootstrapService.php * Update BootstrapService.php * Update BootstrapService.php * Update BootstrapService.php * Update BootstrapService.php * Update AppServiceProvider.php * moved check to composer * pint --------- Co-authored-by: Your Name <you@example.com>
1 parent 9efb7d5 commit 8aa5cd5

4 files changed

Lines changed: 137 additions & 1 deletion

File tree

DOCKER_COMPOSE_SETUP.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ This setup uses `serversideup/php:8.4-fpm-nginx` as the base image and is design
6363
docker compose exec loops php artisan passport:keys
6464
```
6565

66-
8. **Create admin user:**
66+
8. **Ensure boot-time environment:**
67+
```bash
68+
docker compose exec loops php artisan app:ensure-boottime
69+
```
70+
71+
9. **Create admin user:**
6772
```bash
6873
docker compose exec loops php artisan create-admin-account
6974
```

app/Console/Commands/ComposerPostInstallCommand.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Console\Commands;
44

5+
use App\Services\BootstrapService;
56
use App\Services\SettingsFileService;
67
use Illuminate\Console\Command;
78
use Illuminate\Support\Facades\Cache;
@@ -33,6 +34,7 @@ public function handle()
3334

3435
Cache::forget('version_check_result');
3536
app(SettingsFileService::class)->flush();
37+
BootstrapService::ensureBoottimeEnvironment();
3638

3739
$this->info('Post-install tasks completed successfully.');
3840
} catch (\Exception $e) {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace App\Console\Commands;
4+
5+
use App\Services\BootstrapService;
6+
use Illuminate\Console\Command;
7+
8+
class EnsureBoottimeEnvCommand extends Command
9+
{
10+
/**
11+
* The name and signature of the console command.
12+
*
13+
* @var string
14+
*/
15+
protected $signature = 'app:ensure-boottime';
16+
17+
/**
18+
* The console command description.
19+
*
20+
* @var string
21+
*/
22+
protected $description = 'Ensure boot-time environment checks pass (directories, permissions, etc.)';
23+
24+
/**
25+
* Execute the console command.
26+
*/
27+
public function handle(): int
28+
{
29+
try {
30+
BootstrapService::ensureBoottimeEnvironment();
31+
$this->info('All boot-time environment checks passed.');
32+
33+
return Command::SUCCESS;
34+
} catch (\RuntimeException $e) {
35+
$this->error($e->getMessage());
36+
37+
return Command::FAILURE;
38+
}
39+
}
40+
}

app/Services/BootstrapService.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
use Illuminate\Support\Facades\File;
6+
use RuntimeException;
7+
8+
class BootstrapService
9+
{
10+
public static function ensureBoottimeEnvironment(): void
11+
{
12+
self::checkOAuthKeyPermissions();
13+
self::ensureAvatarTempDirectory();
14+
}
15+
16+
protected static function ensureAvatarTempDirectory(): void
17+
{
18+
$path = storage_path('app/avatar-temp');
19+
20+
if (! File::isDirectory($path)) {
21+
File::makeDirectory($path, 0755, true);
22+
23+
return;
24+
}
25+
26+
$perms = fileperms($path) & 0777;
27+
28+
if ($perms === 0755) {
29+
return;
30+
}
31+
32+
if (@chmod($path, 0755)) {
33+
return;
34+
}
35+
36+
throw new RuntimeException(
37+
"Avatar temp directory \"{$path}\" has incorrect permissions (".self::formatPerms($perms).'). '.
38+
"Expected 0755. Please run: chmod 755 {$path}"
39+
);
40+
}
41+
42+
protected static function checkOAuthKeyPermissions(): void
43+
{
44+
$privateKeyPath = storage_path('oauth-private.key');
45+
$publicKeyPath = storage_path('oauth-public.key');
46+
47+
self::checkOAuthFile($privateKeyPath);
48+
self::checkOAuthFile($publicKeyPath);
49+
}
50+
51+
protected static function checkOAuthFile(string $filePath): void
52+
{
53+
if (app()->environment('production') && ! file_exists($filePath)) {
54+
throw new RuntimeException(
55+
"OAuth key file {$filePath} is missing. Please generate OAuth keys."
56+
);
57+
}
58+
59+
$permissions = self::getPermissions($filePath);
60+
61+
$isSafe = ($permissions === '600' || $permissions === '660');
62+
63+
if ($isSafe) {
64+
return;
65+
}
66+
67+
$fixed = @chmod($filePath, 0660);
68+
69+
if ($fixed) {
70+
return;
71+
}
72+
73+
throw new RuntimeException(
74+
"File {$filePath} has bad permissions ({$permissions}). "."Should be 600 or 660. Run this command: chmod 660 {$filePath}"
75+
);
76+
}
77+
78+
protected static function getPermissions(string $filePath): string
79+
{
80+
$permissionNumber = fileperms($filePath) & 0777;
81+
82+
return decoct($permissionNumber);
83+
}
84+
85+
protected static function formatPerms(int $perms): string
86+
{
87+
return sprintf('%04o', $perms);
88+
}
89+
}

0 commit comments

Comments
 (0)