Files
compras/scripts/sgf-sourcing-install.php
2026-11-02 20:27:11 +02:00

32 lines
3.3 KiB
PHP

<?php
// Offline additive migration; no rights, credentials, contacts or activation are provisioned.
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Support\Facades\DB;
use Symfony\Component\Process\Process;
use Tempo\Purchasing\Delivery\Installation;
use Tempo\Purchasing\Delivery\CodeDeployment;
use Tempo\Purchasing\SgfSourcing\Schema;
$root=dirname(__DIR__);$action=$argv[1]??'';$host=realpath($argv[2]??'');
try{
if(!$host||!in_array($action,['plan','migrate'],true))throw new RuntimeException('Usage: plan <host> | migrate <host> <target-sha> <backup-file> <backup-sha> <operator> <manifest-sha>');
$host=str_replace('\\','/',$host);$manifestText=file_get_contents($root.'/manifest.json');$manifest=json_decode($manifestText,true,64,JSON_THROW_ON_ERROR);$manifestHash=hash('sha256',$manifestText);
foreach($manifest['support_files'] as $file=>$hash)if(str_contains($file,'..')||!preg_match('~^[A-Za-z0-9_./-]+$~D',$file)||is_link($root.'/'.$file)||!hash_equals($hash,hash_file('sha256',$root.'/'.$file)))throw new RuntimeException('Bundle integrity failed');
require $host.'/vendor/autoload.php';$app=require $host.'/bootstrap/app.php';$app->make(Kernel::class)->bootstrap();
if(!class_exists(Installation::class))require_once $root.'/integrations/sgf/operational/bootstrap.php';
if(!class_exists(Schema::class))require_once $root.'/integrations/sgf/sourcing/Schema.php';
require_once $root.'/integrations/sgf/operational/CodeDeployment.php';
$db=DB::connection();$current=json_decode(file_get_contents($host.'/storage/app/private/module-deployments/current.json'),true,32,JSON_THROW_ON_ERROR);
(new CodeDeployment($host))->plan($host.'/packages/internal-requisitions',$current['manifest_sha256']);
$plan=['target_sha256'=>Installation::target($db),'manifest_sha256'=>$manifestHash,'schema_changes'=>Schema::TABLES,'new_rights'=>Schema::RIGHTS,'grant_performed'=>false,'activated'=>false];
if($action==='plan'){echo json_encode($plan,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR);exit;}
$backup=$argv[4]??'';$backupHash=$argv[5]??'';$operator=$argv[6]??'';
if(!hash_equals($plan['target_sha256'],$argv[3]??'')||!hash_equals($manifestHash,$argv[7]??'')||!is_file($backup)||!hash_equals($backupHash,hash_file('sha256',$backup))||trim($operator)===''||!is_file($host.'/storage/framework/down'))throw new RuntimeException('Target, manifest, backup, operator and maintenance are required');
(new Process([getenv('PG_RESTORE_BINARY')?:'pg_restore','--list',$backup],null,null,null,60))->mustRun();
$result=Schema::migrate($db,$backupHash,$operator);
$old=json_decode(file_get_contents($host.'/packages/internal-requisitions/manifest.json'),true,64,JSON_THROW_ON_ERROR);
$receipt=['host'=>$host,'from_manifest_sha256'=>$current['manifest_sha256'],'to_manifest_sha256'=>$manifestHash,'schema_verified'=>Schema::compatible($db),'changes'=>[]];
foreach($manifest['support_files'] as $file=>$hash)if(($old['support_files'][$file]??null)!==$hash)$receipt['changes'][$file]=['before'=>$old['support_files'][$file]??null,'after'=>$hash];
file_put_contents($host.'/storage/app/private/module-deployments/schema-transition-'.$manifestHash.'.json',json_encode($receipt,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
echo json_encode($result+['activated'=>false,'rights_granted'=>false],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR);
}catch(Throwable $e){fwrite(STDERR,$e->getMessage()."\n");exit(1);}