backup_water_cleanup.php 1.7 KB

1234567891011121314151617181920212223242526272829
  1. <?php
  2. /** Internal maintenance backup: output contains only counts and paths, never row data. */
  3. require dirname(__DIR__) . '/vendor/autoload.php';
  4. \Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
  5. \support\App::loadAllConfig(['route']);
  6. \Webman\ThinkOrm\ThinkOrm::start(null);
  7. use support\think\Db;
  8. $destination = $argv[1] ?? '';
  9. if (!$destination || !is_dir($destination)) throw new RuntimeException('Existing backup directory required');
  10. $snapshot = ['created_at'=>date(DATE_ATOM),'tables'=>[]];
  11. Db::startTrans();
  12. try {
  13. foreach (Db::query('SHOW TABLES') as $record) {
  14. $table = array_values($record)[0];
  15. if (!preg_match('/^[a-zA-Z0-9_]+$/D',$table)) throw new RuntimeException('Unexpected table identifier');
  16. $schema = Db::query('SHOW CREATE TABLE `'.$table.'`')[0]['Create Table'];
  17. $rows = Db::table($table)->select()->toArray();
  18. $snapshot['tables'][$table] = ['schema'=>$schema,'rows'=>$rows];
  19. }
  20. } finally { Db::rollback(); }
  21. $file = $destination . '/database.json.gz';
  22. if (file_exists($file)) throw new RuntimeException('Refusing to overwrite existing backup');
  23. $json = json_encode($snapshot,JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR);
  24. if (file_put_contents($file,gzencode($json,9),LOCK_EX) === false) throw new RuntimeException('Backup write failed');
  25. $verified = json_decode(gzdecode(file_get_contents($file)),true,512,JSON_THROW_ON_ERROR);
  26. if ($verified !== $snapshot) throw new RuntimeException('Backup verification failed');
  27. $manifest = ['sha256'=>hash_file('sha256',$file),'tables'=>array_map(fn($table)=>count($table['rows']),$snapshot['tables'])];
  28. file_put_contents($destination.'/database-manifest.json',json_encode($manifest,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
  29. echo 'Verified database backup: '.count($manifest['tables'])." tables\n";