Files
huntingclubroster/bin/backup-database.php
T

30 lines
3.0 KiB
PHP
Raw Normal View History

<?php
/** Scheduler entry point. Credentials are never passed on the process command line. */
if(PHP_SAPI!=='cli'){http_response_code(404);exit;}
require __DIR__.'/../classes/Base.php';
$directory=realpath($argv[1]??'');$root=realpath(__DIR__.'/..');
if(!$directory||!is_dir($directory)||strtolower($directory)===strtolower($root)||str_starts_with(strtolower($directory),strtolower($root).DIRECTORY_SEPARATOR))throw new RuntimeException('Choose an existing protected backup directory outside the application tree.');
$base=new Base();$settings=$base->getSettings();$db=$base->connect();
$engines=$db->query("SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_TYPE='BASE TABLE'")->fetchAll(PDO::FETCH_COLUMN);
if(array_filter($engines,static fn($v)=>$v!=='InnoDB'))throw new RuntimeException('Nontransactional tables require a maintenance-window locked backup.');
$file=$directory.DIRECTORY_SEPARATOR.'hcr-'.gmdate('Ymd-His').'-'.bin2hex(random_bytes(3)).'.sql';
$partial=$file.'.partial';$config=null;
$quote=static fn($value)=>'"'.str_replace(["\\",'"',"\r","\n"],["\\\\",'\\"','\\r','\\n'],(string)$value).'"';
$options="[client]\nhost=".$quote($settings->DatabaseServer)."\nport=".(int)($settings->DatabasePort??3306)."\nuser=".$quote($settings->DatabaseUser)."\npassword=".$quote($settings->DatabasePassword)."\n";
$ca=getenv('HCR_DB_TLS_CA');if($ca)$options.='ssl-ca='.$quote($ca)."\nssl-verify-server-cert\n";
try{
$handle=fopen($partial,'x');if(!$handle)throw new RuntimeException('Cannot create protected backup output.');fclose($handle);chmod($partial,0600);
$config=tempnam($directory,'hcr-connection-');if($config===false)throw new RuntimeException('Cannot create protected connection configuration.');chmod($config,0600);
file_put_contents($config,$options);
$command=[getenv('HCR_DUMP_BINARY')?:'mariadb-dump','--defaults-extra-file='.$config,'--single-transaction','--quick','--routines','--events','--triggers','--hex-blob','--databases',(string)$settings->DatabaseName,'--result-file='.$partial];
$process=proc_open($command,[0=>['pipe','r'],1=>['pipe','w'],2=>['pipe','w']],$pipes);
if(!is_resource($process))throw new RuntimeException('Backup process could not start.');
fclose($pipes[0]);$out=stream_get_contents($pipes[1]);$error=stream_get_contents($pipes[2]);fclose($pipes[1]);fclose($pipes[2]);
if(proc_close($process)!==0){error_log('Backup process failed: '.$error);throw new RuntimeException('Backup failed; inspect the protected backup error log.');}
if(!BackupValidation::complete($partial))throw new RuntimeException('Backup completion could not be confirmed.');
if(!rename($partial,$file))throw new RuntimeException('Cannot finalize completed backup.');
chmod($file,0600);
$metadata=['created_utc'=>gmdate(DATE_ATOM),'bytes'=>filesize($file),'sha256'=>hash_file('sha256',$file)];file_put_contents($file.'.json',json_encode($metadata,JSON_PRETTY_PRINT));chmod($file.'.json',0600);
echo json_encode(['backup'=>$file]+$metadata),"\n";
}finally{if(is_string($config)&&is_file($config))unlink($config);if(is_file($partial))unlink($partial);}