d2d.emegrendeles.hu/app/Services/Deployment/DeploymentGuard.php
E98Developer 7ad9c6fea8 ADD Deployment csomagoló phase2 csomag előállítása (mappa, manifest, törlendők, verziójelölő)
A kijelölt tartományból elkészül a feltölthető mappa: deploy_<target>_<időbélyeg>, benne
a fájlok eredeti relatív útvonalon, hogy a feltöltés mappa-összeolvasztás legyen.

- GitRepository::archiveTo(): git archive --format=zip + ZipArchive kicsomagolás, ~100
  fájlonként darabolva a parancssor-limit miatt. A tartalom a git objektumtárból jön, nem a
  working tree-ből: így nem szivárog ki commitolatlan módosítás, és a .gitattributes eol=lf
  miatt LF sorvéggel kerül a Linux célgépre. Üres pathspec esetén nem hívunk gitet, mert az
  a TELJES fát csomagolná.
- DeploymentPackageBuilder: _MANIFEST.json/.txt (sha1 + méret fájlonként), _TORLENDO.txt a
  törölt és az átnevezett fájlok régi útvonalával, _TEENDOK.md a diffből származó lépésekkel
  (migrate, composer install, asset-figyelmeztetés, verzió-ellenőrzés), és public/
  deploy-version.json a kirakott commit hashével.
- A git archive által kihagyott fájlok (pl. .gitattributes export-ignore) külön "missing"
  listára kerülnek - csendben hiányzó fájl a manuális deploynál a legrosszabb hiba.
- Felületen fájlonkénti kézi kivétel, megerősítéses csomagolás gomb, eredmény-kártya.
- DeploymentGuard::ensureEnvironmentAllowed(): a builder felhasználó nélkül is ellenőrzi az
  env kapcsolót és a stage whitelistet, így konzolról indítva sem fut le egy szerveren.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:09:05 +02:00

93 lines
3.0 KiB
PHP

<?php
namespace App\Services\Deployment;
use App\Models\User;
use Laravel\Pennant\Feature;
use RuntimeException;
/**
* A deployment csomagoló hozzáférés-ellenőrzése egy helyen.
*
* Az alkalmazás kódja manuális fájlmásolással kerül a szerverekre, tehát ez az oldal
* fizikailag ott lesz az éles környezetben is. Ezért négy, egymástól független feltétel
* van, és mindet MINDEN belépési ponton ellenőrizni kell (oldal mount, akciók, később a
* csomagoló service) - ha csak a felületet védjük, az látszatvédelem.
*
* A négyből a tényleges védelmet az .env kapcsoló és a stage whitelist adja, a
* jogosultságot a szerepkör. A feature flag NEM biztonsági réteg: a menü-láthatóságot
* vezérli, és lehetővé teszi a deploy nélküli kikapcsolást, illetve a név szerinti
* átadást (feature_flag_overrides) developer szerep osztogatása nélkül.
*/
class DeploymentGuard
{
public function isAllowed(?User $user = null): bool
{
return $this->denialReason($user) === null;
}
/**
* Csak a környezeti feltételek (1-2. réteg), felhasználó nélkül.
*
* Ezt hívja a csomagoló service is: így akkor sem fut le, ha valaki a felületet
* megkerülve (konzolról, jobból) indítaná el egy szerveren.
*/
public function environmentDenialReason(): ?string
{
if (! config('deployment.enabled')) {
return 'A deployment csomagoló ki van kapcsolva (DEPLOYMENT_PACKAGE_ENABLED).';
}
/** @var array<int, string> $stages */
$stages = (array) config('deployment.stages', []);
if (! in_array((string) config('app.stage'), $stages, true)) {
return sprintf(
'A deployment csomagoló csak fejlesztői környezetben használható (engedélyezett stage: %s, jelenlegi: %s).',
implode(', ', $stages),
(string) config('app.stage'),
);
}
return null;
}
/**
* Az első nem teljesülő feltétel magyarázata, vagy null, ha minden rendben.
*/
public function denialReason(?User $user = null): ?string
{
if ($reason = $this->environmentDenialReason()) {
return $reason;
}
if (! $user) {
return 'Bejelentkezés szükséges.';
}
if (! $user->hasRole((string) config('deployment.role'))) {
return 'A deployment csomagoló csak fejlesztői szerepkörrel érhető el.';
}
if (! Feature::for($user)->active((string) config('deployment.feature_flag'))) {
return 'A deployment csomagoló feature flag nincs bekapcsolva erre a felhasználóra.';
}
return null;
}
public function ensureAllowed(?User $user = null): void
{
if ($reason = $this->denialReason($user)) {
abort(403, $reason);
}
}
public function ensureEnvironmentAllowed(): void
{
if ($reason = $this->environmentDenialReason()) {
throw new RuntimeException($reason);
}
}
}