Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion app/Console/Commands/Environment/AppSettingsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,29 @@

namespace App\Console\Commands\Environment;

use App\Services\Environment\InstallationHealthService;
use App\Traits\Commands\DisplaysEnvironmentChecks;
use Illuminate\Console\Command;

class AppSettingsCommand extends Command
{
use DisplaysEnvironmentChecks;

protected $description = 'Configure basic environment settings for the Panel.';

protected $signature = 'p:environment:setup';

public function handle(): void
public function handle(InstallationHealthService $health): int
{
$results = $health->systemRequirements();
$this->displayEnvironmentChecks($results);

if ($health->hasFailures($results)) {
Comment thread
LegacyAngel2K9 marked this conversation as resolved.
$this->error(trans('commands.environment_check.preflight_failed'));

return self::FAILURE;
}

$path = base_path('.env');
if (!file_exists($path)) {
$this->comment('Copying example .env file');
Expand All @@ -28,5 +41,7 @@ public function handle(): void

$this->comment('Caching components & icons');
$this->call('filament:optimize');

return self::SUCCESS;
}
}
38 changes: 38 additions & 0 deletions app/Console/Commands/Environment/EnvironmentHealthCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace App\Console\Commands\Environment;

use App\Services\Environment\InstallationHealthService;
use App\Traits\Commands\DisplaysEnvironmentChecks;
use Illuminate\Console\Command;

class EnvironmentHealthCommand extends Command
{
use DisplaysEnvironmentChecks;

protected $description = 'Validate the complete Panel installation after an install or update.';

protected $signature = 'p:environment:health
{--skip-queue : Skip the active queue-worker probe.}
{--queue-timeout=10 : Seconds to wait for the queue probe.}';

public function handle(InstallationHealthService $health): int
{
$results = $health->completeInstallation(
includeQueue: !$this->option('skip-queue'),
queueTimeoutSeconds: max(0, (int) $this->option('queue-timeout')),
);

$this->displayEnvironmentChecks($results);

if ($health->hasFailures($results)) {
$this->error(trans('commands.environment_check.health_failed'));

return self::FAILURE;
}

$this->info(trans('commands.environment_check.health_passed'));

return self::SUCCESS;
}
}
51 changes: 51 additions & 0 deletions app/Console/Commands/Environment/EnvironmentPreflightCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace App\Console\Commands\Environment;

use App\Services\Environment\InstallationHealthService;
use App\Traits\Commands\DisplaysEnvironmentChecks;
use Illuminate\Console\Command;

class EnvironmentPreflightCommand extends Command
{
use DisplaysEnvironmentChecks;

protected $description = 'Verify server requirements before installing or reconfiguring the Panel.';

protected $signature = 'p:environment:preflight
{--with-database : Also verify the currently configured database connection.}
{--with-queue : Also dispatch a real job to verify the configured queue worker.}
{--queue-timeout=10 : Seconds to wait for the queue probe.}';

public function handle(InstallationHealthService $health): int
{
$results = $health->systemRequirements();

if ($this->option('with-database')) {
$driver = (string) config('database.default');
$results[] = $health->databaseDriverExtension($driver);
$results[] = $health->database();
}

if ($this->option('with-queue')) {
$results[] = $health->queueWorker($this->queueTimeout());
}

$this->displayEnvironmentChecks($results);

if ($health->hasFailures($results)) {
$this->error(trans('commands.environment_check.preflight_failed'));

return self::FAILURE;
}

$this->info(trans('commands.environment_check.preflight_passed'));

return self::SUCCESS;
}

private function queueTimeout(): int
{
return max(0, (int) $this->option('queue-timeout'));
}
}
50 changes: 50 additions & 0 deletions app/Console/Commands/Maintenance/FinishUpdateCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace App\Console\Commands\Maintenance;

use App\Services\Environment\InstallationHealthService;
use App\Services\Maintenance\UpdateSnapshotService;
use App\Traits\Commands\DisplaysEnvironmentChecks;
use Illuminate\Console\Command;

class FinishUpdateCommand extends Command
{
use DisplaysEnvironmentChecks;

protected $description = 'Validate the Panel after an update and show rollback guidance when validation fails.';

protected $signature = 'p:maintenance:finish-update
{--snapshot= : Pre-update snapshot directory. The newest snapshot is used by default.}
{--skip-queue : Skip the active queue-worker probe.}
{--queue-timeout=10 : Seconds to wait for the queue probe.}';

public function handle(InstallationHealthService $health, UpdateSnapshotService $snapshots): int
{
$results = $health->completeInstallation(
includeQueue: !$this->option('skip-queue'),
queueTimeoutSeconds: max(0, (int) $this->option('queue-timeout')),
);
$this->displayEnvironmentChecks($results);

if (!$health->hasFailures($results)) {
$this->info(trans('commands.update.healthy'));

return self::SUCCESS;
}

$snapshotOption = $this->option('snapshot');
$snapshot = is_string($snapshotOption) && $snapshotOption !== ''
? $snapshots->fromPath($snapshotOption)
: $snapshots->latest();

if ($snapshot === null) {
$this->error(trans('commands.update.snapshot_missing'));

return self::FAILURE;
}

$this->error(trans('commands.update.unhealthy', ['path' => $snapshot->rollbackGuide]));

return self::FAILURE;
}
}
71 changes: 71 additions & 0 deletions app/Console/Commands/Maintenance/PrepareUpdateCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace App\Console\Commands\Maintenance;

use App\Services\Environment\InstallationHealthService;
use App\Services\Maintenance\UpdateCompatibilityService;
use App\Services\Maintenance\UpdateSnapshotService;
use App\Traits\Commands\DisplaysEnvironmentChecks;
use Illuminate\Console\Command;
use Throwable;

class PrepareUpdateCommand extends Command
{
use DisplaysEnvironmentChecks;

protected $description = 'Capture the current Panel state and verify the target release before an update.';

protected $signature = 'p:maintenance:prepare-update
{--source= : Extracted target release directory containing composer.json and composer.lock.}
{--target-version= : Target Panel version recorded in the snapshot metadata.}
{--skip-queue : Skip the active queue-worker probe.}
{--queue-timeout=10 : Seconds to wait for the queue probe.}';

public function handle(
InstallationHealthService $health,
UpdateCompatibilityService $compatibility,
UpdateSnapshotService $snapshots,
): int {
$source = $this->option('source');
if (!is_string($source) || $source === '') {
$this->error(trans('commands.update.source_required'));

return self::FAILURE;
}

$results = $health->completeInstallation(
includeQueue: !$this->option('skip-queue'),
queueTimeoutSeconds: max(0, (int) $this->option('queue-timeout')),
);
$this->displayEnvironmentChecks($results);

if ($health->hasFailures($results)) {
$this->error(trans('commands.update.preparation_failed'));

return self::FAILURE;
}

$compatibilityResult = $compatibility->check($source);
$this->displayEnvironmentChecks([$compatibilityResult]);

if ($compatibilityResult->failed()) {
$this->error(trans('commands.update.preparation_failed'));

return self::FAILURE;
}

try {
$snapshot = $snapshots->capture($this->option('target-version'));
} catch (Throwable $exception) {
$this->error($exception->getMessage());

return self::FAILURE;
}

$this->info(trans('commands.update.snapshot_created', ['path' => $snapshot->path]));
$this->line(trans('commands.update.database_guidance', ['guidance' => $snapshot->databaseGuidance]));
$this->info(trans('commands.update.ready'));

return self::SUCCESS;
}
}
2 changes: 2 additions & 0 deletions app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Database\Console\PruneCommand;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Spatie\Health\Commands\DispatchQueueCheckJobsCommand;
use Spatie\Health\Commands\RunHealthChecksCommand;
use Spatie\Health\Commands\ScheduleCheckHeartbeatCommand;

Expand Down Expand Up @@ -60,6 +61,7 @@ protected function schedule(Schedule $schedule): void
}

$schedule->command(ScheduleCheckHeartbeatCommand::class)->everyMinute();
$schedule->command(DispatchQueueCheckJobsCommand::class)->everyMinute();
$schedule->command(RunHealthChecksCommand::class)->everyFiveMinutes();
}
}
15 changes: 15 additions & 0 deletions app/Enums/EnvironmentCheckStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace App\Enums;

enum EnvironmentCheckStatus: string
{
case Passed = 'passed';
case Warning = 'warning';
case Failed = 'failed';

public function isFailure(): bool
{
return $this === self::Failed;
}
}
31 changes: 31 additions & 0 deletions app/Jobs/QueueWorkerProbeJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\File;

class QueueWorkerProbeJob implements ShouldQueue
{
use Dispatchable;
use Queueable;

public function __construct(private readonly string $token) {}

public function handle(): void
{
$path = self::markerPath($this->token);

File::ensureDirectoryExists(dirname($path));
File::put($path, now()->toIso8601String());
}

public static function markerPath(string $token): string
{
$safeToken = preg_replace('/[^a-zA-Z0-9-]/', '', $token);

return storage_path("framework/cache/pelican-queue-probes/{$safeToken}");
}
}
Loading
Loading