diff --git a/.github/workflows/pint.yaml b/.github/workflows/pint.yaml index 1fa5752..1577081 100644 --- a/.github/workflows/pint.yaml +++ b/.github/workflows/pint.yaml @@ -20,4 +20,4 @@ jobs: run: composer global require laravel/pint - name: Run Pint - run: pint + run: pint --test diff --git a/app/Console/Commands/PerformSiteHealthCheck.php b/app/Console/Commands/PerformSiteHealthCheck.php new file mode 100644 index 0000000..f8c63ad --- /dev/null +++ b/app/Console/Commands/PerformSiteHealthCheck.php @@ -0,0 +1,34 @@ +input->getArgument('siteId')) + ); + } +} diff --git a/app/Enum/HttpMethod.php b/app/Enum/HttpMethod.php new file mode 100644 index 0000000..c9a3d02 --- /dev/null +++ b/app/Enum/HttpMethod.php @@ -0,0 +1,13 @@ +header('PHP_AUTH_USER', null) && $request->header('PHP_AUTH_PW', null)) { + $username = $request->header('PHP_AUTH_USER'); + $password = $request->header('PHP_AUTH_PW'); + + if ($username === config('horizon.basic_auth.username') + && $password === config('horizon.basic_auth.password')) { + $authenticationHasPassed = true; + } + } + + if ($authenticationHasPassed === false) { + return response()->make('Invalid credentials.', 401, ['WWW-Authenticate' => 'Basic']); + } + + return $next($request); + } +} diff --git a/app/Jobs/CheckSiteHealth.php b/app/Jobs/CheckSiteHealth.php new file mode 100644 index 0000000..3e0ab36 --- /dev/null +++ b/app/Jobs/CheckSiteHealth.php @@ -0,0 +1,46 @@ +site->connection; + + $response = $connection->checkHealth(); + + $healthData = collect($response); + + // Write updated data to DB + $this->site->update( + $healthData->only([ + 'php_version', + 'db_type', + 'db_version', + 'cms_version', + 'server_os' + ])->toArray() + ); + } +} diff --git a/app/Jobs/UpdateSite.php b/app/Jobs/UpdateSite.php new file mode 100644 index 0000000..00a69c2 --- /dev/null +++ b/app/Jobs/UpdateSite.php @@ -0,0 +1,29 @@ + 'bool' ]; } + + public function getUrlAttribute(string $value): string + { + return rtrim($value, "/") . "/"; + } + + public function getConnectionAttribute(): SiteConnectionService + { + return new SiteConnectionService($this->url, $this->key); + } } diff --git a/app/Models/User.php b/app/Models/User.php deleted file mode 100644 index 802d005..0000000 --- a/app/Models/User.php +++ /dev/null @@ -1,32 +0,0 @@ - 'datetime', - 'password' => 'hashed', - ]; - } -} diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php new file mode 100644 index 0000000..d98d242 --- /dev/null +++ b/app/Providers/HorizonServiceProvider.php @@ -0,0 +1,22 @@ +performWebserviceRequest( + HttpMethod::GET, + WebserviceEndpoint::HEALTH_CHECK + ); + + // Perform a sanity check + if (empty($healthData['cms_version'])) { + throw new \Exception("Invalid health response content"); + } + + return $healthData; + } + + public function performExtractionRequest(array $data): array + { + $request = new Request( + 'POST', + $this->baseUrl . 'extract.php' + ); + + $data['password'] = $this->key; + + return $this->performHttpRequest( + $request, + [ + 'form_params' => $data, + 'timeout' => 300.0 + ] + ); + } + + protected function performWebserviceRequest( + HttpMethod $method, + WebserviceEndpoint $endpoint, + array $data = [] + ): array { + $request = new Request( + $method->name, + $this->baseUrl . $endpoint->value, + [ + 'Authorization' => 'JUpdate-Token ' . $this->key + ] + ); + + return $this->performHttpRequest( + $request, + [ + "json" => $data + ] + ); + } + + protected function performHttpRequest( + RequestInterface $request, + array $options = [] + ): array { + /** @var Client $httpClient */ + $httpClient = App::make(Client::class); + + /** @var Response $response */ + $response = $httpClient->send( + $request, + $options + ); + + // Validate response + if (!json_validate((string) $response->getBody())) { + throw new RequestException( + "Invalid JSON body", + $request, + $response + ); + } + + // Return decoded body + return json_decode( + (string) $response->getBody(), + true, + 512, + JSON_THROW_ON_ERROR + ); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 7b162da..36e7dd8 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -11,7 +11,9 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware) { - // + $middleware->alias([ + 'horizonBasicAuth' => \App\Http\Middleware\HorizonBasicAuthMiddleware::class + ]); }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 38b258d..0a3ca7b 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -2,4 +2,6 @@ return [ App\Providers\AppServiceProvider::class, + App\Providers\HorizonServiceProvider::class, + App\Providers\HttpclientServiceProvider::class, ]; diff --git a/config/auth.php b/config/auth.php index 0ba5d5d..353567c 100644 --- a/config/auth.php +++ b/config/auth.php @@ -60,15 +60,6 @@ */ 'providers' => [ - 'users' => [ - 'driver' => 'eloquent', - 'model' => env('AUTH_MODEL', App\Models\User::class), - ], - - // 'users' => [ - // 'driver' => 'database', - // 'table' => 'users', - // ], ], /* diff --git a/config/horizon.php b/config/horizon.php new file mode 100644 index 0000000..dfac10b --- /dev/null +++ b/config/horizon.php @@ -0,0 +1,218 @@ + null, + + /* + |-------------------------------------------------------------------------- + | Horizon Path + |-------------------------------------------------------------------------- + | + | This is the URI path where Horizon will be accessible from. Feel free + | to change this path to anything you like. Note that the URI will not + | affect the paths of its internal API that aren't exposed to users. + | + */ + + 'path' => env('HORIZON_PATH', 'horizon'), + + /* + |-------------------------------------------------------------------------- + | Horizon Redis Connection + |-------------------------------------------------------------------------- + | + | This is the name of the Redis connection where Horizon will store the + | meta information required for it to function. It includes the list + | of supervisors, failed jobs, job metrics, and other information. + | + */ + + 'use' => 'default', + + /* + |-------------------------------------------------------------------------- + | Horizon Redis Prefix + |-------------------------------------------------------------------------- + | + | This prefix will be used when storing all Horizon data in Redis. You + | may modify the prefix when you are running multiple installations + | of Horizon on the same server so that they don't have problems. + | + */ + + 'prefix' => env( + 'HORIZON_PREFIX', + Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:' + ), + + /* + |-------------------------------------------------------------------------- + | Horizon Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will get attached onto each Horizon route, giving you + | the chance to add your own middleware to this list or change any of + | the existing middleware. Or, you can simply stick with this list. + | + */ + + 'middleware' => ['web', 'horizonBasicAuth'], + + /* + |-------------------------------------------------------------------------- + | Queue Wait Time Thresholds + |-------------------------------------------------------------------------- + | + | This option allows you to configure when the LongWaitDetected event + | will be fired. Every connection / queue combination may have its + | own, unique threshold (in seconds) before this event is fired. + | + */ + + 'waits' => [ + 'redis:default' => 60, + ], + + /* + |-------------------------------------------------------------------------- + | Job Trimming Times + |-------------------------------------------------------------------------- + | + | Here you can configure for how long (in minutes) you desire Horizon to + | persist the recent and failed jobs. Typically, recent jobs are kept + | for one hour while all failed jobs are stored for an entire week. + | + */ + + 'trim' => [ + 'recent' => 60, + 'pending' => 60, + 'completed' => 60, + 'recent_failed' => 10080, + 'failed' => 10080, + 'monitored' => 10080, + ], + + /* + |-------------------------------------------------------------------------- + | Silenced Jobs + |-------------------------------------------------------------------------- + | + | Silencing a job will instruct Horizon to not place the job in the list + | of completed jobs within the Horizon dashboard. This setting may be + | used to fully remove any noisy jobs from the completed jobs list. + | + */ + + 'silenced' => [ + // App\Jobs\ExampleJob::class, + ], + + /* + |-------------------------------------------------------------------------- + | Metrics + |-------------------------------------------------------------------------- + | + | Here you can configure how many snapshots should be kept to display in + | the metrics graph. This will get used in combination with Horizon's + | `horizon:snapshot` schedule to define how long to retain metrics. + | + */ + + 'metrics' => [ + 'trim_snapshots' => [ + 'job' => 24, + 'queue' => 24, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Fast Termination + |-------------------------------------------------------------------------- + | + | When this option is enabled, Horizon's "terminate" command will not + | wait on all of the workers to terminate unless the --wait option + | is provided. Fast termination can shorten deployment delay by + | allowing a new instance of Horizon to start while the last + | instance will continue to terminate each of its workers. + | + */ + + 'fast_termination' => false, + + /* + |-------------------------------------------------------------------------- + | Memory Limit (MB) + |-------------------------------------------------------------------------- + | + | This value describes the maximum amount of memory the Horizon master + | supervisor may consume before it is terminated and restarted. For + | configuring these limits on your workers, see the next section. + | + */ + + 'memory_limit' => 64, + + /* + |-------------------------------------------------------------------------- + | Queue Worker Configuration + |-------------------------------------------------------------------------- + | + | Here you may define the queue worker settings used by your application + | in all environments. These supervisors and settings handle all your + | queued jobs and will be provisioned by Horizon during deployment. + | + */ + + 'defaults' => [ + 'supervisor-1' => [ + 'connection' => 'redis', + 'queue' => ['default'], + 'balance' => 'auto', + 'autoScalingStrategy' => 'time', + 'maxProcesses' => 1, + 'maxTime' => 0, + 'maxJobs' => 0, + 'memory' => 128, + 'tries' => 1, + 'timeout' => 60, + 'nice' => 0, + ], + ], + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + 'maxProcesses' => 10, + 'balanceMaxShift' => 1, + 'balanceCooldown' => 3, + ], + ], + + 'local' => [ + 'supervisor-1' => [ + 'maxProcesses' => 3, + ], + ], + ], + + 'basic_auth' => [ + 'username' => env('HORIZON_BASIC_AUTH_USERNAME', 'joomla'), + 'password' => env('HORIZON_BASIC_AUTH_PASSWORD', 'updates') + ], +]; diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php deleted file mode 100644 index 05fb5d9..0000000 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ /dev/null @@ -1,49 +0,0 @@ -id(); - $table->string('name'); - $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); - $table->string('password'); - $table->rememberToken(); - $table->timestamps(); - }); - - Schema::create('password_reset_tokens', function (Blueprint $table) { - $table->string('email')->primary(); - $table->string('token'); - $table->timestamp('created_at')->nullable(); - }); - - Schema::create('sessions', function (Blueprint $table) { - $table->string('id')->primary(); - $table->foreignId('user_id')->nullable()->index(); - $table->string('ip_address', 45)->nullable(); - $table->text('user_agent')->nullable(); - $table->longText('payload'); - $table->integer('last_activity')->index(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('users'); - Schema::dropIfExists('password_reset_tokens'); - Schema::dropIfExists('sessions'); - } -}; diff --git a/database/migrations/0001_01_01_000002_create_jobs_table.php b/database/migrations/0001_01_01_000002_create_jobs_table.php index 425e705..ccf3ec1 100644 --- a/database/migrations/0001_01_01_000002_create_jobs_table.php +++ b/database/migrations/0001_01_01_000002_create_jobs_table.php @@ -4,8 +4,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration -{ +return new class () extends Migration { /** * Run the migrations. */ diff --git a/database/migrations/2024_11_15_191942_create_sites_table.php b/database/migrations/2024_11_15_191942_create_sites_table.php index 47fe6d8..e790b20 100644 --- a/database/migrations/2024_11_15_191942_create_sites_table.php +++ b/database/migrations/2024_11_15_191942_create_sites_table.php @@ -4,8 +4,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class extends Migration -{ +return new class () extends Migration { /** * Run the migrations. */ diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..684f282 --- /dev/null +++ b/pint.json @@ -0,0 +1,3 @@ +{ + "preset": "psr12" +} diff --git a/routes/console.php b/routes/console.php index 1612c78..ec6db2f 100644 --- a/routes/console.php +++ b/routes/console.php @@ -1,4 +1,6 @@ daily();