Because cats have 9 lives, but servers don't - so they need backup-restore! Complete backup solution with S3/MinIO support. - Full WordPress backup (files + database) - S3 / MinIO / S3-compatible storage backends - Scheduled automatic backups - Disaster recovery / one-click restore - Backup integrity validation - Cat-themed admin interface Includes build.sh and .distignore for WordPress-installable release ZIPs.
277 lines
8.4 KiB
PHP
277 lines
8.4 KiB
PHP
<?php
|
|
/**
|
|
* S3 Compatible Storage Backend
|
|
*
|
|
* Handles Amazon S3 and compatible storage (MinIO) for backups
|
|
*
|
|
* @package TigerStyleLife9
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* S3 storage backend class
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
class TigerStyle_Life9_Storage_S3 extends TigerStyle_Life9_Storage_Backend {
|
|
|
|
/**
|
|
* Store file to S3 storage
|
|
*
|
|
* @param string $file_path Local file path
|
|
* @param array $config Storage configuration
|
|
* @return array Storage result
|
|
*/
|
|
public function store($file_path, $config = []) {
|
|
if (!file_exists($file_path)) {
|
|
throw new Exception('🐱 Meow! Source file does not exist - cats can\'t backup invisible files!');
|
|
}
|
|
|
|
// Validate required configuration
|
|
$required_fields = ['access_key', 'secret_key', 'bucket', 'region'];
|
|
foreach ($required_fields as $field) {
|
|
if (empty($config[$field])) {
|
|
throw new Exception("🐱 Paws! Missing required S3 configuration: {$field}");
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Create S3 client
|
|
$s3_config = [
|
|
'version' => 'latest',
|
|
'region' => $config['region'],
|
|
'credentials' => [
|
|
'key' => $config['access_key'],
|
|
'secret' => $config['secret_key'],
|
|
]
|
|
];
|
|
|
|
// Add custom endpoint for MinIO
|
|
if (!empty($config['endpoint'])) {
|
|
$s3_config['endpoint'] = $config['endpoint'];
|
|
$s3_config['use_path_style_endpoint'] = true;
|
|
}
|
|
|
|
if (!class_exists('Aws\S3\S3Client')) {
|
|
throw new Exception('🐱 Hiss! AWS SDK not found. Install it with: composer require aws/aws-sdk-php');
|
|
}
|
|
|
|
$s3 = new Aws\S3\S3Client($s3_config);
|
|
|
|
// Generate remote path with cat-themed organization
|
|
$remote_path = $this->generate_cat_remote_path($file_path);
|
|
|
|
// Upload file
|
|
$result = $s3->putObject([
|
|
'Bucket' => $config['bucket'],
|
|
'Key' => $remote_path,
|
|
'SourceFile' => $file_path,
|
|
'Metadata' => [
|
|
'created-by' => 'tigerstyle-life9',
|
|
'backup-type' => 'cat-lives',
|
|
'created-at' => date('c'),
|
|
'purr-factor' => 'maximum'
|
|
]
|
|
]);
|
|
|
|
return [
|
|
'url' => $result['ObjectURL'] ?? '',
|
|
'remote_path' => $remote_path,
|
|
'storage_id' => $remote_path,
|
|
'metadata' => [
|
|
'size' => filesize($file_path),
|
|
'created' => date('Y-m-d H:i:s'),
|
|
'etag' => $result['ETag'] ?? '',
|
|
'cat_rating' => '🐱🐱🐱🐱🐱'
|
|
]
|
|
];
|
|
|
|
} catch (Exception $e) {
|
|
throw new Exception('🐱 Cat-astrophic S3 upload failure: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve file from S3 storage
|
|
*
|
|
* @param string $remote_path Remote file path or ID
|
|
* @param string $local_path Local destination path
|
|
* @param array $config Storage configuration
|
|
* @return bool Success status
|
|
*/
|
|
public function retrieve($remote_path, $local_path, $config = []) {
|
|
try {
|
|
$s3 = $this->create_s3_client($config);
|
|
|
|
$s3->getObject([
|
|
'Bucket' => $config['bucket'],
|
|
'Key' => $remote_path,
|
|
'SaveAs' => $local_path
|
|
]);
|
|
|
|
return true;
|
|
|
|
} catch (Exception $e) {
|
|
error_log('🐱 S3 download failed: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete file from S3 storage
|
|
*
|
|
* @param string $remote_path Remote file path or ID
|
|
* @param array $config Storage configuration
|
|
* @return bool Success status
|
|
*/
|
|
public function delete($remote_path, $config = []) {
|
|
try {
|
|
$s3 = $this->create_s3_client($config);
|
|
|
|
$s3->deleteObject([
|
|
'Bucket' => $config['bucket'],
|
|
'Key' => $remote_path
|
|
]);
|
|
|
|
return true;
|
|
|
|
} catch (Exception $e) {
|
|
error_log('🐱 S3 deletion failed: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test S3 storage connection
|
|
*
|
|
* @param array $config Storage configuration
|
|
* @return bool Connection status
|
|
*/
|
|
public function test_connection($config = []) {
|
|
try {
|
|
$s3 = $this->create_s3_client($config);
|
|
|
|
// Test by checking if bucket exists and is accessible
|
|
$s3->headBucket(['Bucket' => $config['bucket']]);
|
|
|
|
return true;
|
|
|
|
} catch (Exception $e) {
|
|
error_log('🐱 S3 connection test failed: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List backup files in S3
|
|
*
|
|
* @param array $config Storage configuration
|
|
* @return array List of backup files
|
|
*/
|
|
public function list_backups($config = []) {
|
|
try {
|
|
$s3 = $this->create_s3_client($config);
|
|
|
|
$result = $s3->listObjects([
|
|
'Bucket' => $config['bucket'],
|
|
'Prefix' => 'tigerstyle-life9/'
|
|
]);
|
|
|
|
$backups = [];
|
|
if (isset($result['Contents'])) {
|
|
foreach ($result['Contents'] as $object) {
|
|
$backups[] = [
|
|
'key' => $object['Key'],
|
|
'size' => $object['Size'],
|
|
'modified' => $object['LastModified']->format('Y-m-d H:i:s'),
|
|
'cat_rating' => $this->calculate_cat_rating($object['Size'])
|
|
];
|
|
}
|
|
}
|
|
|
|
return $backups;
|
|
|
|
} catch (Exception $e) {
|
|
error_log('🐱 Failed to list S3 backups: ' . $e->getMessage());
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create S3 client with proper configuration
|
|
*
|
|
* @param array $config Storage configuration
|
|
* @return Aws\S3\S3Client
|
|
*/
|
|
private function create_s3_client($config) {
|
|
$s3_config = [
|
|
'version' => 'latest',
|
|
'region' => $config['region'],
|
|
'credentials' => [
|
|
'key' => $config['access_key'],
|
|
'secret' => $config['secret_key'],
|
|
]
|
|
];
|
|
|
|
// Add custom endpoint for MinIO
|
|
if (!empty($config['endpoint'])) {
|
|
$s3_config['endpoint'] = $config['endpoint'];
|
|
$s3_config['use_path_style_endpoint'] = true;
|
|
}
|
|
|
|
return new Aws\S3\S3Client($s3_config);
|
|
}
|
|
|
|
/**
|
|
* Generate cat-themed remote path
|
|
*
|
|
* @param string $file_path Local file path
|
|
* @return string Remote file path
|
|
*/
|
|
protected function generate_cat_remote_path($file_path) {
|
|
$filename = basename($file_path);
|
|
$date_path = date('Y/m/d');
|
|
$hour = date('H');
|
|
|
|
// Add cat-themed hour descriptions
|
|
$cat_time = '';
|
|
if ($hour >= 0 && $hour < 6) {
|
|
$cat_time = 'midnight-prowl';
|
|
} elseif ($hour >= 6 && $hour < 12) {
|
|
$cat_time = 'morning-stretch';
|
|
} elseif ($hour >= 12 && $hour < 18) {
|
|
$cat_time = 'afternoon-nap';
|
|
} else {
|
|
$cat_time = 'evening-hunt';
|
|
}
|
|
|
|
return "tigerstyle-life9/{$date_path}/{$cat_time}/{$filename}";
|
|
}
|
|
|
|
/**
|
|
* Calculate cat rating based on file size
|
|
*
|
|
* @param int $size File size in bytes
|
|
* @return string Cat rating
|
|
*/
|
|
private function calculate_cat_rating($size) {
|
|
$size_mb = $size / (1024 * 1024);
|
|
|
|
if ($size_mb < 10) {
|
|
return '🐱';
|
|
} elseif ($size_mb < 50) {
|
|
return '🐱🐱';
|
|
} elseif ($size_mb < 100) {
|
|
return '🐱🐱🐱';
|
|
} elseif ($size_mb < 500) {
|
|
return '🐱🐱🐱🐱';
|
|
} else {
|
|
return '🐱🐱🐱🐱🐱';
|
|
}
|
|
}
|
|
} |