generated from jric11/baseProject
ChatGPT's update to harden software and run db updates.
This commit is contained in:
@@ -8,21 +8,22 @@ $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';$handle=fopen($partial,'x');if(!$handle)throw new RuntimeException('Cannot create protected backup output.');fclose($handle);chmod($partial,0600);
|
||||
$config=tempnam($directory,'hcr-connection-');chmod($config,0600);
|
||||
$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(!is_file($partial)||filesize($partial)<100||!str_contains(file_get_contents($partial),'Dump completed on'))throw new RuntimeException('Backup completion could not be confirmed.');
|
||||
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_file($config))unlink($config);if(is_file($partial))unlink($partial);}
|
||||
}finally{if(is_string($config)&&is_file($config))unlink($config);if(is_file($partial))unlink($partial);}
|
||||
|
||||
@@ -11,9 +11,9 @@ for directory in ['classes','css','images','js','html','migrations','reports','v
|
||||
for file in (ROOT/directory).rglob('*'):
|
||||
if file.is_file() and not file.is_symlink() and file.suffix.lower() not in ['.sql','.php','.js','.css','.xsl'] and directory not in ['vendor','images','css','html']:continue
|
||||
if file.is_file() and not file.is_symlink() and not any(p.startswith('.') for p in file.relative_to(ROOT).parts) and '_orig.' not in file.name:files.append(file)
|
||||
for name in ['main.php','.htaccess','web.config','composer.json','composer.lock','xml/settings.xml','xml/constants.xml']:
|
||||
for name in ['main.php','.htaccess','web.config','composer.json','composer.lock','THIRD-PARTY-NOTICES.md','browser-dependencies.json','xml/settings.xml','xml/constants.xml']:
|
||||
files.append(ROOT/name)
|
||||
for name in ['migrate-auth.php','migrate-roster.php','migrate-production.php','cleanup-auth.php','check-production.py','backup-database.php','check-health.php']:
|
||||
for name in ['migrate-auth.php','migrate-roster.php','migrate-production.php','migrate-tenants.php','migrate-security-capacity.php','cleanup-auth.php','check-production.py','backup-database.php','check-health.php']:
|
||||
file=ROOT/'bin'/name
|
||||
if file.exists():files.append(file)
|
||||
if not (ROOT/'vendor/autoload.php').is_file():raise SystemExit('Install locked Composer dependencies before building a release.')
|
||||
|
||||
@@ -4,6 +4,8 @@ require __DIR__.'/../classes/Base.php';
|
||||
try{
|
||||
$db=(new Base())->connect();$db->query('SELECT 1')->fetchColumn();
|
||||
if(!(new RosterService())->ready())throw new RuntimeException('Required schema is missing.');
|
||||
foreach(['user_mfa','mfa_recovery','club_resource_usage'] as $table)$db->query('SELECT 1 FROM '.$table.' LIMIT 0');
|
||||
if((string)(new Base())->getSettings()->Environment==='Production'){ $key=base64_decode(getenv('HCR_MFA_KEY')?:'',true);if($key===false||strlen($key)!==32)throw new RuntimeException('MFA key is missing.');}
|
||||
foreach(['user_email_verified_at'] as $column){$columns=$db->query('SHOW COLUMNS FROM users')->fetchAll(PDO::FETCH_COLUMN);if(!in_array($column,$columns,true))throw new RuntimeException('Account schema is missing.');}
|
||||
echo "Database and required application schema are available.\n";
|
||||
}catch(Throwable $e){fwrite(STDERR,"Health check failed. Review the protected server logs.\n");error_log((string)$e);exit(1);}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
if(PHP_SAPI!=='cli'){http_response_code(404);exit;}
|
||||
require_once __DIR__.'/../classes/Base.php';
|
||||
$db=(new Base())->connect();
|
||||
foreach(explode(';',file_get_contents(__DIR__.'/../migrations/005_security_capacity.sql')) as $sql)if(trim($sql))$db->exec($sql);
|
||||
echo "Security and capacity schema ready. Configure HCR_MFA_KEY before administrator sign-in.\n";
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/** Explicit account selection; never promotes every legacy administrator. */
|
||||
if(PHP_SAPI!=='cli'){http_response_code(404);exit;}
|
||||
require_once __DIR__.'/../classes/Base.php';
|
||||
$db=(new Base())->connect();
|
||||
$columns=$db->query('SHOW COLUMNS FROM club_memberships')->fetchAll(PDO::FETCH_COLUMN);
|
||||
if(!in_array('membership_role',$columns,true))$db->exec("ALTER TABLE club_memberships ADD COLUMN membership_role ENUM('member','administrator') NOT NULL DEFAULT 'member'");
|
||||
// Club display names are tenant-local, not a global account lookup key.
|
||||
$index=$db->query("SHOW INDEX FROM clubs WHERE Key_name='club_name'")->fetch();
|
||||
if($index && !(int)$index['Non_unique'])$db->exec('ALTER TABLE clubs DROP INDEX club_name, ADD INDEX club_name (club_name)');
|
||||
foreach(explode(';',file_get_contents(__DIR__.'/../migrations/004_tenants.sql')) as $sql)if(trim($sql))$db->exec($sql);
|
||||
$email=getenv('HCR_SUPER_ADMIN_EMAIL')?:'';
|
||||
if($email!==''){
|
||||
if(!filter_var($email,FILTER_VALIDATE_EMAIL))throw new RuntimeException('Use a valid existing Super System Administrator email.');
|
||||
$db->beginTransaction();
|
||||
try{
|
||||
$stmt=$db->prepare("SELECT user_serial,user_role FROM users WHERE user_email=? AND user_status='active' FOR UPDATE");$stmt->execute([$email]);$user=$stmt->fetch();
|
||||
if(!$user)throw new RuntimeException('The selected active account does not exist. No account was promoted.');
|
||||
if($user['user_role']!==TenantAccess::SUPER){
|
||||
$stmt=$db->prepare('UPDATE users SET user_role=?,user_auth_version=user_auth_version+1 WHERE user_serial=?');$stmt->execute([TenantAccess::SUPER,$user['user_serial']]);
|
||||
foreach(['password_resets','email_verifications'] as $table){$stmt=$db->prepare('DELETE FROM '.$table.' WHERE user_serial=?');$stmt->execute([$user['user_serial']]);}
|
||||
Audit::required($db,(int)$user['user_serial'],'designated Super System Administrator by CLI migration');
|
||||
}
|
||||
$db->commit();
|
||||
}catch(Throwable $e){if($db->inTransaction())$db->rollBack();throw $e;}
|
||||
}
|
||||
echo "Tenant schema is ready. Existing clubs require explicit administrator assignments; no memberships were guessed.\n";
|
||||
Reference in New Issue
Block a user