Skip to content

Commit 6d75b18

Browse files
authored
Deploy to production (#72)
* feat: Generate OG image for each package * feat: Generate OG image for each package (#71) * fix: Chromium is not found * fix: Chromium issue * fix: Package SEO wrong URLs * cd: Add chromium to environment variables
1 parent e77146b commit 6d75b18

File tree

18 files changed

+1198
-28
lines changed

18 files changed

+1198
-28
lines changed

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ AWS_USE_PATH_STYLE_ENDPOINT=false
6565

6666
VITE_APP_NAME="${APP_NAME}"
6767

68-
6968
# --------------------------------------------------------------------------
7069
# 📌 Laravel Model Status Configuration
7170
# These variables define the behavior of the package.
@@ -101,3 +100,6 @@ TURNSTILE_SITE_KEY=""
101100
TURNSTILE_SECRET_KEY=""
102101
VITE_TURNSTILE_SITE_KEY="${TURNSTILE_SITE_KEY}"
103102
VITE_TURNSTILE_SECRET_KEY="${TURNSTILE_SECRET_KEY}"
103+
# --------------------------------------------------------------------------
104+
CHROME_PATH=
105+
BROWSERSHOT_USER_DATA_DIR=

.github/workflows/cd.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ jobs:
121121
TURNSTILE_SECRET_KEY=${{ secrets.TURNSTILE_SECRET_KEY }}
122122
VITE_TURNSTILE_SITE_KEY=${{ secrets.TURNSTILE_SITE_KEY }}
123123
VITE_TURNSTILE_SECRET_KEY=${{ secrets.TURNSTILE_SECRET_KEY }}
124+
125+
CHROME_PATH=${{ secrets.CHROME_PATH }}
126+
BROWSERSHOT_USER_DATA_DIR=${{ secrets.BROWSERSHOT_USER_DATA_DIR }}
124127
EOF
125128
126129
deploy:

.github/workflows/update-env.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ jobs:
111111
TURNSTILE_SECRET_KEY=${{ secrets.TURNSTILE_SECRET_KEY }}
112112
VITE_TURNSTILE_SITE_KEY=${{ secrets.TURNSTILE_SITE_KEY }}
113113
VITE_TURNSTILE_SECRET_KEY=${{ secrets.TURNSTILE_SECRET_KEY }}
114+
115+
CHROME_PATH=${{ secrets.CHROME_PATH }}
116+
BROWSERSHOT_USER_DATA_DIR=${{ secrets.BROWSERSHOT_USER_DATA_DIR }}
114117
EOF
115118
116119
- name: Refresh configurations
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Actions;
6+
7+
use App\Models\Package;
8+
use Illuminate\Support\Facades\Log;
9+
use Illuminate\Support\Str;
10+
use Spatie\Browsershot\Browsershot;
11+
12+
class GeneratePackageOgImageAction
13+
{
14+
public function handle(Package $package): void
15+
{
16+
try {
17+
$tempUrl = route('og-images.package', ['package' => $package->id]);
18+
19+
// Target file
20+
$storagePath = storage_path('app/public/package-og-images');
21+
$this->ensureDir($storagePath);
22+
23+
$filename = Str::slug($package->name) . '-' . Str::random(8) . '.jpg';
24+
$fullPath = $storagePath . '/' . $filename;
25+
26+
// Binaries
27+
$nodePath = $this->which('node') ?? '/usr/bin/node';
28+
$npmPath = $this->which('npm') ?? '/usr/bin/npm';
29+
30+
// Writable Chrome profile/cache dir
31+
$userDataDir = rtrim((string) env('BROWSERSHOT_USER_DATA_DIR', '/var/www/browsershot-cache'), '/');
32+
$this->ensureDir($userDataDir);
33+
34+
// Ensure the env seen by the child process points to our writable dir
35+
foreach (['HOME', 'XDG_CONFIG_HOME', 'XDG_CACHE_HOME'] as $var) {
36+
putenv($var . '=' . $userDataDir);
37+
$_ENV[$var] = $userDataDir;
38+
$_SERVER[$var] = $userDataDir;
39+
}
40+
41+
// Prefer explicit path via env; otherwise auto-detect
42+
$chromePath = env('CHROME_PATH') ?: $this->detectChromePath();
43+
44+
// IMPORTANT: do NOT prefix with `--` here; Browsershot will add them
45+
$chromiumArgs = [
46+
'headless=new',
47+
'no-sandbox',
48+
'disable-setuid-sandbox',
49+
'disable-dev-shm-usage',
50+
'disable-gpu',
51+
'no-zygote',
52+
'single-process',
53+
'hide-scrollbars',
54+
"user-data-dir={$userDataDir}",
55+
'no-first-run',
56+
'no-default-browser-check',
57+
'disable-crash-reporter',
58+
"data-path={$userDataDir}/data",
59+
"disk-cache-dir={$userDataDir}/cache",
60+
];
61+
62+
$browserShot = Browsershot::url($tempUrl)
63+
->setNodeBinary($nodePath)
64+
->setNpmBinary($npmPath)
65+
->windowSize(1200, 630)
66+
->waitUntilNetworkIdle()
67+
->setScreenshotType('jpeg', 90)
68+
->addChromiumArguments($chromiumArgs);
69+
70+
if ($chromePath) {
71+
$browserShot->setChromePath($chromePath);
72+
}
73+
74+
$browserShot->save($fullPath);
75+
76+
if (is_file($fullPath)) {
77+
$package->addMedia($fullPath)
78+
->usingName('og-image')
79+
->usingFileName($filename)
80+
->toMediaCollection('og-images', 'package-og-images');
81+
82+
Log::info('OG Image Generated: ' . $package->getFirstMediaUrl('og-images'));
83+
} else {
84+
Log::error('OG image file missing after save', ['path' => $fullPath]);
85+
}
86+
} catch (\Throwable $e) {
87+
Log::error('OG Image Generation Error: ' . $e->getMessage(), ['exception' => $e]);
88+
throw $e;
89+
}
90+
}
91+
92+
private function ensureDir(string $dir): void
93+
{
94+
if (! is_dir($dir)) {
95+
@mkdir($dir, 0755, true);
96+
}
97+
}
98+
99+
private function which(string $bin): ?string
100+
{
101+
$path = @exec(sprintf('command -v %s', escapeshellarg($bin)));
102+
return $path ?: null;
103+
}
104+
105+
private function detectChromePath(): ?string
106+
{
107+
$candidates = [
108+
'/usr/bin/google-chrome',
109+
'/usr/bin/google-chrome-stable',
110+
'/usr/local/bin/google-chrome',
111+
'/usr/bin/chromium',
112+
'/usr/bin/chromium-browser',
113+
];
114+
115+
foreach ($candidates as $path) {
116+
if (is_file($path) && is_executable($path)) {
117+
return $path;
118+
}
119+
}
120+
121+
return null;
122+
}
123+
}

app/Filament/Resources/PackageResource.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public static function getPages(): array
143143
return [
144144
'index' => Pages\ListPackages::route('/'),
145145
'create' => Pages\CreatePackage::route('/create'),
146-
// 'edit' => Pages\EditPackage::route('/{record}/edit'),
146+
'edit' => Pages\EditPackage::route('/{record}/edit'),
147147
];
148148
}
149149
}

app/Filament/Resources/PackageResource/Pages/CreatePackage.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,30 @@
22

33
namespace App\Filament\Resources\PackageResource\Pages;
44

5+
use App\Actions\GeneratePackageOgImageAction;
56
use App\Filament\Resources\PackageResource;
7+
use Filament\Notifications\Notification;
68
use Filament\Resources\Pages\CreateRecord;
79

810
class CreatePackage extends CreateRecord
911
{
1012
protected static string $resource = PackageResource::class;
13+
14+
protected function afterCreate(): void
15+
{
16+
try {
17+
app(GeneratePackageOgImageAction::class)->handle($this->record);
18+
19+
Notification::make()
20+
->title('OG Image Generated')
21+
->success()
22+
->send();
23+
} catch (\Exception $e) {
24+
Notification::make()
25+
->title('Failed to generate OG image')
26+
->body($e->getMessage())
27+
->danger()
28+
->send();
29+
}
30+
}
1131
}

app/Filament/Resources/PackageResource/Pages/EditPackage.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
namespace App\Filament\Resources\PackageResource\Pages;
44

5+
use App\Actions\GeneratePackageOgImageAction;
56
use App\Filament\Resources\PackageResource;
67
use Filament\Actions;
8+
use Filament\Notifications\Notification;
79
use Filament\Resources\Pages\EditRecord;
10+
use Illuminate\Support\Facades\Log;
811

912
class EditPackage extends EditRecord
1013
{
@@ -16,4 +19,29 @@ protected function getHeaderActions(): array
1619
Actions\DeleteAction::make(),
1720
];
1821
}
22+
23+
protected function afterSave(): void
24+
{
25+
Log::info('EditPackage afterSave triggered', ['package_id' => $this->record->id]);
26+
27+
try {
28+
app(GeneratePackageOgImageAction::class)->handle($this->record);
29+
30+
Notification::make()
31+
->title('OG Image Regenerated')
32+
->success()
33+
->send();
34+
} catch (\Exception $e) {
35+
Log::error('OG Image Generation Error in afterSave', [
36+
'package_id' => $this->record->id,
37+
'error' => $e->getMessage(),
38+
]);
39+
40+
Notification::make()
41+
->title('Failed to regenerate OG image')
42+
->body($e->getMessage())
43+
->danger()
44+
->send();
45+
}
46+
}
1947
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Models\Package;
6+
7+
class OgImageController extends Controller
8+
{
9+
/**
10+
* Render the OG image for a package
11+
*
12+
* @return \Illuminate\Contracts\View\View
13+
*/
14+
public function package(Package $package)
15+
{
16+
return view('og-images.package', compact('package'));
17+
}
18+
}

app/Http/Controllers/PackageController.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
namespace App\Http\Controllers;
44

5+
use App\Actions\GeneratePackageOgImageAction;
56
use App\Http\Resources\Admin\CategoryResource;
67
use App\Http\Resources\PackageResource;
78
use App\Models\Category;
89
use App\Models\Package;
910
use App\Queries\PackageQuery;
1011
use App\Services\GitHubService;
1112
use Illuminate\Http\Request;
13+
use Illuminate\Support\Facades\Log;
1214
use Inertia\Inertia;
1315

1416
class PackageController extends Controller
@@ -41,11 +43,25 @@ public function index(Request $request)
4143
public function show(string $slug)
4244
{
4345
$package = Package::query()
46+
->with('media')
4447
->select('id', 'index_id', 'name', 'slug', 'description', 'repository_url', 'meta_title', 'meta_description', 'language', 'stars', 'owner', 'owner_avatar', 'created_at')
45-
->with(['categories', 'indexes'])
48+
->with(['categories', 'indexes', 'media'])
4649
->where('slug', $slug)
4750
->firstOrFail();
4851

52+
if (! $package->hasMedia('og-images')) {
53+
try {
54+
app(GeneratePackageOgImageAction::class)->handle($package);
55+
56+
$package->load('media');
57+
} catch (\Exception $e) {
58+
Log::error('Failed to generate OG image in controller', [
59+
'package_id' => $package->id,
60+
'error' => $e->getMessage(),
61+
]);
62+
}
63+
}
64+
4965
return Inertia::render('Package', [
5066
'package' => new PackageResource($package),
5167
'readme' => Inertia::defer(fn () => GitHubService::fetchReadmeContent($package->repository_url)),

app/Http/Resources/PackageResource.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public function toArray(Request $request): array
3131
'stars' => $this->stars,
3232
'owner' => $this->owner,
3333
'owner_avatar' => $this->owner_avatar,
34+
'og_image' => $this->getFirstMediaUrl('og-images'),
3435
'status' => $this->status,
3536
'created_at' => $this->created_at,
3637
'updated_at' => $this->updated_at,

0 commit comments

Comments
 (0)