diff --git a/scripts/backup_restore/backup_config.php b/scripts/backup_restore/backup_config.php deleted file mode 100644 index 66f48ec4a..000000000 --- a/scripts/backup_restore/backup_config.php +++ /dev/null @@ -1,75 +0,0 @@ - '/var/backups', - - // 排除规则 - 使用正则表达式匹配需要排除的文件路径 - 'exclude_patterns' => [ - '/\.git/', // 排除git版本控制目录 - '/node_modules/', // 排除npm依赖 - '/\.log$/', // 排除日志文件 - '/\.tmp$/', // 排除临时文件 - '/cache/', // 排除缓存目录 - '/temp/', // 排除临时目录 - '/tmp/', // 排除临时目录 - '/logs/', // 排除日志目录 - '/runtime/', // 排除运行时目录 - '/uploads/', // 排除上传目录 - '/attachment/', // 排除附件目录 - ], - - // 包含规则 - 使用正则表达式匹配需要强制包含的文件路径 - // 即使这些文件被排除规则匹配,也会被包含 - 'include_patterns' => [ - '/.well-known/', // 包含well-known目录 - '/addon/', // 包含插件目录 - '/addons/', // 包含插件目录 - '/app/', // 包含应用目录 - '/config/', // 包含配置目录 - '/extend/', // 包含扩展目录 - // '/h5/', // 包含h5目录 - // '/hwapp/', // 包含hwapp目录 - '/public/', // 包含公共目录 - '/vendor/', // 包含composer依赖 - // '/web/', // 包含web目录 - - // 以下是包含的文件 - '/.404.html', // 包含404页面 - '/index.php', // 包含入口文件 - '/install.php', // 包含安装文件 - '/install.lock', // 包含安装锁文件 - - '/.env', // 包含环境变量文件 - '/.env.test', // 包含测试环境变量文件 - '/.env.production', // 包含生产环境变量文件 - '/.env.staging', // 包含预发布环境变量文件 - '/.env.development', // 包含开发环境变量文件 - '/.env.local', // 包含本地环境变量文件 - '/.gitignore', // 包含git忽略文件 - '/.htaccess', // 包含htaccess文件 - '/.user.ini', // 包含user.ini文件 - '/composer.json', // 包含composer.json文件 - '/composer.lock', // 包含composer.lock文件 - ], - - // 备份模式 - 使用通配符匹配文件类型 - // 这里使用了更全面的文件类型覆盖,确保支持所有常见文件 - 'backup_patterns' => [ - // 如果为空数组,将备份所有文件(不建议) - ], - - // 保留的最大备份文件数量,超过此数量会自动删除最旧的备份 - 'max_backups' => 30, - - // 压缩级别(1-9,1最快但压缩率最低,9最慢但压缩率最高) - 'compression_level' => 6, - - // 是否保留文件权限 - 'preserve_permissions' => true - -]; diff --git a/scripts/deploy.php b/scripts/deploy.php deleted file mode 100644 index 00bee5e56..000000000 --- a/scripts/deploy.php +++ /dev/null @@ -1,512 +0,0 @@ -zipPath = $zipPath; - $this->targetDir = rtrim($targetDir, DIRECTORY_SEPARATOR); - $this->dryRun = $options['dryRun'] ?? false; - $this->verbose = $options['verbose'] ?? false; - $this->backup = $options['backup'] ?? false; - - $this->validatePaths(); - } - - /** - * 验证路径有效性 - */ - private function validatePaths(): void - { - if (!file_exists($this->zipPath)) { - throw new InvalidArgumentException("ZIP文件不存在: {$this->zipPath}"); - } - - if (!is_readable($this->zipPath)) { - throw new InvalidArgumentException("ZIP文件不可读: {$this->zipPath}"); - } - - if (!is_dir($this->targetDir)) { - throw new InvalidArgumentException("目标目录不存在: {$this->targetDir}"); - } - - if (!is_writable($this->targetDir)) { - throw new InvalidArgumentException("目标目录不可写: {$this->targetDir}"); - } - } - - /** - * 执行部署 - */ - public function deploy(): array - { - $this->logMessage('INFO', '开始自动部署流程'); - $this->logMessage('INFO', "ZIP文件: {$this->zipPath}"); - $this->logMessage('INFO', "目标目录: {$this->targetDir}"); - - try { - // 打开ZIP文件 - $zip = new ZipArchive(); - $openResult = $zip->open($this->zipPath); - - if ($openResult !== true) { - throw new RuntimeException("无法打开ZIP文件,错误代码: {$openResult}"); - } - - $this->logMessage('INFO', 'ZIP文件打开成功'); - - // 分析ZIP文件结构 - $zipFiles = $this->analyzeZipStructure($zip); - $this->logMessage('INFO', "ZIP包中包含 {$zipFiles['fileCount']} 个文件"); - - // 基于ZIP文件结构进行部署,不再预先分析目标目录 - $result = $this->compareAndDeploy($zip, $zipFiles); - - // 关闭ZIP文件 - $zip->close(); - - $this->logMessage('SUCCESS', '部署完成'); - return $result; - - } catch (Exception $e) { - $this->logMessage('ERROR', "部署失败: " . $e->getMessage()); - throw $e; - } - } - - /** - * 分析ZIP文件结构 - */ - private function analyzeZipStructure(ZipArchive $zip): array - { - $files = []; - $fileCount = 0; - $totalSize = 0; - - for ($i = 0; $i < $zip->numFiles; $i++) { - $fileInfo = $zip->statIndex($i); - - if ($fileInfo === false) { - continue; - } - - $fileName = $fileInfo['name']; - - // 跳过目录条目 - if (substr($fileName, -1) === '/') { - continue; - } - - $files[$fileName] = [ - 'size' => $fileInfo['size'], - 'compressed_size' => $fileInfo['comp_size'], - 'modified' => $fileInfo['mtime'], - 'crc32' => $fileInfo['crc'] - ]; - - $fileCount++; - $totalSize += $fileInfo['size']; - } - - return [ - 'files' => $files, - 'fileCount' => $fileCount, - 'totalSize' => $totalSize - ]; - } - - /** - * 获取单个文件信息 - */ - private function getFileInfo(string $filePath): ?array - { - if (!file_exists($filePath)) { - return null; - } - - return [ - 'size' => filesize($filePath), - 'modified' => filemtime($filePath), - 'permissions' => fileperms($filePath) - ]; - } - - /** - * 比较差异并部署文件 - */ - private function compareAndDeploy(ZipArchive $zip, array $zipFiles): array - { - $results = [ - 'added' => 0, - 'updated' => 0, - 'skipped' => 0, - 'errors' => 0, - 'details' => [] - ]; - - foreach ($zipFiles['files'] as $filePath => $zipFileInfo) { - $targetFilePath = $this->targetDir . DIRECTORY_SEPARATOR . $filePath; - - try { - // 直接检查文件是否存在,不再依赖预先分析的目标目录结构 - if (!file_exists($targetFilePath)) { - // 新文件 - 需要创建目录并复制文件 - $result = $this->deployNewFile($zip, $filePath, $targetFilePath); - $results['added']++; - $results['details'][] = [ - 'file' => $filePath, - 'action' => 'added', - 'success' => true - ]; - } else { - // 文件已存在 - 检查是否需要更新 - $targetFileInfo = $this->getFileInfo($targetFilePath); - $needsUpdate = $this->needsUpdate($zip, $filePath, $targetFileInfo); - - if ($needsUpdate) { - $result = $this->deployUpdatedFile($zip, $filePath, $targetFilePath); - $results['updated']++; - $results['details'][] = [ - 'file' => $filePath, - 'action' => 'updated', - 'success' => true - ]; - } else { - $results['skipped']++; - $results['details'][] = [ - 'file' => $filePath, - 'action' => 'skipped', - 'success' => true - ]; - $this->logMessage('INFO', "文件无需更新: {$filePath}"); - } - } - } catch (Exception $e) { - $results['errors']++; - $results['details'][] = [ - 'file' => $filePath, - 'action' => 'error', - 'success' => false, - 'error' => $e->getMessage() - ]; - $this->logMessage('ERROR', "处理文件失败 {$filePath}: " . $e->getMessage()); - } - } - - return $results; - } - - /** - * 部署新文件 - */ - private function deployNewFile(ZipArchive $zip, string $filePath, string $targetFilePath): bool - { - $this->logMessage('INFO', "添加新文件: {$filePath}"); - - if ($this->dryRun) { - $this->logMessage('DRY_RUN', "将创建文件: {$targetFilePath}"); - return true; - } - - // 创建目录(如果需要) - $targetDir = dirname($targetFilePath); - if (!is_dir($targetDir)) { - if (!mkdir($targetDir, 0755, true)) { - throw new RuntimeException("无法创建目录: {$targetDir}"); - } - $this->logMessage('INFO', "创建目录: {$targetDir}"); - } - - // 从ZIP中提取文件 - $fileContent = $zip->getFromName($filePath); - if ($fileContent === false) { - throw new RuntimeException("无法从ZIP中读取文件: {$filePath}"); - } - - // 写入文件 - if (file_put_contents($targetFilePath, $fileContent) === false) { - throw new RuntimeException("无法写入文件: {$targetFilePath}"); - } - - $this->logMessage('SUCCESS', "文件创建成功: {$filePath}"); - return true; - } - - /** - * 部署更新文件 - */ - private function deployUpdatedFile(ZipArchive $zip, string $filePath, string $targetFilePath): bool - { - $this->logMessage('INFO', "更新文件: {$filePath}"); - - if ($this->dryRun) { - $this->logMessage('DRY_RUN', "将更新文件: {$targetFilePath}"); - return true; - } - - // 备份原文件(可选) - if ($this->backup && file_exists($targetFilePath)) { - $backupPath = $targetFilePath . '.backup.' . date('YmdHis'); - if (copy($targetFilePath, $backupPath)) { - $this->logMessage('INFO', "文件已备份: {$backupPath}"); - } - } - - // 从ZIP中提取文件内容 - $fileContent = $zip->getFromName($filePath); - if ($fileContent === false) { - throw new RuntimeException("无法从ZIP中读取文件: {$filePath}"); - } - - // 写入文件 - if (file_put_contents($targetFilePath, $fileContent) === false) { - throw new RuntimeException("无法写入文件: {$targetFilePath}"); - } - - $this->logMessage('SUCCESS', "文件更新成功: {$filePath}"); - return true; - } - - /** - * 检查文件是否需要更新 - */ - private function needsUpdate(ZipArchive $zip, string $filePath, ?array $targetFileInfo): bool - { - // 如果目标文件信息不存在,返回false(这种情况实际上不会发生,因为前面已经检查了文件存在) - if ($targetFileInfo === null) { - return false; - } - - $zipFileInfo = $zip->statName($filePath); - if ($zipFileInfo === false) { - return false; - } - - // 比较文件大小 - if ($zipFileInfo['size'] != $targetFileInfo['size']) { - $this->logMessage('DEBUG', "文件大小不同: {$filePath} (ZIP: {$zipFileInfo['size']}, 目标: {$targetFileInfo['size']})"); - return true; - } - - // 比较修改时间(ZIP时间可能不准确,作为次要判断) - if ($zipFileInfo['mtime'] > $targetFileInfo['modified']) { - $this->logMessage('DEBUG', "ZIP文件较新: {$filePath}"); - return true; - } - - // 比较CRC32校验(最可靠的方法) - $targetFilePath = $this->targetDir . DIRECTORY_SEPARATOR . $filePath; - $targetCrc = hash_file('crc32b', $targetFilePath); - $zipCrc = sprintf('%08x', $zipFileInfo['crc']); - - if (strtolower($targetCrc) !== strtolower($zipCrc)) { - $this->logMessage('DEBUG', "文件内容不同: {$filePath}"); - return true; - } - - return false; - } - - /** - * 获取相对路径 - */ - private function getRelativePath(string $path, string $basePath): string - { - $basePath = rtrim($basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - return str_replace($basePath, '', $path); - } - - /** - * 记录日志 - */ - private function logMessage(string $level, string $message): void - { - $timestamp = date('Y-m-d H:i:s'); - $logEntry = "[{$timestamp}] [{$level}] {$message}"; - - $this->log[] = $logEntry; - - if ($this->verbose || in_array($level, ['ERROR', 'SUCCESS'])) { - echo $logEntry . PHP_EOL; - } - } - - /** - * 获取部署日志 - */ - public function getLog(): array - { - return $this->log; - } -} - -// 命令行接口 -class CommandLineInterface -{ - private array $options; - - public function __construct(array $argv) - { - $this->options = $this->parseOptions($argv); - } - - public function run(): void - { - try { - $this->showBanner(); - - if ($this->options['help'] ?? false) { - $this->showHelp(); - exit(0); - } - - // 验证参数 - $zipPath = $this->options['zip'] ?? null; - $targetDir = $this->options['target'] ?? null; - - if (!$zipPath || !$targetDir) { - echo "错误: 必须指定 --zip 和 --target 参数\n"; - $this->showHelp(); - exit(1); - } - - // 创建部署器实例 - $deployer = new AutoDeployer($zipPath, $targetDir, [ - 'dryRun' => $this->options['dry-run'] ?? false, - 'verbose' => $this->options['verbose'] ?? false, - 'backup' => $this->options['backup'] ?? false - ]); - - // 执行部署 - $result = $deployer->deploy(); - - // 显示结果摘要 - $this->showSummary($result); - - } catch (Exception $e) { - echo "错误: " . $e->getMessage() . PHP_EOL; - exit(1); - } - } - - private function parseOptions(array $argv): array - { - $options = [ - 'help' => false, - 'verbose' => false, - 'dry-run' => false - ]; - - for ($i = 1; $i < count($argv); $i++) { - $arg = $argv[$i]; - - switch ($arg) { - case '--help': - case '-h': - $options['help'] = true; - break; - - case '--verbose': - case '-v': - $options['verbose'] = true; - break; - - case '--dry-run': - case '-d': - $options['dry-run'] = true; - break; - - case '--backup': - case '-b': - $options['backup'] = true; - break; - - case '--zip': - $options['zip'] = $argv[++$i] ?? null; - break; - - case '--target': - $options['target'] = $argv[++$i] ?? null; - break; - - default: - if (substr($arg, 0, 1) === '-') { - echo "警告: 未知选项 {$arg}\n"; - } - break; - } - } - - return $options; - } - - private function showBanner(): void - { - echo "========================================\n"; - echo " 文件自动部署工具 v1.0\n"; - echo " PHP 7.4 兼容版本\n"; - echo "========================================\n\n"; - } - - private function showHelp(): void - { - echo "用法: php deploy.php [选项]\n\n"; - echo "选项:\n"; - echo " --zip ZIP文件路径 (必需)\n"; - echo " --target 目标目录路径 (必需)\n"; - echo " --dry-run, -d 试运行,不实际修改文件\n"; - echo " --backup, -b 部署前备份目标文件或目录\n"; - echo " --verbose, -v 显示详细输出\n"; - echo " --help, -h 显示此帮助信息\n\n"; - echo "示例:\n"; - echo " php deploy.php --zip update.zip --target /var/www/html\n"; - echo " php deploy.php --zip patch.zip --target ./app --dry-run\n"; - echo " php deploy.php --zip release.zip --target /var/www --verbose\n"; - } - - private function showSummary(array $result): void - { - echo "\n部署摘要:\n"; - echo "========================================\n"; - echo "新增文件: {$result['added']}\n"; - echo "更新文件: {$result['updated']}\n"; - echo "跳过文件: {$result['skipped']}\n"; - echo "错误文件: {$result['errors']}\n"; - echo "========================================\n"; - - if ($result['errors'] > 0) { - echo "\n错误详情:\n"; - foreach ($result['details'] as $detail) { - if (!$detail['success']) { - echo " - {$detail['file']}: {$detail['error']}\n"; - } - } - } - } -} - -// 主程序入口 -if (PHP_SAPI === 'cli') { - $cli = new CommandLineInterface($argv); - $cli->run(); -} else { - echo "此脚本只能在命令行模式下运行\n"; - exit(1); -} -?> \ No newline at end of file diff --git a/scripts/enhanced_deploy.php b/scripts/enhanced_deploy.php deleted file mode 100644 index 9b2c2148b..000000000 --- a/scripts/enhanced_deploy.php +++ /dev/null @@ -1,679 +0,0 @@ -zipPath = $zipPath; - $this->targetDir = rtrim($targetDir, DIRECTORY_SEPARATOR); - - $this->config = array_merge([ - 'dry_run' => false, - 'verbose' => false, - 'backup_enabled' => true, - 'backup_dir' => $this->targetDir . '/backups/deploy', - 'delete_enabled' => true, - 'delete_list_file' => '_DELETE_LIST.txt', - 'delete_manifest_file' => '_DELETE_MANIFEST.json', - 'safe_delete' => true, - 'max_backups' => 5, - 'confirmation_required' => false - ], $config); - - $this->dryRun = $this->config['dry_run']; - $this->verbose = $this->config['verbose']; - - $this->validatePaths(); - } - - /** - * 获取相对路径 - */ - private function getRelativePath(string $path, string $basePath): string - { - $basePath = rtrim($basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - return str_replace($basePath, '', $path); - } - - /** - * 执行部署(包含删除功能) - */ - public function deploy(): array - { - $this->logMessage('INFO', '开始增强版自动部署流程'); - $this->logMessage('INFO', "ZIP文件: {$this->zipPath}"); - $this->logMessage('INFO', "目标目录: {$this->targetDir}"); - - try { - // 打开ZIP文件 - $zip = new ZipArchive(); - if ($zip->open($this->zipPath) !== true) { - throw new RuntimeException("无法打开ZIP文件: {$this->zipPath}"); - } - - $this->logMessage('INFO', 'ZIP文件打开成功'); - - // 步骤1:读取删除清单 - $this->loadDeleteList($zip); - - // 步骤2:分析ZIP文件结构 - $zipFiles = $this->analyzeZipStructure($zip); - $this->logMessage('INFO', "ZIP包中包含 {$zipFiles['file_count']} 个文件"); - - // 步骤3:分析目标目录结构 - $targetFiles = $this->analyzeTargetDirectory(); - $this->logMessage('INFO', "目标目录中包含 {$targetFiles['file_count']} 个文件"); - - // 步骤4:确认删除操作(如果需要) - if ($this->config['confirmation_required'] && !empty($this->deleteList)) { - if (!$this->confirmDeletion()) { - $zip->close(); - throw new RuntimeException("用户取消部署操作"); - } - } - - // 步骤5:执行删除操作 - $deleteResult = $this->executeDeletion(); - - // 步骤6:部署新文件和更新文件 - $deployResult = $this->deployFiles($zip, $zipFiles, $targetFiles); - - // 关闭ZIP文件 - $zip->close(); - - // 合并结果 - $result = array_merge($deployResult, [ - 'deletion' => $deleteResult - ]); - - $this->logMessage('SUCCESS', '部署完成'); - return $result; - - } catch (Exception $e) { - $this->logMessage('ERROR', "部署失败: " . $e->getMessage()); - throw $e; - } - } - - /** - * 分析ZIP文件结构 - */ - private function analyzeZipStructure(ZipArchive $zip): array - { - $files = []; - $fileCount = 0; - $totalSize = 0; - - for ($i = 0; $i < $zip->numFiles; $i++) { - $fileInfo = $zip->statIndex($i); - - if ($fileInfo === false) { - continue; - } - - $fileName = $fileInfo['name']; - - // 跳过目录条目 - if (substr($fileName, -1) === '/') { - continue; - } - - $files[$fileName] = [ - 'size' => $fileInfo['size'], - 'compressed_size' => $fileInfo['comp_size'], - 'modified' => $fileInfo['mtime'], - 'crc32' => $fileInfo['crc'] - ]; - - $fileCount++; - $totalSize += $fileInfo['size']; - } - - return [ - 'files' => $files, - 'fileCount' => $fileCount, - 'totalSize' => $totalSize - ]; - } - - /** - * 分析目标目录结构 - */ - private function analyzeTargetDirectory(): array - { - $files = []; - $fileCount = 0; - $totalSize = 0; - - $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator( - $this->targetDir, - RecursiveDirectoryIterator::SKIP_DOTS - ), - RecursiveIteratorIterator::SELF_FIRST - ); - - foreach ($iterator as $file) { - if ($file->isDir()) { - continue; - } - - $relativePath = $this->getRelativePath($file->getPathname(), $this->targetDir); - $files[$relativePath] = [ - 'size' => $file->getSize(), - 'modified' => $file->getMTime(), - 'permissions' => $file->getPerms() - ]; - - $fileCount++; - $totalSize += $file->getSize(); - } - - return [ - 'files' => $files, - 'fileCount' => $fileCount, - 'totalSize' => $totalSize - ]; - } - - - /** - * 加载删除清单 - */ - private function loadDeleteList(ZipArchive $zip): void - { - $this->deleteList = []; - - // 尝试从多个可能的文件中读取删除清单 - $possibleFiles = [ - $this->config['delete_list_file'], - 'DELETE_LIST.txt', - 'delete.list', - 'manifest.json', - $this->config['delete_manifest_file'] - ]; - - foreach ($possibleFiles as $listFile) { - $content = $zip->getFromName($listFile); - if ($content !== false) { - $this->parseDeleteList($content, $listFile); - break; - } - } - - if (empty($this->deleteList)) { - $this->logMessage('INFO', '未找到删除清单文件,跳过删除操作'); - } else { - $this->logMessage('INFO', "找到删除清单,包含 " . count($this->deleteList) . " 个待删除文件"); - } - } - - /** - * 解析删除清单 - */ - private function parseDeleteList(string $content, string $sourceFile): void - { - $this->logMessage('DEBUG', "从文件 {$sourceFile} 解析删除清单"); - - // 尝试解析JSON格式 - if (strpos($sourceFile, '.json') !== false) { - $data = json_decode($content, true); - if ($data && isset($data['files_to_delete'])) { - $this->deleteList = $data['files_to_delete']; - return; - } - } - - // 解析文本格式(每行一个文件路径) - $lines = explode("\n", $content); - foreach ($lines as $line) { - $line = trim($line); - - // 跳过空行和注释 - if (empty($line) || $line[0] === '#' || $line[0] === ';') { - continue; - } - - $this->deleteList[] = $line; - } - - // 去重 - $this->deleteList = array_unique($this->deleteList); - } - - /** - * 执行删除操作 - */ - private function executeDeletion(): array - { - if (!$this->config['delete_enabled'] || empty($this->deleteList)) { - return [ - 'total' => 0, - 'deleted' => 0, - 'skipped' => 0, - 'errors' => 0, - 'details' => [] - ]; - } - - $result = [ - 'total' => count($this->deleteList), - 'deleted' => 0, - 'skipped' => 0, - 'errors' => 0, - 'details' => [] - ]; - - $this->logMessage('INFO', "开始执行删除操作,共 {$result['total']} 个文件"); - - foreach ($this->deleteList as $fileToDelete) { - try { - $fullPath = $this->targetDir . DIRECTORY_SEPARATOR . $fileToDelete; - - if (!$this->shouldDeleteFile($fullPath, $fileToDelete)) { - $result['skipped']++; - $result['details'][] = [ - 'file' => $fileToDelete, - 'action' => 'skipped', - 'reason' => '安全检查未通过', - 'success' => true - ]; - continue; - } - - $deleteResult = $this->deleteFile($fullPath, $fileToDelete); - - if ($deleteResult) { - $result['deleted']++; - $result['details'][] = [ - 'file' => $fileToDelete, - 'action' => 'deleted', - 'success' => true - ]; - } else { - $result['skipped']++; - $result['details'][] = [ - 'file' => $fileToDelete, - 'action' => 'skipped', - 'reason' => '文件不存在或无法删除', - 'success' => true - ]; - } - - } catch (Exception $e) { - $result['errors']++; - $result['details'][] = [ - 'file' => $fileToDelete, - 'action' => 'error', - 'error' => $e->getMessage(), - 'success' => false - ]; - $this->logMessage('ERROR', "删除文件失败 {$fileToDelete}: " . $e->getMessage()); - } - } - - $this->logMessage('SUCCESS', "删除操作完成: 成功 {$result['deleted']} 个, 跳过 {$result['skipped']} 个, 错误 {$result['errors']} 个"); - - return $result; - } - - /** - * 检查是否应该删除文件 - */ - private function shouldDeleteFile(string $fullPath, string $relativePath): bool - { - // 安全检查:确保路径在目标目录内 - $realTarget = realpath($this->targetDir); - $realFile = realpath($fullPath); - - if ($realFile === false || strpos($realFile, $realTarget) !== 0) { - $this->logMessage('WARNING', "安全警告: 跳过删除外部文件 {$relativePath}"); - return false; - } - - // 检查重要文件保护 - $protectedPatterns = [ - '/\.env$/', - '/config\.php$/', - '/database\.php$/', - '/\.htaccess$/', - '/web\.config$/', - '/index\.php$/', - '/composer\.json$/', - '/composer\.lock$/', - '/package\.json$/', - '/yarn\.lock$/', - '/\.gitignore$/' - ]; - - foreach ($protectedPatterns as $pattern) { - if (preg_match($pattern, $relativePath)) { - $this->logMessage('WARNING', "保护文件: 跳过删除 {$relativePath}"); - return false; - } - } - - // 检查文件是否存在 - if (!file_exists($fullPath)) { - $this->logMessage('DEBUG', "文件不存在,跳过删除: {$relativePath}"); - return false; - } - - // 检查是否为目录 - if (is_dir($fullPath)) { - $this->logMessage('WARNING', "跳过删除目录: {$relativePath}"); - return false; - } - - return true; - } - - /** - * 删除单个文件 - */ - private function deleteFile(string $fullPath, string $relativePath): bool - { - if ($this->dryRun) { - $this->logMessage('DRY_RUN', "将删除文件: {$relativePath}"); - return true; - } - - // 备份文件(如果启用) - if ($this->config['backup_enabled']) { - $this->backupFile($fullPath, $relativePath); - } - - // 删除文件 - if (unlink($fullPath)) { - $this->logMessage('INFO', "文件已删除: {$relativePath}"); - - // 尝试删除空目录 - $this->cleanupEmptyDirectories(dirname($fullPath)); - - return true; - } else { - $this->logMessage('ERROR', "无法删除文件: {$relativePath}"); - return false; - } - } - - /** - * 备份文件 - */ - private function backupFile(string $filePath, string $relativePath): void - { - if (!file_exists($filePath)) { - return; - } - - $backupDir = $this->config['backup_dir']; - if (!is_dir($backupDir)) { - mkdir($backupDir, 0755, true); - } - - $backupPath = $backupDir . DIRECTORY_SEPARATOR . - date('Ymd_His') . '_' . - str_replace('/', '_', $relativePath); - - $backupDir = dirname($backupPath); - if (!is_dir($backupDir)) { - mkdir($backupDir, 0755, true); - } - - if (copy($filePath, $backupPath)) { - $this->logMessage('INFO', "文件已备份: {$backupPath}"); - } else { - $this->logMessage('WARNING', "文件备份失败: {$relativePath}"); - } - } - - /** - * 清理空目录 - */ - private function cleanupEmptyDirectories(string $directory): void - { - $currentDir = $directory; - $baseDir = realpath($this->targetDir); - - while ($currentDir !== $baseDir && is_dir($currentDir)) { - // 检查目录是否为空 - $files = scandir($currentDir); - if (count($files) === 2) { // 只有 . 和 .. - if ($this->dryRun) { - $this->logMessage('DRY_RUN', "将删除空目录: {$currentDir}"); - } else { - if (rmdir($currentDir)) { - $this->logMessage('INFO', "空目录已删除: {$currentDir}"); - } - } - - $currentDir = dirname($currentDir); - } else { - break; - } - } - } - - /** - * 确认删除操作 - */ - private function confirmDeletion(): bool - { - echo "\n⚠️ 警告: 以下文件将被删除:\n"; - echo "========================================\n"; - - foreach ($this->deleteList as $index => $file) { - echo sprintf("%3d. %s\n", $index + 1, $file); - } - - echo "========================================\n"; - echo "总共 " . count($this->deleteList) . " 个文件将被删除\n\n"; - - echo "是否继续?(y/N): "; - $handle = fopen('php://stdin', 'r'); - $input = trim(fgets($handle)); - fclose($handle); - - return strtolower($input) === 'y'; - } - - /** - * 部署文件(从父类继承或实现) - */ - private function deployFiles(ZipArchive $zip, array $zipFiles, array $targetFiles): array - { - // 这里实现文件部署逻辑,与之前的AutoDeployer类似 - // 包括添加新文件和更新现有文件 - $result = [ - 'added' => 0, - 'updated' => 0, - 'skipped' => 0, - 'errors' => 0, - 'details' => [] - ]; - - // 实现文件部署逻辑... - - return $result; - } - - /** - * 记录日志 - */ - private function logMessage(string $level, string $message): void - { - $timestamp = date('Y-m-d H:i:s'); - $logEntry = "[{$timestamp}] [{$level}] {$message}"; - - $this->log[] = $logEntry; - - if ($this->verbose || in_array($level, ['ERROR', 'SUCCESS', 'WARNING'])) { - echo $logEntry . PHP_EOL; - } - } - - /** - * 验证路径 - */ - private function validatePaths(): void - { - if (!file_exists($this->zipPath)) { - throw new InvalidArgumentException("ZIP文件不存在: {$this->zipPath}"); - } - - if (!is_dir($this->targetDir)) { - throw new InvalidArgumentException("目标目录不存在: {$this->targetDir}"); - } - } - - /** - * 获取日志 - */ - public function getLog(): array - { - return $this->log; - } -} - -// 命令行接口 -class EnhancedDeployerCLI -{ - public function run(array $argv): void - { - try { - $options = $this->parseOptions($argv); - - if ($options['help']) { - $this->showHelp(); - exit(0); - } - - if (!$options['zip'] || !$options['target']) { - echo "错误: 必须指定 --zip 和 --target 参数\n"; - $this->showHelp(); - exit(1); - } - - $deployer = new EnhancedAutoDeployer($options['zip'], $options['target'], [ - 'dry_run' => $options['dry-run'], - 'verbose' => $options['verbose'], - 'confirmation_required' => $options['confirm'] - ]); - - $result = $deployer->deploy(); - $this->showResults($result); - - } catch (Exception $e) { - echo "错误: " . $e->getMessage() . PHP_EOL; - exit(1); - } - } - - private function parseOptions(array $argv): array - { - $options = [ - 'help' => false, - 'dry-run' => false, - 'verbose' => false, - 'confirm' => false, - 'zip' => null, - 'target' => null - ]; - - for ($i = 1; $i < count($argv); $i++) { - switch ($argv[$i]) { - case '--help': - case '-h': - $options['help'] = true; - break; - - case '--dry-run': - case '-d': - $options['dry-run'] = true; - break; - - case '--verbose': - case '-v': - $options['verbose'] = true; - break; - - case '--confirm': - case '-c': - $options['confirm'] = true; - break; - - case '--zip': - $options['zip'] = $argv[++$i] ?? null; - break; - - case '--target': - $options['target'] = $argv[++$i] ?? null; - break; - } - } - - return $options; - } - - private function showHelp(): void - { - echo "增强版自动部署工具\n\n"; - echo "用法: php enhanced_deploy.php [选项]\n\n"; - echo "选项:\n"; - echo " --zip ZIP文件路径 (必需)\n"; - echo " --target 目标目录路径 (必需)\n"; - echo " --dry-run, -d 试运行,不实际修改文件\n"; - echo " --verbose, -v 显示详细输出\n"; - echo " --confirm, -c 确认删除操作\n"; - echo " --help, -h 显示此帮助信息\n\n"; - echo "示例:\n"; - echo " php enhanced_deploy.php --zip update.zip --target /var/www/html\n"; - echo " php enhanced_deploy.php --zip patch.zip --target ./app --dry-run\n"; - echo " php enhanced_deploy.php --zip deploy.zip --target /var/www --confirm\n"; - } - - private function showResults(array $result): void - { - echo "\n部署结果:\n"; - echo "========================================\n"; - echo "文件部署:\n"; - echo " 新增: {$result['added']} 个文件\n"; - echo " 更新: {$result['updated']} 个文件\n"; - echo " 跳过: {$result['skipped']} 个文件\n"; - echo " 错误: {$result['errors']} 个文件\n\n"; - - echo "文件删除:\n"; - echo " 总计: {$result['deletion']['total']} 个文件\n"; - echo " 删除: {$result['deletion']['deleted']} 个文件\n"; - echo " 跳过: {$result['deletion']['skipped']} 个文件\n"; - echo " 错误: {$result['deletion']['errors']} 个文件\n"; - echo "========================================\n"; - } -} - -// 主程序入口 -if (PHP_SAPI === 'cli') { - $cli = new EnhancedDeployerCLI(); - $cli->run($argv); -} else { - echo "此脚本只能在命令行模式下运行\n"; - exit(1); -} -?> \ No newline at end of file diff --git a/scripts/generate_deploy_package.php b/scripts/generate_deploy_package.php deleted file mode 100644 index 37619256c..000000000 --- a/scripts/generate_deploy_package.php +++ /dev/null @@ -1,801 +0,0 @@ -sourceDir = rtrim($sourceDir, DIRECTORY_SEPARATOR); - $this->targetDir = rtrim($targetDir, DIRECTORY_SEPARATOR); - $this->outputPath = $outputPath; - - $this->config = array_merge([ - 'exclude_patterns' => [ - '/\.git/', // Git目录 - '/\.svn/', // SVN目录 - '/node_modules/', // Node模块 - '/vendor/', // Composer依赖 - '/cache/', // 缓存目录 - '/logs/', // 日志目录 - '/temp/', // 临时目录 - '/tmp/', // 临时目录 - '/backup/', // 备份目录 - '/runtime/', // 运行时目录 - '/upload/', // 上传目录 - '/\.DS_Store$/', // Mac系统文件 - '/Thumbs\.db$/', // Windows缩略图 - '/\.log$/', // 日志文件 - '/\.tmp$/', // 临时文件 - '/\.env$/', // 环境变量 - '/composer\.lock$/', // Composer依赖锁文件 - ], - 'include_patterns' => [], // 包含模式(优先于排除) - 'compare_method' => 'content', // content, size, mtime, hash - 'hash_algorithm' => 'md5', // md5, sha1, crc32 - 'min_file_size' => 0, // 最小文件大小(字节) - 'max_file_size' => 10485760, // 最大文件大小(10MB) - 'dry_run' => false, // 试运行 - 'verbose' => false, // 详细输出 - 'backup_old' => true, // 备份旧版本 - 'compression_level' => 9, // ZIP压缩级别 0-9 - ], $config); - - $this->validateDirectories(); - } - - /** - * 验证目录有效性 - */ - private function validateDirectories(): void - { - if (!is_dir($this->sourceDir)) { - throw new InvalidArgumentException("源目录不存在: {$this->sourceDir}"); - } - - if (!is_readable($this->sourceDir)) { - throw new InvalidArgumentException("源目录不可读: {$this->sourceDir}"); - } - - if (!is_dir($this->targetDir)) { - $this->logMessage('WARNING', "目标目录不存在,将创建完整部署包: {$this->targetDir}"); - } elseif (!is_readable($this->targetDir)) { - throw new InvalidArgumentException("目标目录不可读: {$this->targetDir}"); - } - - $outputDir = dirname($this->outputPath); - if (!is_dir($outputDir)) { - throw new InvalidArgumentException("输出目录不存在: {$outputDir}"); - } - - if (!is_writable($outputDir)) { - throw new InvalidArgumentException("输出目录不可写: {$outputDir}"); - } - } - - /** - * 生成部署包 - */ - public function generate(): array - { - $this->logMessage('INFO', '开始生成部署包'); - $this->logMessage('INFO', "源目录: {$this->sourceDir}"); - $this->logMessage('INFO', "目标目录: {$this->targetDir}"); - $this->logMessage('INFO', "输出文件: {$this->outputPath}"); - - try { - // 步骤1:分析目录结构 - $this->logMessage('INFO', '分析目录结构...'); - $sourceFiles = $this->analyzeDirectory($this->sourceDir); - $targetFiles = is_dir($this->targetDir) ? $this->analyzeDirectory($this->targetDir) : []; - - $this->logMessage('INFO', "源目录文件数: " . count($sourceFiles)); - $this->logMessage('INFO', "目标目录文件数: " . count($targetFiles)); - - // 步骤2:比较文件差异 - $this->logMessage('INFO', '比较文件差异...'); - $diffFiles = $this->compareFiles($sourceFiles, $targetFiles); - - // 步骤3:生成部署包 - $this->logMessage('INFO', '生成部署ZIP包...'); - $result = $this->createDeployPackage($diffFiles); - - $this->logMessage('SUCCESS', '部署包生成完成'); - return array_merge($result, [ - 'comparison' => $this->comparisonResult - ]); - - } catch (Exception $e) { - $this->logMessage('ERROR', "生成失败: " . $e->getMessage()); - throw $e; - } - } - - /** - * 分析目录结构 - */ - private function analyzeDirectory(string $directory): array - { - $files = []; - $basePath = rtrim($directory, DIRECTORY_SEPARATOR); - - $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator( - $directory, - FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS - ), - RecursiveIteratorIterator::SELF_FIRST - ); - - foreach ($iterator as $file) { - if ($file->isDir()) { - continue; - } - - $filePath = $file->getPathname(); - $relativePath = $this->getRelativePath($filePath, $basePath); - - // 检查是否应该排除 - if ($this->shouldExclude($relativePath)) { - continue; - } - - // 检查文件大小限制 - $fileSize = $file->getSize(); - if ($fileSize < $this->config['min_file_size'] || $fileSize > $this->config['max_file_size']) { - $this->logMessage('DEBUG', "跳过文件(大小限制): {$relativePath} ({$fileSize} bytes)"); - continue; - } - - $files[$relativePath] = [ - 'full_path' => $filePath, - 'size' => $fileSize, - 'modified' => $file->getMTime(), - 'permissions' => $file->getPerms(), - 'hash' => $this->calculateFileHash($filePath) - ]; - } - - return $files; - } - - /** - * 比较文件差异 - */ - private function compareFiles(array $sourceFiles, array $targetFiles): array - { - $diffFiles = [ - 'added' => [], // 新增文件 - 'modified' => [], // 修改文件 - 'deleted' => [], // 删除文件(记录以便清理) - 'unchanged' => [] // 未改变文件 - ]; - - $this->comparisonResult = [ - 'total_source_files' => count($sourceFiles), - 'total_target_files' => count($targetFiles), - 'files_analyzed' => 0 - ]; - - // 检查新增和修改的文件 - foreach ($sourceFiles as $relativePath => $sourceFileInfo) { - $this->comparisonResult['files_analyzed']++; - - if (!isset($targetFiles[$relativePath])) { - // 新增文件 - $diffFiles['added'][$relativePath] = $sourceFileInfo; - $this->logMessage('INFO', "新增文件: {$relativePath}"); - } else { - // 文件存在,检查是否需要更新 - $targetFileInfo = $targetFiles[$relativePath]; - - if ($this->needsUpdate($sourceFileInfo, $targetFileInfo)) { - $diffFiles['modified'][$relativePath] = $sourceFileInfo; - $this->logMessage('INFO', "修改文件: {$relativePath}"); - } else { - $diffFiles['unchanged'][$relativePath] = $sourceFileInfo; - $this->logMessage('DEBUG', "未改变: {$relativePath}"); - } - } - } - - // 检查需要删除的文件 - foreach ($targetFiles as $relativePath => $targetFileInfo) { - if (!isset($sourceFiles[$relativePath])) { - $diffFiles['deleted'][$relativePath] = $targetFileInfo; - $this->logMessage('INFO', "待删除文件: {$relativePath}"); - } - } - - $this->comparisonResult = array_merge($this->comparisonResult, [ - 'added_files' => count($diffFiles['added']), - 'modified_files' => count($diffFiles['modified']), - 'deleted_files' => count($diffFiles['deleted']), - 'unchanged_files' => count($diffFiles['unchanged']) - ]); - - $this->logMessage('INFO', "比较完成: 新增{$this->comparisonResult['added_files']}个, 修改{$this->comparisonResult['modified_files']}个, 删除{$this->comparisonResult['deleted_files']}个"); - - return $diffFiles; - } - - /** - * 检查文件是否需要更新 - */ - private function needsUpdate(array $sourceFile, array $targetFile): bool - { - $method = $this->config['compare_method']; - - switch ($method) { - case 'content': - // 内容比较(最准确) - return $sourceFile['hash'] !== $targetFile['hash']; - - case 'size': - // 大小比较 - return $sourceFile['size'] !== $targetFile['size']; - - case 'mtime': - // 修改时间比较 - return $sourceFile['modified'] > $targetFile['modified']; - - case 'hash': - default: - // 哈希比较(默认) - return $sourceFile['hash'] !== $targetFile['hash']; - } - } - - /** - * 创建部署包 - */ - protected function createDeployPackage(array $diffFiles): array - { - if ($this->config['dry_run']) { - $this->logMessage('DRY_RUN', "试运行模式,将创建包含 " . - (count($diffFiles['added']) + count($diffFiles['modified'])) . " 个文件的ZIP包"); - return $this->simulatePackageCreation($diffFiles); - } - - $zip = new ZipArchive(); - $openResult = $zip->open($this->outputPath, ZipArchive::CREATE | ZipArchive::OVERWRITE); - - if ($openResult !== true) { - throw new RuntimeException("无法创建ZIP文件,错误代码: {$openResult}"); - } - - $addedCount = 0; - $modifiedCount = 0; - - // 添加新增和修改的文件 - $filesToAdd = array_merge($diffFiles['added'], $diffFiles['modified']); - - - $fileIndex = 0; - foreach ($filesToAdd as $relativePath => $fileInfo) { - try { - // 确保目录结构被正确保留 - // 首先尝试直接添加文件 - if ($zip->addFile($fileInfo['full_path'], $relativePath)) { - $zip->setCompressionName($relativePath, ZipArchive::CM_DEFLATE); - $zip->setCompressionIndex($fileIndex, ZipArchive::CM_DEFLATE, $this->config['compression_level']); - $fileIndex++; - - if (isset($diffFiles['added'][$relativePath])) { - $addedCount++; - } else { - $modifiedCount++; - } - - $this->logMessage('DEBUG', "添加到ZIP: {$relativePath}"); - } else { - // 如果直接添加失败,尝试先创建目录结构 - $dir = dirname($relativePath); - if ($dir !== '.' && $dir !== '') { - // 确保所有父目录都被创建 - $parts = explode(DIRECTORY_SEPARATOR, $dir); - $currentPath = ''; - foreach ($parts as $part) { - $currentPath .= $part . DIRECTORY_SEPARATOR; - $currentPath = rtrim($currentPath, DIRECTORY_SEPARATOR); - if ($currentPath !== '' && !$zip->addEmptyDir($currentPath)) { - // 添加空目录失败可能是因为目录已存在,继续尝试 - continue; - } - } - } - - // 再次尝试添加文件 - if ($zip->addFile($fileInfo['full_path'], $relativePath)) { - $zip->setCompressionName($relativePath, ZipArchive::CM_DEFLATE); - $zip->setCompressionIndex($fileIndex, ZipArchive::CM_DEFLATE, $this->config['compression_level']); - $fileIndex++; - - if (isset($diffFiles['added'][$relativePath])) { - $addedCount++; - } else { - $modifiedCount++; - } - - $this->logMessage('DEBUG', "添加到ZIP (创建目录后): {$relativePath}"); - } else { - $this->logMessage('WARNING', "添加文件失败: {$relativePath}"); - } - } - } catch (Exception $e) { - $this->logMessage('ERROR', "处理文件失败 {$relativePath}: " . $e->getMessage()); - } - } - - // 创建删除列表文件 - if (!empty($diffFiles['deleted'])) { - $deleteListContent = "# 需要删除的文件列表\n# 生成时间: " . date('Y-m-d H:i:s') . "\n\n"; - foreach (array_keys($diffFiles['deleted']) as $fileToDelete) { - $deleteListContent .= $fileToDelete . "\n"; - } - - $zip->addFromString('_DELETE_LIST.txt', $deleteListContent); - $this->logMessage('INFO', "创建删除列表,包含 " . count($diffFiles['deleted']) . " 个文件"); - } - - // 创建部署清单 - $manifest = $this->createManifest($diffFiles); - $zip->addFromString('_DEPLOY_MANIFEST.json', json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - - // 关闭ZIP文件 - if (!$zip->close()) { - throw new RuntimeException("ZIP文件保存失败"); - } - - // 验证ZIP文件 - $zipSize = filesize($this->outputPath); - $fileCount = $zip->numFiles; - - $result = [ - 'output_path' => $this->outputPath, - 'file_size' => $zipSize, - 'file_size_human' => $this->formatBytes($zipSize), - 'files_added' => $addedCount, - 'files_modified' => $modifiedCount, - 'files_deleted' => count($diffFiles['deleted']), - 'total_files' => $addedCount + $modifiedCount, - 'compression_level' => $this->config['compression_level'], - 'created_at' => date('Y-m-d H:i:s') - ]; - - $this->logMessage('SUCCESS', "部署包创建成功: {$result['file_size_human']}, 包含 {$result['total_files']} 个文件"); - - return $result; - } - - /** - * 创建部署清单 - */ - private function createManifest(array $diffFiles): array - { - return [ - 'metadata' => [ - 'generator' => 'DeployPackageGenerator', - 'version' => '1.0', - 'created_at' => date('c'), - 'source_dir' => $this->sourceDir, - 'target_dir' => $this->targetDir, - 'compare_method' => $this->config['compare_method'] - ], - 'statistics' => $this->comparisonResult, - 'files' => [ - 'added' => array_keys($diffFiles['added']), - 'modified' => array_keys($diffFiles['modified']), - 'deleted' => array_keys($diffFiles['deleted']) - ], - 'file_details' => [ - 'added' => $diffFiles['added'], - 'modified' => $diffFiles['modified'] - ] - ]; - } - - /** - * 试运行模式 - */ - private function simulatePackageCreation(array $diffFiles): array - { - $totalSize = 0; - - foreach (array_merge($diffFiles['added'], $diffFiles['modified']) as $fileInfo) { - $totalSize += $fileInfo['size']; - } - - return [ - 'output_path' => $this->outputPath, - 'file_size' => $totalSize, - 'file_size_human' => $this->formatBytes($totalSize), - 'files_added' => count($diffFiles['added']), - 'files_modified' => count($diffFiles['modified']), - 'files_deleted' => count($diffFiles['deleted']), - 'total_files' => count($diffFiles['added']) + count($diffFiles['modified']), - 'dry_run' => true, - 'message' => '试运行模式 - 未实际创建文件' - ]; - } - - /** - * 检查是否应该排除文件 - */ - private function shouldExclude(string $relativePath): bool - { - // 首先检查包含模式 - foreach ($this->config['include_patterns'] as $pattern) { - if (preg_match($pattern, $relativePath)) { - return false; - } - } - - // 然后检查排除模式 - foreach ($this->config['exclude_patterns'] as $pattern) { - if (preg_match($pattern, $relativePath)) { - $this->logMessage('DEBUG', "排除文件: {$relativePath} (匹配模式: {$pattern})"); - return true; - } - } - - return false; - } - - /** - * 计算文件哈希 - */ - private function calculateFileHash(string $filePath): string - { - $algorithm = $this->config['hash_algorithm']; - - switch ($algorithm) { - case 'md5': - return md5_file($filePath); - case 'sha1': - return sha1_file($filePath); - case 'crc32': - return hash_file('crc32b', $filePath); - default: - return md5_file($filePath); - } - } - - /** - * 获取相对路径 - */ - private function getRelativePath(string $path, string $basePath): string - { - $basePath = rtrim($basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - return str_replace($basePath, '', $path); - } - - /** - * 格式化字节大小 - */ - private function formatBytes(int $bytes, int $precision = 2): string - { - $units = ['B', 'KB', 'MB', 'GB', 'TB']; - $bytes = max($bytes, 0); - $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); - $pow = min($pow, count($units) - 1); - $bytes /= pow(1024, $pow); - - return round($bytes, $precision) . ' ' . $units[$pow]; - } - - /** - * 记录日志 - */ - private function logMessage(string $level, string $message): void - { - $timestamp = date('Y-m-d H:i:s'); - $logEntry = "[{$timestamp}] [{$level}] {$message}"; - - $this->log[] = $logEntry; - - if ($this->config['verbose'] || in_array($level, ['ERROR', 'SUCCESS', 'WARNING'])) { - echo $logEntry . PHP_EOL; - } - } - - /** - * 获取日志 - */ - public function getLog(): array - { - return $this->log; - } -} - -/** - * 增强版部署包生成器 - * 生成包含删除清单的部署包 - */ -class EnhancedPackageGenerator extends DeployPackageGenerator -{ - /** - * 创建部署包(增强版) - */ - protected function createDeployPackage(array $diffFiles): array - { - $result = parent::createDeployPackage($diffFiles); - - // 添加删除清单 - if (!empty($diffFiles['deleted'])) { - $this->addDeleteManifest($diffFiles['deleted']); - } - - return $result; - } - - /** - * 添加删除清单文件 - */ - private function addDeleteManifest(array $deletedFiles): void - { - if ($this->config['dry_run']) { - $this->logMessage('DRY_RUN', "将创建删除清单,包含 " . count($deletedFiles) . " 个文件"); - return; - } - - $zip = new ZipArchive(); - if ($zip->open($this->outputPath) !== true) { - throw new RuntimeException("无法打开ZIP文件添加删除清单"); - } - - // 文本格式删除清单 - $textContent = $this->createTextDeleteList($deletedFiles); - $zip->addFromString('DELETE_LIST.txt', $textContent); - - // JSON格式删除清单 - $jsonContent = $this->createJsonDeleteList($deletedFiles); - $zip->addFromString('delete_manifest.json', $jsonContent); - - $zip->close(); - - $this->logMessage('INFO', "删除清单已添加: " . count($deletedFiles) . " 个待删除文件"); - } - - /** - * 创建文本格式删除清单 - */ - private function createTextDeleteList(array $deletedFiles): string - { - $content = "# 自动部署删除清单\n"; - $content .= "# 生成时间: " . date('Y-m-d H:i:s') . "\n"; - $content .= "# 此文件列出了需要删除的文件列表\n\n"; - - foreach (array_keys($deletedFiles) as $filePath) { - $content .= $filePath . "\n"; - } - - $content .= "\n# 注释以 # 开头\n"; - $content .= "# 空行和注释行将被忽略\n"; - - return $content; - } - - /** - * 创建JSON格式删除清单 - */ - private function createJsonDeleteList(array $deletedFiles): string - { - $manifest = [ - 'version' => '1.0', - 'generated_at' => date('c'), - 'description' => '自动部署删除清单', - 'files_to_delete' => array_keys($deletedFiles), - 'total_files' => count($deletedFiles), - 'metadata' => [ - 'safe_deletion' => true, - 'backup_recommended' => true, - 'generator' => 'EnhancedPackageGenerator' - ] - ]; - - return json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); - } -} - -// 命令行接口 -class PackageGeneratorCLI -{ - private array $options; - - public function __construct(array $argv) - { - $this->options = $this->parseOptions($argv); - } - - public function run(): void - { - try { - $this->showBanner(); - - if ($this->options['help'] ?? false) { - $this->showHelp(); - exit(0); - } - - // 验证参数 - $sourceDir = $this->options['source'] ?? null; - $targetDir = $this->options['target'] ?? null; - $outputFile = $this->options['output'] ?? null; - - if (!$sourceDir || !$targetDir || !$outputFile) { - echo "错误: 必须指定 --source, --target 和 --output 参数\n"; - $this->showHelp(); - exit(1); - } - - // 构建配置 - $config = [ - 'dry_run' => $this->options['dry-run'] ?? false, - 'verbose' => $this->options['verbose'] ?? false, - 'compare_method' => $this->options['compare'] ?? 'content' - ]; - - // 加载配置文件 - if (isset($this->options['config'])) { - $config = array_merge($config, $this->loadConfigFile($this->options['config'])); - } - - // 创建生成器实例 - $generator = new DeployPackageGenerator($sourceDir, $targetDir, $outputFile, $config); - - // 生成部署包 - $result = $generator->generate(); - - // 显示结果 - $this->showResults($result); - - } catch (Exception $e) { - echo "错误: " . $e->getMessage() . PHP_EOL; - exit(1); - } - } - - private function parseOptions(array $argv): array - { - $options = [ - 'help' => false, - 'verbose' => false, - 'dry-run' => false - ]; - - for ($i = 1; $i < count($argv); $i++) { - $arg = $argv[$i]; - - switch ($arg) { - case '--help': - case '-h': - $options['help'] = true; - break; - - case '--verbose': - case '-v': - $options['verbose'] = true; - break; - - case '--dry-run': - case '-d': - $options['dry-run'] = true; - break; - - case '--source': - case '-s': - $options['source'] = $argv[++$i] ?? null; - break; - - case '--target': - case '-t': - $options['target'] = $argv[++$i] ?? null; - break; - - case '--output': - case '-o': - $options['output'] = $argv[++$i] ?? null; - break; - - case '--config': - case '-c': - $options['config'] = $argv[++$i] ?? null; - break; - - case '--compare': - $options['compare'] = $argv[++$i] ?? 'content'; - break; - - default: - if (substr($arg, 0, 1) === '-') { - echo "警告: 未知选项 {$arg}\n"; - } - break; - } - } - - return $options; - } - - private function loadConfigFile(string $configPath): array - { - if (!file_exists($configPath)) { - throw new InvalidArgumentException("配置文件不存在: {$configPath}"); - } - - $config = include $configPath; - - if (!is_array($config)) { - throw new InvalidArgumentException("无效的配置文件: {$configPath}"); - } - - return $config; - } - - private function showBanner(): void - { - echo "========================================\n"; - echo " 部署包生成工具 v1.0\n"; - echo " PHP 7.4 兼容版本\n"; - echo "========================================\n\n"; - } - - private function showHelp(): void - { - echo "用法: php generate_deploy_package.php [选项]\n\n"; - echo "选项:\n"; - echo " --source, -s 源目录(新版本)\n"; - echo " --target, -t 目标目录(旧版本)\n"; - echo " --output, -o 输出ZIP文件路径\n"; - echo " --config, -c 配置文件路径\n"; - echo " --compare 比较方法 (content, size, mtime, hash)\n"; - echo " --dry-run, -d 试运行,不实际创建文件\n"; - echo " --verbose, -v 显示详细输出\n"; - echo " --help, -h 显示此帮助信息\n\n"; - echo "示例:\n"; - echo " php generate_deploy_package.php --source ./new_version --target ./old_version --output update.zip\n"; - echo " php generate_deploy_package.php -s /var/www/new -t /var/www/old -o patch.zip --dry-run\n"; - echo " php generate_deploy_package.php --source ./v2.0 --target ./v1.0 --output deploy.zip --config deploy.conf.php\n"; - } - - private function showResults(array $result): void - { - echo "\n生成结果:\n"; - echo "========================================\n"; - echo "输出文件: {$result['output_path']}\n"; - echo "文件大小: {$result['file_size_human']}\n"; - echo "包含文件: {$result['total_files']} 个\n"; - echo " - 新增: {$result['files_added']} 个\n"; - echo " - 修改: {$result['files_modified']} 个\n"; - echo " - 删除: {$result['files_deleted']} 个\n"; - - if (isset($result['dry_run'])) { - echo "模式: 试运行(未实际创建文件)\n"; - } - - echo "========================================\n"; - } -} - -// 主程序入口 -if (PHP_SAPI === 'cli') { - $cli = new PackageGeneratorCLI($argv); - $cli->run(); -} else { - echo "此脚本只能在命令行模式下运行\n"; - exit(1); -} -?> \ No newline at end of file diff --git a/scripts/patch_tools/docker-compose.yml b/scripts/patch_tools/docker-compose.yml deleted file mode 100644 index a0fe647ee..000000000 --- a/scripts/patch_tools/docker-compose.yml +++ /dev/null @@ -1,16 +0,0 @@ -# TODO: 创建一个基于alpine的docker镜像,用于运行patch工具 -# 镜像名称:patch-tools-alpine -# 基础镜像:alpine:3.18 -# 安装依赖:bash, tar, gzip, zip, diffutils, patch -# 复制脚本:patch_config.sh, patch.sh -# 入口点:/bin/bash /patch.sh - -services: - patch-tools: - image: ubuntu/squid:latest - container_name: patch-tools-alpine - volumes: - - ./:/working_dir/scripts - - ../patch-dir:/working_dir/patchs - - ../../ftp-src/:/working_dir/old-shop-src - - ../../src/:/working_dir/new-shop-src diff --git a/scripts/patch_tools/install_patch_system.sh b/scripts/patch_tools/install_patch_system.sh deleted file mode 100644 index b206bdae6..000000000 --- a/scripts/patch_tools/install_patch_system.sh +++ /dev/null @@ -1,298 +0,0 @@ -#!/bin/bash -# install_patch_system.sh - 补丁管理系统安装脚本 - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -INSTALL_DIR="/opt/patch-management" -CONFIG_FILE="${SCRIPT_DIR}/patch_config.sh" - -log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"; } -info() { log "INFO: $1"; } -error() { log "ERROR: $1"; exit 1; } -warn() { log "WARNING: $1"; } - -# 检测是否在Docker容器中运行 -is_docker_environment() { - # 检查多种Docker环境标识 - if [[ -f /.dockerenv ]]; then - return 0 - fi - - if grep -q docker /proc/1/cgroup 2>/dev/null; then - return 0 - fi - - if grep -q lxc /proc/1/cgroup 2>/dev/null; then - return 0 - fi - - if [[ -n "${container:-}" ]]; then - return 0 - fi - - if [[ -n "${DOCKER_CONTAINER:-}" ]]; then - return 0 - fi - - # 检查容器相关的环境变量组合 - local env_vars=( - "HOSTNAME" - "HOME" - "USER" - ) - - for var in "${env_vars[@]}"; do - if [[ -n "${!var:-}" ]] && [[ "${!var}" =~ ^[a-f0-9]{12,}$ ]]; then - # 检查环境变量值是否像容器ID - if [[ ${!var} =~ ^[a-f0-9]{12,64}$ ]]; then - return 0 - fi - fi - done - - return 1 -} - -# 获取适当的命令前缀(在Docker中使用空前缀,否则使用sudo) -get_cmd_prefix() { - if is_docker_environment; then - echo "" - else - echo "sudo" - fi -} - -# 配置加载 -load_config() { - if [[ ! -f "$CONFIG_FILE" ]]; then - error "配置文件不存在: $CONFIG_FILE" - exit 1 - fi - - source "$CONFIG_FILE" - info "配置文件加载完成" -} - -install_dependencies() { - info "安装系统依赖..." - - local sudo_prefix - sudo_prefix=$(get_cmd_prefix) - - - local will_install_dependencies=false - local dependencies=( - "tar" - "gzip" - "bzip2" - "jq" - "gpg" - "bc" - ) - - for dep in "${dependencies[@]}"; do - if command -v "$dep" >/dev/null 2>&1; then - info "系统依赖 $dep 已安装" - else - warn "系统依赖 $dep 未安装" - will_install_dependencies=true - fi - done - - if ! $will_install_dependencies; then - info "系统依赖已安装" - return 0 - fi - - # 关键依赖 - local keys_deps=( - "coreutils" - "findutils" - "util-linux" - ) - - if command -v apt-get >/dev/null 2>&1; then - # Debian/Ubuntu - $sudo_prefix apt-get update - $sudo_prefix apt-get install -y $(printf "%s " "${keys_deps[@]}") $(printf "%s " "${dependencies[@]}") - elif command -v yum >/dev/null 2>&1; then - # CentOS/RHEL - $sudo_prefix yum install -y $(printf "%s " "${keys_deps[@]}") $(printf "%s " "${dependencies[@]}") - else - warn "无法自动安装依赖,请手动安装: $(printf "%s " "${keys_deps[@]}") $(printf "%s " "${dependencies[@]}")" - fi - - # 安装GPG(用于签名验证) - if command -v apt-get >/dev/null 2>&1; then - $sudo_prefix apt-get install -y gnupg - elif command -v yum >/dev/null 2>&1; then - $sudo_prefix yum install -y gnupg - fi -} - -create_directories() { - info "创建目录结构..." - - local sudo_prefix - sudo_prefix=$(get_cmd_prefix) - - $sudo_prefix mkdir -p "$INSTALL_DIR" - $sudo_prefix mkdir -p "/var/backups/patch" - $sudo_prefix mkdir -p "/var/log/patch_system" - $sudo_prefix mkdir -p "/etc/patch/keys" - - # 设置权限 - $sudo_prefix chmod 755 "$INSTALL_DIR" - $sudo_prefix chmod 755 "/var/backups/patch" - $sudo_prefix chmod 755 "/var/log/patch_system" - $sudo_prefix chmod 700 "/etc/patch/keys" -} - -install_scripts() { - info "安装脚本文件..." - - local sudo_prefix - sudo_prefix=$(get_cmd_prefix) - - # 复制脚本文件, 存在则覆盖 - $sudo_prefix cp --force "$SCRIPT_DIR/patch_generator.sh" "$INSTALL_DIR/" - $sudo_prefix cp --force "$SCRIPT_DIR/patch_fnc.sh" "$INSTALL_DIR/" - $sudo_prefix cp --force "$SCRIPT_DIR/patch_applier.sh" "$INSTALL_DIR/" - $sudo_prefix cp --force "$SCRIPT_DIR/patch_rollback.sh" "$INSTALL_DIR/" - $sudo_prefix cp --force "$SCRIPT_DIR/patch_verifier.sh" "$INSTALL_DIR/" - $sudo_prefix cp --force "$SCRIPT_DIR/patch_workflow.sh" "$INSTALL_DIR/" - $sudo_prefix cp --force "$SCRIPT_DIR/patch_config.sh" "$INSTALL_DIR/" - - info "脚本文件已安装在: $INSTALL_DIR/" - - # 设置执行权限 - $sudo_prefix chmod +x "$INSTALL_DIR"/*.sh - - # 创建符号链接 - $sudo_prefix ln -sf "$INSTALL_DIR/patch_workflow.sh" "/usr/local/bin/patch-mgmt" - info "符号链接已创建: /usr/local/bin/patch-mgmt" -} - -setup_cron() { - info "设置定时任务..." - - if is_docker_environment; then - info "Docker环境,跳过系统定时任务设置" - info "如需定时任务,请考虑使用宿主机的crontab或Docker运行参数" - return 0 - fi - - local sudo_prefix - sudo_prefix=$(get_cmd_prefix) - - local cron_job="0 2 * * * $INSTALL_DIR/patch_verifier.sh /opt/patches batch > /var/log/patch_system/batch_verify.log 2>&1" - - if ! crontab -l 2>/dev/null | grep -q "patch_verifier.sh"; then - (crontab -l 2>/dev/null; echo "$cron_job") | crontab - - info "定时任务已添加" - else - info "定时任务已存在" - fi -} - -generate_gpg_key() { - local name="${1:-John Doe}" - local email="${2:-johndoe@example.com}" - local key_type="${3:-RSA}" - local key_length="${4:-4096}" - - cat > /tmp/gpg_batch << EOF -Key-Type: $key_type -Key-Length: $key_length -Subkey-Type: $key_type -Subkey-Length: $key_length -Name-Real: $name -Name-Email: $email -Expire-Date: 0 -%commit -EOF - - gpg --batch --generate-key /tmp/gpg_batch - rm -f /tmp/gpg_batch - - echo "✅ 密钥生成完成" - gpg --list-secret-keys --keyid-format LONG "$email" -} - -generate_keys() { - info "生成签名密钥..." - - local key_dir="/etc/patch/keys" - local sudo_prefix - sudo_prefix=$(get_cmd_prefix) - - if [[ ! -f "$key_dir/private.pem" ]]; then - $sudo_prefix mkdir -p "$key_dir" - - # 生成GPG密钥对 - generate_gpg_key "$PATCH_AUTHOR" "$PATCH_EMAIL" "RSA" "4096" - - # 生成RSA密钥对 - openssl genrsa -out "$key_dir/private.pem" 4096 - openssl rsa -in "$key_dir/private.pem" -pubout -out "$key_dir/public.pem" - - $sudo_prefix chmod 600 "$key_dir/private.pem" - $sudo_prefix chmod 644 "$key_dir/public.pem" - - info "密钥对已生成: $key_dir/" - else - info "密钥对已存在" - fi -} - -main() { - info "开始安装企业级补丁管理系统" - echo "========================================" - echo "📋 安装配置文件: $INSTALL_DIR/patch_config.sh" - - # 加载配置 - load_config - - # 检查运行环境 - if is_docker_environment; then - info "检测到Docker容器环境,将以root权限执行(不适用sudo)" - SUDO_CMD="" - else - info "检测到主机环境,将使用sudo执行管理员操作" - SUDO_CMD="sudo" - - # 在非Docker环境中,建议非root用户运行 - if [[ $EUID -eq 0 ]]; then - warn "检测到以root用户运行,建议在非Docker环境中使用具备sudo权限的普通用户" - fi - fi - - # 安装依赖 - install_dependencies - - # 创建目录 - create_directories - - # 安装脚本 - install_scripts - - # 设置定时任务 - setup_cron - - # 生成密钥 - generate_keys - - info "🎉 补丁管理系统安装完成!" - echo "" - echo "📁 安装目录: $INSTALL_DIR" - echo "🔧 使用命令: patch-mgmt" - echo "📋 配置文件: $INSTALL_DIR/patch_config.sh" - echo "" - echo "💡 下一步操作:" - echo " 1. 编辑配置文件: $INSTALL_DIR/patch_config.sh" - echo " 2. 测试系统: patch-mgmt --help" - echo " 3. 配置通知: 修改SLACK_WEBHOOK等设置" -} - -main "$@" \ No newline at end of file diff --git a/scripts/patch_tools/patch_applier.sh b/scripts/patch_tools/patch_applier.sh deleted file mode 100644 index 7b3833932..000000000 --- a/scripts/patch_tools/patch_applier.sh +++ /dev/null @@ -1,1038 +0,0 @@ -#!/bin/bash -# patch_applier.sh - 企业级补丁包应用脚本 - -set -euo pipefail -shopt -s nullglob extglob - -# 脚本目录 -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CONFIG_FILE="${SCRIPT_DIR}/patch_config.sh" -LOG_FILE="/var/log/patch_system/apply_$(date +%Y%m%d_%H%M%S).log" -LOCK_FILE="/tmp/patch_apply.lock" -ROLLBACK_INFO_DIR="/var/lib/patch_system/rollback_info" - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -PURPLE='\033[0;35m' -CYAN='\033[0;36m' -NC='\033[0m' - - -# 日志函数 -log() { - local level="$1" - local message="$2" - local timestamp=$(date '+%Y-%m-%d %H:%M:%S') - - case "$level" in - "INFO") color="$GREEN" ;; - "WARN") color="$YELLOW" ;; - "ERROR") color="$RED" ;; - "DEBUG") color="$BLUE" ;; - *) color="$NC" ;; - esac - - echo -e "${color}[$timestamp] [$level]${NC} $message" | tee -a "$LOG_FILE" -} - -info() { log "INFO" "$1"; } -warn() { log "WARN" "$1"; } -error() { log "ERROR" "$1"; } -debug() { log "DEBUG" "$1"; } - -# 配置和初始化 -load_config() { - if [[ ! -f "$CONFIG_FILE" ]]; then - error "配置文件不存在: $CONFIG_FILE" - exit 1 - fi - - source "$CONFIG_FILE" - info "配置文件加载完成" - - # 设置默认值 - : ${LOG_LEVEL:="INFO"} - : ${BACKUP_DIR:="/var/backups/patch"} - : ${TEMP_DIR:="/tmp/patch_apply_$$"} - : ${ROLLBACK_ENABLED:="true"} - : ${ROLLBACK_INFO_DIR:="/var/lib/patch_system/rollback_info"} - : ${VALIDATION_ENABLED:="true"} - : ${NOTIFICATIONS_ENABLED:="true"} - : ${ATOMIC_OPERATIONS:="true"} - : ${MAX_BACKUP_COUNT:=10} - : ${PATCH_STAGING_DIR:="/tmp/patch_staging"} -} - -setup_environment() { - mkdir -p "$(dirname "$LOG_FILE")" - mkdir -p "$BACKUP_DIR" - mkdir -p "$TEMP_DIR" - mkdir -p "$ROLLBACK_INFO_DIR" - mkdir -p "$PATCH_STAGING_DIR" - - info "环境设置完成" - info "日志文件: $LOG_FILE" - info "备份目录: $BACKUP_DIR" - info "临时目录: $TEMP_DIR" -} - -# 锁管理 -acquire_lock() { - local max_retries=5 - local retry_count=0 - - while [[ $retry_count -lt $max_retries ]]; do - if (set -C; echo $$ > "$LOCK_FILE") 2>/dev/null; then - info "🔒 获取应用锁" - return 0 - fi - - local lock_pid=$(cat "$LOCK_FILE" 2>/dev/null) - if kill -0 "$lock_pid" 2>/dev/null; then - warn "补丁应用正在进行中 (PID: $lock_pid),等待..." - sleep 5 - ((retry_count++)) - else - warn "清理过期的锁文件" - rm -f "$LOCK_FILE" - fi - done - - error "无法获取锁,可能已有补丁应用在进行中" - exit 1 -} - -release_lock() { - rm -f "$LOCK_FILE" - info "🔓 释放应用锁" -} - -cleanup() { - local exit_code=$? - - if [[ -d "$TEMP_DIR" ]]; then - rm -rf "$TEMP_DIR" - info "临时目录已清理: $TEMP_DIR" - fi - - release_lock - - if [[ $exit_code -eq 0 ]]; then - info "✅ 补丁应用流程完成" - else - error "💥 补丁应用流程失败 (退出码: $exit_code)" - fi - - exit $exit_code -} - -trap cleanup EXIT INT TERM - -# 依赖检查 -check_dependencies() { - local deps=("tar" "gzip" "find" "stat" "sha256sum" "date" "mkdir" "cp" "jq") - local missing=() - - for dep in "${deps[@]}"; do - if ! command -v "$dep" >/dev/null 2>&1; then - missing+=("$dep") - fi - done - - if [[ ${#missing[@]} -gt 0 ]]; then - error "缺少依赖工具: ${missing[*]}" - return 1 - fi - - info "依赖检查通过" - return 0 -} - -# 补丁包验证 -verify_patch_package() { - local patch_file="$1" - - info "🔍 验证补丁包: $patch_file" - - # 检查文件存在性 - if [[ ! -f "$patch_file" ]]; then - error "补丁包不存在: $patch_file" - return 1 - fi - - # 检查文件大小 - local size=$(stat -c%s "$patch_file" 2>/dev/null || echo "0") - if [[ $size -eq 0 ]]; then - error "补丁包为空: $patch_file" - return 1 - fi - - # 验证压缩包完整性 - if ! tar -tzf "$patch_file" >/dev/null 2>&1; then - error "补丁包损坏或格式错误: $patch_file" - return 1 - fi - - # 验证校验和(如果存在) - local checksum_file="${patch_file}.sha256" - if [[ -f "$checksum_file" ]]; then - if sha256sum -c "$checksum_file" >/dev/null 2>&1; then - info "✅ 校验和验证通过" - else - error "❌ 校验和验证失败" - return 1 - fi - fi - - # 验证签名(如果存在) - local sig_file="${patch_file}.sig" - if [[ -f "$sig_file" ]] && command -v gpg >/dev/null 2>&1; then - if gpg --verify "$sig_file" "$patch_file" >/dev/null 2>&1; then - info "✅ 签名验证通过" - else - error "❌ 签名验证失败" - return 1 - fi - fi - - info "✅ 补丁包验证通过" - return 0 -} - -# 提取补丁包 -extract_patch() { - local patch_file="$1" - local extract_dir="$2" - - info "📦 提取补丁包到: $extract_dir" - - if [[ ! -d "$extract_dir" ]]; then - mkdir -p "$extract_dir" - fi - - if tar -xzf "$patch_file" -C "$extract_dir"; then - info "✅ 补丁包提取完成" - - # 验证提取内容 - if [[ -f "$extract_dir/MANIFEST.MF" ]]; then - info "✅ 清单文件验证通过" - return 0 - else - error "❌ 补丁包缺少清单文件" - return 1 - fi - else - error "❌ 补丁包提取失败" - return 1 - fi -} - -# 解析清单文件 -parse_manifest() { - local manifest_file="$1" - - info "📋 解析清单文件: $manifest_file" >&2 - - if [[ ! -f "$manifest_file" ]]; then - error "清单文件不存在: $manifest_file" - return 1 - fi - - # 读取基础信息 - local patch_name=$(grep -i "名称:" "$manifest_file" | cut -d':' -f2- | sed 's/^[[:space:]]*//') - local patch_version=$(grep -i "版本:" "$manifest_file" | cut -d':' -f2- | sed 's/^[[:space:]]*//') - local change_count=$(grep -c -E "^(ADDED|MODIFIED|DELETED)" "$manifest_file" 2>/dev/null || echo "0") - - echo "补丁信息:" - echo " 名称: ${patch_name:-未知}" - echo " 版本: ${patch_version:-未知}" - echo " 变更数: $change_count" - echo "" - - # 提取变更列表 - local changes=() - while IFS='|' read -r change_type path extra_info; do - changes+=("$change_type|$path|$extra_info") - done < <(grep -E "^(ADDED|MODIFIED|DELETED)" "$manifest_file") - - if [[ ${#changes[@]} -eq 0 ]]; then - warn "⚠️ 清单中没有找到变更记录" >&2 - fi - - # 返回变更数组 - printf "%s\n" "${changes[@]}" - return 0 -} - -# 创建备份 -create_backup() { - local changes_file="$1" - local backup_dir="$2" - - info "💾 创建应用前备份..." >&2 - - if [[ ! -s "$changes_file" ]]; then - warn "无变更需要备份" >&2 - return 0 - fi - - local files_to_backup=() - - # 分析需要备份的文件 - while IFS='|' read -r change_type path extra_info; do - case "$change_type" in - "MODIFIED"|"DELETED") - local target_file="$path" - if [[ -f "$target_file" ]]; then - files_to_backup+=("$target_file") - debug "需要备份: $target_file" >&2 - fi - ;; - esac - done < "$changes_file" - - if [[ ${#files_to_backup[@]} -eq 0 ]]; then - info "⚠️ 无文件需要备份" >&2 - return 0 - fi - - # 创建备份 - local backup_tar="$backup_dir/backup_$(date +%Y%m%d_%H%M%S).tar.gz" - mkdir -p "$(dirname "$backup_tar")" - - local work_dir="$(pwd)" # 默认是当前工作目录 - - if tar -czf "$backup_tar" -C "${work_dir}" "${files_to_backup[@]/#\//}"; then - local size=$(du -h "$backup_tar" | cut -f1) - info "✅ 备份创建完成: $backup_tar ($size)" >&2 - echo "$backup_tar" - return 0 - else - error "❌ 备份创建失败" >&2 - return 1 - fi -} - -# 应用文件变更 -apply_file_changes() { - local extract_dir="$1" - local changes_file="$2" - local dry_run="${3:-false}" - - info "🔄 开始应用文件变更 (干跑模式: $dry_run)" >&2 - - local applied_count=0 - local failed_count=0 - local skip_count=0 - - # 读取变更列表 - while IFS='|' read -r change_type path extra_info; do - case "$change_type" in - "ADDED") - apply_file_addition "$extract_dir" "$path" "$dry_run" - result=$? - ;; - "MODIFIED") - apply_file_modification "$extract_dir" "$path" "$dry_run" - result=$? - ;; - "DELETED") - apply_file_deletion "$path" "$dry_run" - result=$? - ;; - *) - warn "⚠️ 未知变更类型: $change_type" >&2 - result=99 - ;; - esac - - case $result in - 0) ((applied_count++)) ;; - 1) ((failed_count++)) ;; - 2) ((skip_count++)) ;; - esac - done < "$changes_file" - - info "📊 变更应用统计:" >&2 - info " 成功: $applied_count" >&2 - info " 失败: $failed_count" >&2 - info " 跳过: $skip_count" >&2 - info " 总计: $((applied_count + failed_count + skip_count))" >&2 - - if [[ $failed_count -gt 0 ]]; then - error "❌ 存在应用失败的变更" >&2 - return 1 - fi - - if [[ "$dry_run" == "true" ]]; then - info "✅ 干跑模式完成 - 无实际变更" >&2 - return 0 - else - info "✅ 文件变更应用完成" >&2 - return 0 - fi -} - -# 应用文件新增 -apply_file_addition() { - local extract_dir="$1" - local file_path="$2" - local dry_run="$3" - - local source_file="$extract_dir/files/$file_path" - local target_file="$file_path" - local target_dir=$(dirname "$target_file") - - # 检查源文件是否存在 - if [[ ! -f "$source_file" ]]; then - error "源文件不存在: $source_file" >&2 - return 1 - fi - - if [[ "$dry_run" == "true" ]]; then - info "📋 [干跑] 新增文件: $file_path" >&2 - return 0 - fi - - # 创建目标目录 - if [[ ! -d "$target_dir" ]]; then - if ! mkdir -p "$target_dir"; then - error "创建目录失败: $target_dir" >&2 - return 1 - fi - debug "创建目录: $target_dir" >&2 - fi - - # 检查目标文件是否已存在 - if [[ -f "$target_file" ]]; then - warn "目标文件已存在,将被覆盖: $target_file" >&2 - fi - - # 复制文件 - if cp "$source_file" "$target_file"; then - # 设置权限 - set_file_permissions "$target_file" "644" - info "✅ 新增文件: $file_path" >&2 - return 0 - else - error "❌ 文件新增失败: $file_path" >&2 - return 1 - fi -} - -# 应用文件修改 -apply_file_modification() { - local extract_dir="$1" - local file_path="$2" - local dry_run="$3" - - local source_file="$extract_dir/files/$file_path" - local target_file="$file_path" - - info "🔄 开始应用文件修改: $file_path" >&2 - info "当前工作目录: $(pwd)" >&2 - info "源文件: $source_file" >&2 - info "目标文件: $target_file" >&2 - - # 检查源文件 - if [[ ! -f "$source_file" ]]; then - error "源文件不存在: $source_file" >&2 - return 1 - fi - - # 检查目标文件 - if [[ ! -f "$target_file" ]]; then - warn "目标文件不存在,将作为新增处理: $target_file" >&2 - return apply_file_addition "$extract_dir" "$file_path" "$dry_run" - fi - - if [[ "$dry_run" == "true" ]]; then - info "📋 [干跑] 修改文件: $file_path" >&2 - return 0 - fi - - # 备份原文件 - local backup_file="$TEMP_DIR/backup/$file_path" - local backup_dir=$(dirname "$backup_file") - mkdir -p "$backup_dir" - - if ! cp -p "$target_file" "$backup_file"; then - error "备份原文件失败: $target_file" >&2 - return 1 - fi - - # 应用修改 - if cp "$source_file" "$target_file"; then - # 保持原权限 - local original_perm=$(stat -c%a "$backup_file" 2>/dev/null || echo "644") - set_file_permissions "$target_file" "$original_perm" - info "✅ 修改文件: $file_path" >&2 - return 0 - else - error "❌ 文件修改失败: $file_path" >&2 - # 恢复备份 - cp "$backup_file" "$target_file" - return 1 - fi -} - -# 应用文件删除 -apply_file_deletion() { - local file_path="$1" - local dry_run="$2" - - local target_file="$file_path" - - if [[ ! -f "$target_file" ]]; then - warn "文件不存在,无需删除: $target_file" >&2 - return 2 # 跳过 - fi - - if [[ "$dry_run" == "true" ]]; then - info "📋 [干跑] 删除文件: $file_path" >&2 - return 0 - fi - - # 备份文件 - local backup_file="$TEMP_DIR/backup/$file_path" - local backup_dir=$(dirname "$backup_file") - mkdir -p "$backup_dir" - - if ! cp -p "$target_file" "$backup_file"; then - error "备份文件失败: $target_file" >&2 - return 1 - fi - - # 删除文件 - if rm -f "$target_file"; then - info "✅ 删除文件: $file_path" >&2 - - # 尝试清理空目录 - clean_empty_directories "$(dirname "$target_file")" - return 0 - else - error "❌ 文件删除失败: $file_path" >&2 - return 1 - fi -} - -# 清理空目录 -clean_empty_directories() { - local dir="$1" - - while [[ "$dir" != "/" ]] && [[ -d "$dir" ]]; do - if ! rmdir "$dir" 2>/dev/null; then - break # 目录非空,停止清理 - fi - debug "清理空目录: $dir" >&2 - dir=$(dirname "$dir") - done -} - -# 设置文件权限 -set_file_permissions() { - local file="$1" - local permissions="${2:-644}" - - if chmod "$permissions" "$file" 2>/dev/null; then - debug "设置权限: $file → $permissions" - else - warn "权限设置失败: $file" - fi -} - -# 验证应用结果 -verify_application() { - local extract_dir="$1" - local changes_file="$2" - - if [[ "$VALIDATION_ENABLED" != "true" ]]; then - info "验证功能已禁用" >&2 - return 0 - fi - - info "🔍 验证应用结果..." >&2 - - local manifest_file="$extract_dir/MANIFEST.MF" - local verification_passed=true - local verified_count=0 - local failed_count=0 - - # 读取变更列表进行验证 - while IFS='|' read -r change_type path extra_info; do - case "$change_type" in - "ADDED"|"MODIFIED") - if verify_file_application "$extract_dir" "$path" "$change_type"; then - ((verified_count++)) - else - ((failed_count++)) - verification_passed=false - fi - ;; - "DELETED") - if verify_file_deletion "$path"; then - ((verified_count++)) - else - ((failed_count++)) - verification_passed=false - fi - ;; - esac - done < "$changes_file" - - info "📊 验证统计:" >&2 - info " 成功: $verified_count" >&2 - info " 失败: $failed_count" >&2 - - if $verification_passed; then - info "✅ 应用验证通过" >&2 - return 0 - else - error "❌ 应用验证失败" >&2 - return 1 - fi -} - -# 验证文件应用 -verify_file_application() { - local extract_dir="$1" - local file_path="$2" - local change_type="$3" - - local source_file="$extract_dir/files/$file_path" - local target_file="$file_path" - - # 检查目标文件是否存在 - if [[ ! -f "$target_file" ]]; then - error "❌ 目标文件不存在: $target_file" - return 1 - fi - - # 比较文件内容 - local source_hash=$(sha256sum "$source_file" | cut -d' ' -f1) - local target_hash=$(sha256sum "$target_file" | cut -d' ' -f1) - - if [[ "$source_hash" != "$target_hash" ]]; then - error "❌ 文件内容不匹配: $file_path" - error " 源哈希: $source_hash" - error " 目标哈希: $target_hash" - return 1 - fi - - debug "✅ 文件验证通过: $file_path" - return 0 -} - -# 验证文件删除 -verify_file_deletion() { - local file_path="$1" - local target_file="$file_path" - - if [[ -f "$target_file" ]]; then - error "❌ 文件未成功删除: $target_file" - return 1 - fi - - debug "✅ 文件删除验证通过: $file_path" - return 0 -} - -# 创建回滚点 -create_rollback_point() { - local patch_file="$1" - local backup_file="$2" - local extract_dir="$3" - local changes_file="$4" - - if [[ "$ROLLBACK_ENABLED" != "true" ]]; then - info "回滚功能已禁用" - return 0 - fi - - info "🔄 创建回滚点..." - - local patch_name=$(basename "$patch_file" .tar.gz) - local timestamp=$(date +%Y%m%d_%H%M%S) - local rollback_info_file="$ROLLBACK_INFO_DIR/${patch_name}_${timestamp}.json" - - # 读取清单信息 - local manifest_file="$extract_dir/MANIFEST.MF" - local patch_info="" - if [[ -f "$manifest_file" ]]; then - patch_info=$(grep -E "^(名称|版本|描述):" "$manifest_file" | head -3 | tr '\n' ';') - fi - - # 统计变更类型 - local added_count=$(grep -c "^ADDED" "$changes_file") - local modified_count=$(grep -c "^MODIFIED" "$changes_file") - local deleted_count=$(grep -c "^DELETED" "$changes_file") - - # 创建回滚信息JSON - local rollback_info=$(cat << EOF -{ - "patch_name": "$patch_name", - "apply_time": "$(date -Iseconds)", - "backup_path": "$backup_file", - "rollback_dir": "$BACKUP_DIR/rollback_$timestamp", - "manifest_info": "$patch_info", - "status": "applied", - "changes": { - "added": $added_count, - "modified": $modified_count, - "deleted": $deleted_count, - "total": $((added_count + modified_count + deleted_count)) - }, - "system_info": { - "hostname": "$(hostname)", - "user": "$(whoami)", - "working_directory": "$PWD" - } -} -EOF -) - - echo "$rollback_info" > "$rollback_info_file" - info "✅ 回滚点创建完成: $rollback_info_file" - - # 清理旧回滚点 - cleanup_old_rollback_points -} - -# 清理旧回滚点 -cleanup_old_rollback_points() { - local max_points="${1:-$MAX_BACKUP_COUNT}" - - info "🧹 清理旧回滚点 (保留最近 $max_points 个)" - - if [[ ! -d "$ROLLBACK_INFO_DIR" ]]; then - return 0 - fi - - # 获取所有回滚点并按时间排序 - local points=($(find "$ROLLBACK_INFO_DIR" -name "*.json" -type f -printf "%T@|%p\n" | sort -rn | cut -d'|' -f2)) - local total_points=${#points[@]} - local points_to_remove=$((total_points - max_points)) - - if [[ $points_to_remove -le 0 ]]; then - debug "无需清理,当前 $total_points 个回滚点" - return 0 - fi - - info "清理 $points_to_remove 个旧回滚点" - - for ((i=max_points; i/dev/null 2>&1; then - if curl -s -X POST -H 'Content-type: application/json' \ - --data "$payload" "$SLACK_WEBHOOK" >/dev/null; then - info "✅ Slack通知发送成功" - else - warn "⚠️ Slack通知发送失败" - fi - fi -} - -# 主应用函数 -apply_patch() { - local patch_file="$1" - local dry_run="${2:-false}" - local target_dir="${3:-./}" - - info "🚀 开始应用补丁包: $patch_file" - info "目标目录: $target_dir" - info "干跑模式: $dry_run" - - # 发送开始通知 - send_notification "start" "$patch_file" "开始应用补丁" - - # 验证补丁包 - if ! verify_patch_package "$patch_file"; then - send_notification "failure" "$patch_file" "补丁包验证失败" - return 1 - fi - - # 创建临时目录 - local extract_dir="$TEMP_DIR/extract" - local changes_file="$TEMP_DIR/changes.txt" - - # 提取补丁包 - info ">>提取补丁包..." - if ! extract_patch "$patch_file" "$extract_dir"; then - send_notification "failure" "$patch_file" "补丁包解压失败" - return 1 - fi - - # 解析清单文件 - info ">>解析清单文件..." - if ! parse_manifest "$extract_dir/MANIFEST.MF" > "$changes_file"; then - send_notification "failure" "$patch_file" "清单文件解析失败" - return 1 - fi - - # 检查是否有变更 - info ">>检查变更记录..." - if [[ ! -s "$changes_file" ]]; then - warn "⚠️ 补丁包中没有变更记录" - send_notification "success" "$patch_file" "补丁包无变更记录" - return 0 - fi - - # 创建备份文件(非干跑模式) - local backup_file="" - if [[ "$dry_run" != "true" ]]; then - info ">>创建备份..." - backup_file=$(create_backup "$changes_file" "$BACKUP_DIR") - if [[ $? -ne 0 ]]; then - send_notification "failure" "$patch_file" "备份创建失败" - return 1 - fi - fi - - # 应用变更 - info ">>应用变更..." - if ! apply_file_changes "$extract_dir" "$changes_file" "$dry_run"; then - send_notification "failure" "$patch_file" "文件应用失败" - return 1 - fi - - # 验证应用结果(非干跑模式) - info ">>验证应用结果..." - if [[ "$dry_run" != "true" ]]; then - if ! verify_application "$extract_dir" "$changes_file"; then - send_notification "failure" "$patch_file" "应用验证失败" - return 1 - fi - - # 创建回滚点 - info ">>创建回滚点..." - create_rollback_point "$patch_file" "$backup_file" "$extract_dir" "$changes_file" - fi - - send_notification "success" "$patch_file" "补丁应用成功" - info "✅ 补丁应用流程完成" - return 0 -} - -# 显示使用帮助 -usage() { - cat << EOF -用法: $0 [选项] <补丁包路径> - -选项: - -d, --dry-run 干跑模式(只验证不应用) - -t, --target DIR 目标目录(默认: /) - -c, --config FILE 配置文件路径 - -v, --verbose 详细输出 - -q, --quiet 安静模式 - -h, --help 显示此帮助 - -V, --version 显示版本 - -示例: - $0 patch.tar.gz # 应用补丁 - $0 --dry-run patch.tar.gz # 干跑模式 - $0 --target /app patch.tar.gz # 应用到指定目录 - $0 --config custom_config.sh patch.tar.gz - -环境变量: - PATCH_CONFIG 配置文件路径 - BACKUP_DIR 备份目录 - LOG_LEVEL 日志级别 - -报告问题: 请检查日志文件 $LOG_FILE -EOF -} - -# 显示版本信息 -show_version() { - echo "patch_applier.sh v2.0.0" - echo "企业级补丁应用系统" - echo "支持原子性操作、完整验证、智能回滚点创建" -} - -# 主函数 -main() { - local patch_file="" - local dry_run="false" - local target_dir="/" - local verbose_mode="false" - - # 初始化 - load_config - setup_environment - check_dependencies - acquire_lock - - # 解析命令行参数 - while [[ $# -gt 0 ]]; do - case $1 in - -d|--dry-run) - dry_run="true" - shift - ;; - -t|--target) - target_dir="$2" - shift 2 - ;; - -c|--config) - CONFIG_FILE="$2" - shift 2 - ;; - -v|--verbose) - verbose_mode="true" - shift - ;; - -q|--quiet) - exec >/dev/null 2>&1 - shift - ;; - -h|--help) - usage - exit 0 - ;; - -V|--version) - show_version - exit 0 - ;; - -*) - error "未知选项: $1" - usage - exit 1 - ;; - *) - patch_file="$1" - shift - ;; - esac - done - - # 检查参数 - if [[ -z "$patch_file" ]]; then - error "必须指定补丁包路径" - usage - exit 1 - fi - - if [[ ! -f "$patch_file" ]]; then - error "补丁包不存在: $patch_file" - exit 1 - fi - - - # 执行补丁应用 - if apply_patch "$patch_file" "$dry_run" "$target_dir"; then - if [[ "$dry_run" == "true" ]]; then - info "🎉 干跑模式完成 - 可安全应用补丁" - else - info "🎉 补丁应用成功完成!" - fi - exit 0 - else - error "💥 补丁应用失败" - exit 1 - fi -} - -# 异常处理 -trap 'error "脚本执行中断"; exit 1' INT TERM - -# 执行主函数 -main "$@" diff --git a/scripts/patch_tools/patch_config.sh b/scripts/patch_tools/patch_config.sh deleted file mode 100644 index 549f39a36..000000000 --- a/scripts/patch_tools/patch_config.sh +++ /dev/null @@ -1,196 +0,0 @@ -#!/bin/bash -# patch_config.sh - 企业级补丁配置 - -# ============================================================================== -# 基础配置 - 定义补丁的基本信息 -# ============================================================================== - -# 基础配置 -PATCH_NAME="upgrade-hotfix" -PATCH_VERSION="1.0.0" -PATCH_DESCRIPTION="紧急升级修复" -PATCH_AUTHOR="devops" -PATCH_EMAIL="devops@aigc-quickapp.com" - -# ============================================================================== -# 文件筛选配置 - 定义哪些文件需要被包含或排除 -# ============================================================================== - -## 包含的文件模式 -INCLUDE_PATTERNS=( - ".+/.*" # 匹配所有子目录下的所有文件 - ".env" - ".htaccess" - '.user.ini' - '404.html' - "composer.json" - "index.html" - "index.php" - "install.lock" - "install.php" - "nginx.htaccess" - "think" - "LICENSE" - "README.md" - "robots.txt" -) - -## 排除的文件模式 -EXCLUDE_PATTERNS=( - "*.log" - "*.tmp" - "*.bak" - "*.swp" - "*.swx" - "^.github/" - # 排除任何位置的node_modules文件夹及其所有子目录和文件 - ".*/node_modules($|/.*)" - # 排除任何位置的__pycache__文件夹及其所有子目录和文件 - ".*/__pycache__($|/.*)" - # 排除根目录下的一级文件夹及其所有子目录和文件 - "^.git/" - "^cache($|/.*)" - "^temp($|/.*)" - "^tmp($|/.*)" - "^logs($|/.*)" - "^runtime($|/.*)" - "^uploads($|/.*)" - "^attachment($|/.*)" - "^h5($|/.*)" - "^hwapp($|/.*)" - ".DS_Store" - "Thumbs.db" -) - -# ============================================================================== -# 文件大小限制 - 定义补丁处理的文件大小范围 -# ============================================================================== - -# 文件大小限制 -MAX_FILE_SIZE="20MB" # 最大文件大小 -MIN_FILE_SIZE="0KB" # 最小文件大小 - -# ============================================================================== -# 时间过滤 - 定义补丁处理的文件时间范围 -# ============================================================================== - -# 时间过滤 -MODIFIED_AFTER="2000-01-01T00:00:00Z" # 仅包含修改时间在该时间之后的文件 -CREATED_AFTER="2000-01-01T00:00:00Z" # 仅包含创建时间在该时间之后的文件 - -# ============================================================================== -# 比较方法配置 - 定义补丁处理的比较方式 -# ============================================================================== - -# 比较方法配置 -# 比较内容和时间 -COMPARISON_METHOD="content" # 比较方法: content, time, both -HASH_ALGORITHM="sha256" # 哈希算法,用于文件内容比较 -TIME_PRECISION="second" # 时间精度,用于文件时间比较 -COMPARE_PERMISSIONS=false # 是否比较文件权限 -COMPARE_OWNERSHIP=false # 是否比较文件所有者 -IGNORE_LINE_ENDINGS=true # 是否忽略换行符格式不同时(比如Windows的 \r\n vs Linux的 \n) - -# ============================================================================== -# 增量配置 - 定义是否启用增量补丁 -# ============================================================================== - -# 增量配置 -INCREMENTAL_ENABLED=true # 是否启用增量补丁 -BASE_VERSION="1.0.0" # 基础版本,用于增量比较 -DETECT_RENAMES=true # 是否检测文件重命名 -BINARY_DIFF=true # 是否启用二进制差异比较 -CHUNK_SIZE="8KB" # 二进制差异比较的块大小 - - - -# ============================================================================== -# 压缩配置 - 定义补丁压缩方式和级别 -# ============================================================================== - -# 压缩配置 -COMPRESSION_ENABLED=true # 是否启用压缩 -COMPRESSION_ALGORITHM="gzip" # gzip, bzip2, xz -COMPRESSION_LEVEL=6 # 压缩级别,1-9 -PER_FILE_OPTIMIZATION=true # 是否对每个文件单独压缩 - -# ============================================================================== -# 安全配置 - 定义补丁签名和加密方式 -# ============================================================================== - -# 安全配置 -SIGNING_ENABLED=false # 是否启用签名 -SIGNING_ALGORITHM="rsa" # 签名算法,rsa, ecdsa -PRIVATE_KEY="/etc/patch/keys/private.pem" # 私钥文件路径 -PUBLIC_KEY="/etc/patch/keys/public.pem" # 公钥文件路径 - -ENCRYPTION_ENABLED=false # 是否启用加密 -ENCRYPTION_ALGORITHM="aes-256-gcm" # 加密算法,aes-256-gcm, aes-256-cbc -ENCRYPTION_KEY="/etc/patch/keys/encryption.key" # 加密密钥文件路径 -ENCRYPTION_IV="/etc/patch/keys/encryption.iv" # 加密初始化向量文件路径 - -# ============================================================================== -# 备份配置 - 定义是否启用备份和备份策略 -# ============================================================================== - -# 备份配置 -BACKUP_ENABLED=true # 是否启用备份 -BACKUP_STRATEGY="full" # 备份策略,full, incremental, differential -BACKUP_RETENTION_DAYS=30 # 备份保留天数 -BACKUP_DIR="/var/backups/patch" # 备份目录 - -# ============================================================================== -# 回滚配置 - 定义是否启用自动回滚和回滚策略 -# ============================================================================== - -# 回滚配置 -ROLLBACK_ENABLED=true # 是否启用自动回滚 -ROLLBACK_AUTO_GENERATE=true # 是否自动生成回滚脚本 -ROLLBACK_STRATEGY="snapshot" # 回滚策略,snapshot, restore -ROLLBACK_INFO_DIR="/var/lib/patch_system/rollback_info" - -# ============================================================================== -# 通知配置 - 定义是否启用通知和通知渠道 -# ============================================================================== - -# 通知配置 -NOTIFICATIONS_ENABLED=false # 是否启用通知 -SLACK_WEBHOOK="https://hooks.slack.com/services/..." # Slack Webhook URL -EMAIL_RECIPIENTS="devops@company.com,team@company.com" # 邮箱接收人列表 - -# ============================================================================== -# 性能配置 - 定义并行处理和资源限制 -# ============================================================================== - -# 性能配置 -PARALLEL_PROCESSING=false # 是否启用并行处理 -MAX_WORKERS=4 # 最大工作线程数 -MEMORY_LIMIT="2GB" # 内存限制 -IO_BUFFER_SIZE="64KB" # IO缓冲区大小 - -# ============================================================================== -# 输出配置 - 定义补丁输出格式和目录 -# ============================================================================== - -# 输出配置 -OUTPUT_FORMAT="tar.gz" # 输出格式,tar.gz, zip -OUTPUT_DIRECTORY="/opt/patches" # 输出目录 -NAMING_PATTERN="patch-{name}-{version}-{timestamp}-{git_commit}.{format}" # 文件名命名模式,包含占位符,举例:patch-app-1.0.0-20250101T000000Z-abc123.tar.gz - -# ============================================================================== -# 日志配置 - 定义日志级别、文件路径和大小 -# ============================================================================== - -# 日志配置 -LOG_LEVEL="INFO" # 日志级别,DEBUG, INFO, WARN, ERROR, TRACE; DEBUG 会开启终端调试输出,TRACE 只会开启详细日志输出 -LOG_FILE="/var/log/patch_system/patch.log" # 日志文件路径 -LOG_MAX_SIZE="10MB" # 日志文件最大大小 -LOG_BACKUP_COUNT=10 # 日志文件备份数量 - -# ============================================================================== -# 长路径支持 - 定义是否启用长路径支持 -# ============================================================================== - -# 长路径支持 -LONG_PATH_SUPPORT=true # 是否启用长路径支持 -TAR_OPTIONS="--force-local" # tar命令选项,--force-local 强制使用本地文件系统 diff --git a/scripts/patch_tools/patch_config.sh.example b/scripts/patch_tools/patch_config.sh.example deleted file mode 100644 index 6181fd08d..000000000 --- a/scripts/patch_tools/patch_config.sh.example +++ /dev/null @@ -1,202 +0,0 @@ -#!/bin/bash -# patch_config.sh - 企业级补丁配置 - -# ============================================================================== -# 基础配置 - 定义补丁的基本信息 -# ============================================================================== - -# 基础配置 -PATCH_NAME="security-hotfix-2025" -PATCH_VERSION="1.0.0" -PATCH_DESCRIPTION="紧急安全漏洞修复" -PATCH_AUTHOR="企业DevOps团队" -PATCH_EMAIL="devops@aigc-quickapp.com" - -# ============================================================================== -# 文件筛选配置 - 定义哪些文件需要被包含或排除 -# ============================================================================== - -## 包含的文件模式 -INCLUDE_PATTERNS=( - ".+/.*" # 匹配所有子目录下的文件 - "^.well-known/" - "^addon/" - "^addons/" - "^app/" - "^config/" - "^extend/" - "^public/" - "^templates/" - "^vendor/" - "^web/" - "^webapi/" - "^.404.html" - "^index.php" - "^install.php" - "^install.lock" - "^.env" - "^.env.test" - "^.env.production" - "^.env.staging" - "^.env.development" - "^.env.local" - "^.htaccess" - "^.user.ini" - "^composer.json" - "^composer.lock" -) - -## 排除的文件模式 -EXCLUDE_PATTERNS=( - "*.log" - "*.tmp" - "*.bak" - "*.swp" - "*.swx" - "^.github/" - "/node_modules/*" - "/__pycache__/*" - # 排除以下一级文件夹 - "^.git/" - "^cache/" - "^temp/" - "^tmp/" - "^logs/" - "^runtime/" - "^uploads/" - "^attachment/" - "^h5/" - "^hwapp/" - ".DS_Store" - "Thumbs.db" -) - -# ============================================================================== -# 文件大小限制 - 定义补丁处理的文件大小范围 -# ============================================================================== - -# 文件大小限制 -MAX_FILE_SIZE="20MB" # 最大文件大小 -MIN_FILE_SIZE="0KB" # 最小文件大小 - -# ============================================================================== -# 时间过滤 - 定义补丁处理的文件时间范围 -# ============================================================================== - -# 时间过滤 -MODIFIED_AFTER="2000-01-01T00:00:00Z" # 仅包含修改时间在该时间之后的文件 -CREATED_AFTER="2000-01-01T00:00:00Z" # 仅包含创建时间在该时间之后的文件 - -# ============================================================================== -# 比较方法配置 - 定义补丁处理的比较方式 -# ============================================================================== - -# 比较方法配置 -# 比较内容和时间 -COMPARISON_METHOD="content" # 比较方法: content, time, both -HASH_ALGORITHM="sha256" # 哈希算法,用于文件内容比较 -TIME_PRECISION="second" # 时间精度,用于文件时间比较 -COMPARE_PERMISSIONS=false # 是否比较文件权限 -COMPARE_OWNERSHIP=false # 是否比较文件所有者 - -# ============================================================================== -# 增量配置 - 定义是否启用增量补丁 -# ============================================================================== - -# 增量配置 -INCREMENTAL_ENABLED=true # 是否启用增量补丁 -BASE_VERSION="1.0.0" # 基础版本,用于增量比较 -DETECT_RENAMES=true # 是否检测文件重命名 -BINARY_DIFF=true # 是否启用二进制差异比较 -CHUNK_SIZE="8KB" # 二进制差异比较的块大小 - - - -# ============================================================================== -# 压缩配置 - 定义补丁压缩方式和级别 -# ============================================================================== - -# 压缩配置 -COMPRESSION_ENABLED=true # 是否启用压缩 -COMPRESSION_ALGORITHM="gzip" # gzip, bzip2, xz -COMPRESSION_LEVEL=6 # 压缩级别,1-9 -PER_FILE_OPTIMIZATION=true # 是否对每个文件单独压缩 - -# ============================================================================== -# 安全配置 - 定义补丁签名和加密方式 -# ============================================================================== - -# 安全配置 -SIGNING_ENABLED=true # 是否启用签名 -SIGNING_ALGORITHM="rsa" # 签名算法,rsa, ecdsa -PRIVATE_KEY="/etc/patch/keys/private.pem" # 私钥文件路径 -PUBLIC_KEY="/etc/patch/keys/public.pem" # 公钥文件路径 - -ENCRYPTION_ENABLED=false # 是否启用加密 -ENCRYPTION_ALGORITHM="aes-256-gcm" # 加密算法,aes-256-gcm, aes-256-cbc -ENCRYPTION_KEY="/etc/patch/keys/encryption.key" # 加密密钥文件路径 -ENCRYPTION_IV="/etc/patch/keys/encryption.iv" # 加密初始化向量文件路径 - -# ============================================================================== -# 备份配置 - 定义是否启用备份和备份策略 -# ============================================================================== - -# 备份配置 -BACKUP_ENABLED=true # 是否启用备份 -BACKUP_STRATEGY="full" # 备份策略,full, incremental, differential -BACKUP_RETENTION_DAYS=30 # 备份保留天数 - -# ============================================================================== -# 回滚配置 - 定义是否启用自动回滚和回滚策略 -# ============================================================================== - -# 回滚配置 -ROLLBACK_ENABLED=true # 是否启用自动回滚 -ROLLBACK_AUTO_GENERATE=true # 是否自动生成回滚脚本 -ROLLBACK_STRATEGY="snapshot" # 回滚策略,snapshot, restore - -# ============================================================================== -# 通知配置 - 定义是否启用通知和通知渠道 -# ============================================================================== - -# 通知配置 -NOTIFICATIONS_ENABLED=false # 是否启用通知 -SLACK_WEBHOOK="https://hooks.slack.com/services/..." # Slack Webhook URL -EMAIL_RECIPIENTS="devops@company.com,team@company.com" # 邮箱接收人列表 - -# ============================================================================== -# 性能配置 - 定义并行处理和资源限制 -# ============================================================================== - -# 性能配置 -PARALLEL_PROCESSING=false # 是否启用并行处理 -MAX_WORKERS=4 # 最大工作线程数 -MEMORY_LIMIT="2GB" # 内存限制 -IO_BUFFER_SIZE="64KB" # IO缓冲区大小 - -# ============================================================================== -# 输出配置 - 定义补丁输出格式和目录 -# ============================================================================== - -# 输出配置 -OUTPUT_FORMAT="tar.gz" # 输出格式,tar.gz, zip -OUTPUT_DIRECTORY="/opt/patches" # 输出目录 -NAMING_PATTERN="patch-{name}-{version}-{timestamp}-{git_commit}.{format}" # 文件名命名模式,包含占位符,举例:patch-app-1.0.0-20250101T000000Z-abc123.tar.gz - -# ============================================================================== -# 日志配置 - 定义日志级别、文件路径和大小 -# ============================================================================== - -# 日志配置 -LOG_LEVEL="INFO" # 日志级别,DEBUG, INFO, WARN, ERROR -LOG_FILE="/var/log/patch_system/patch.log" # 日志文件路径 -LOG_MAX_SIZE="100MB" # 日志文件最大大小 -LOG_BACKUP_COUNT=10 # 日志文件备份数量 - -# ============================================================================== -# 长路径支持 - 定义是否启用长路径支持 -# ============================================================================== - -# 长路径支持 -LONG_PATH_SUPPORT=true # 是否启用长路径支持 -TAR_OPTIONS="--force-local" # tar命令选项,--force-local 强制使用本地文件系统 diff --git a/scripts/patch_tools/patch_fnc.sh b/scripts/patch_tools/patch_fnc.sh deleted file mode 100644 index a44e2a691..000000000 --- a/scripts/patch_tools/patch_fnc.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/bin/bash -# patch_fnc.sh - 企业级增量补丁包生成脚本公共函数 -# 用于并行查找,提供给xargs使用 - - -# 文件工具函数 -parse_size() { - local size_str="$1" - local unit=$(echo "$size_str" | sed 's/[0-9.]//g' | tr 'a-z' 'A-Z') - local value=$(echo "$size_str" | sed 's/[^0-9.]//g') - - case "$unit" in - "KB") echo "$(echo "$value * 1024" | bc)" ;; - "MB") echo "$(echo "$value * 1024 * 1024" | bc)" ;; - "GB") echo "$(echo "$value * 1024 * 1024 * 1024" | bc)" ;; - *) echo "$value" ;; - esac -} - -get_file_hash() { - local file_path="$1" - local algorithm="${2:-sha256}" - - case "$algorithm" in - "md5") md5sum "$file_path" | cut -d' ' -f1 ;; - "sha1") sha1sum "$file_path" | cut -d' ' -f1 ;; - "sha256") sha256sum "$file_path" | cut -d' ' -f1 ;; - *) sha256sum "$file_path" | cut -d' ' -f1 ;; - esac -} - -get_file_info() { - local file_path="$1" - - if [[ ! -f "$file_path" ]]; then - error "文件不存在: $file_path" - return 1 - fi - - local stat_output - if stat --version 2>&1 | grep -q GNU; then - # GNU stat - stat_output=$(stat -c "%s|%Y|%Z|%a|%u|%g" "$file_path") - else - # BSD stat - stat_output=$(stat -f "%z|%m|%c|%p|%u|%g" "$file_path") - fi - - echo "$stat_output" -} - -# 文件筛选 -should_include_file() { - local file_path="$1" - local file_info="$2" - - IFS='|' read -r size mtime ctime permissions uid gid <<< "$file_info" - - # 文件大小过滤 - local max_size=$(parse_size "$MAX_FILE_SIZE") - local min_size=$(parse_size "$MIN_FILE_SIZE") - - if [[ $size -gt $max_size ]] || [[ $size -lt $min_size ]]; then - warn "文件大小超出范围: $file_path ($size bytes, 限制:$min_size-$max_size bytes)" - return 1 - fi - - # 包含模式匹配 - local include_match=false - for pattern in "${INCLUDE_PATTERNS[@]}"; do - if [[ "$file_path" == $pattern ]] || [[ "$file_path" =~ $pattern ]]; then - include_match=true - debug "文件匹配包含模式<$pattern>: $file_path" - break - fi - done - - if [[ ${#INCLUDE_PATTERNS[@]} -gt 0 ]] && ! $include_match; then - warn "文件不匹配任何包含模式: $file_path" - return 1 - fi - - # 排除模式匹配 - for pattern in "${EXCLUDE_PATTERNS[@]}"; do - if [[ "$file_path" == $pattern ]] || [[ "$file_path" =~ $pattern ]]; then - warn "文件匹配排除模式<$pattern>: $file_path" - return 1 - fi - done - - # 时间过滤 - if [[ -n "$MODIFIED_AFTER" ]]; then - local filter_time=$(date -d "$MODIFIED_AFTER" +%s 2>/dev/null || date +%s) - if [[ $mtime -lt $filter_time ]]; then - local mtime_str=$(date -d @$mtime +"%Y-%m-%d %H:%M:%S") - warn "文件修改时间过早: $file_path ($mtime_str)" - return 1 - fi - fi - - # 权限过滤 - if [[ "$PERMISSIONS_FILTER" == "true" ]]; then - if [[ $permissions -ne 644 ]] && [[ $permissions -ne 755 ]]; then - warn "文件权限不符合要求: $file_path ($permissions)" - return 1 - fi - fi - - # 用户过滤 - if [[ "$UID_FILTER" == "true" ]]; then - if [[ $uid -ne 0 ]] && [[ $uid -ne 1000 ]]; then - warn "文件所有者不符合要求: $file_path ($uid)" - return 1 - fi - fi - - # 组过滤 - if [[ "$GID_FILTER" == "true" ]]; then - if [[ $gid -ne 0 ]] && [[ $gid -ne 1000 ]]; then - warn "文件组不符合要求: $file_path ($gid)" - return 1 - fi - fi - - return 0 -} \ No newline at end of file diff --git a/scripts/patch_tools/patch_generator.sh b/scripts/patch_tools/patch_generator.sh deleted file mode 100644 index 6f3ccf7eb..000000000 --- a/scripts/patch_tools/patch_generator.sh +++ /dev/null @@ -1,1099 +0,0 @@ -#!/bin/bash -# patch_generator.sh - 企业级增量补丁包生成脚本 -# 支持内容比较、时间比较、长路径、安全签名等高级特性 - -set -euo pipefail -shopt -s nullglob extglob - -# 脚本配置 -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CONFIG_FILE="${SCRIPT_DIR}/patch_config.sh" -LOG_FILE="/var/log/patch_system/generate_$(date +%Y%m%d_%H%M%S).log" -TEMP_DIR=$(mktemp -d "/tmp/patch_gen_XXXXXX") - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -PURPLE='\033[0;35m' -CYAN='\033[0;36m' -NC='\033[0m' - -# 日志函数 -log() { - local level="$1" - local message="$2" - local timestamp=$(date '+%Y-%m-%d %H:%M:%S') - - case "$level" in - "INFO") color="$GREEN" ;; - "WARN") color="$YELLOW" ;; - "ERROR") color="$RED" ;; - "DEBUG") color="$BLUE" ;; - "TRACE") color="$PURPLE" ;; - *) color="$NC" ;; - esac - - - # 日志级别过滤 - case "$LOG_LEVEL" in - "DEBUG") - # DEBUG级别:输出所有日志 - ;; - "TRACE") - # TRACE级别:输出所有日志 - ;; - "INFO") - # INFO级别:只输出INFO、WARN和ERROR日志 - if [[ "$level" == "DEBUG" ]] || [[ "$level" == "TRACE" ]]; then - return 0 - fi - ;; - "WARN") - # WARN级别:只输出WARN和ERROR日志 - if [[ "$level" == "DEBUG" ]] || [[ "$level" == "TRACE" ]] || [[ "$level" == "INFO" ]]; then - return 0 - fi - ;; - "ERROR") - # ERROR级别:只输出ERROR日志 - if [[ "$level" != "ERROR" ]]; then - return 0 - fi - ;; - *) - # 其他级别:不输出任何日志 - return 0 - ;; - esac - - echo -e "${color}[$timestamp] [$level]${NC} $message" | tee -a "$LOG_FILE" -} - -debug() { log "DEBUG" "$1"; } -trace() { log "TRACE" "$1"; } -info() { log "INFO" "$1"; } -warn() { log "WARN" "$1"; } -error() { log "ERROR" "$1"; } - - -# 配置加载 -load_config() { - if [[ ! -f "$CONFIG_FILE" ]]; then - error "配置文件不存在: $CONFIG_FILE" - exit 1 - fi - - # 设置默认值 - : ${PATCH_NAME:="unnamed-patch"} - : ${PATCH_VERSION:="1.0.0"} - : ${OUTPUT_DIRECTORY:="/opt/patches"} - : ${LOG_LEVEL:="INFO"} - : ${MAX_WORKERS:=4} - : ${MAX_FILE_SIZE:="100MB"} - : ${COMPRESSION_LEVEL:=6} - : ${PERMISSIONS_FILTER:="false"} - : ${UID_FILTER:="false"} - : ${GID_FILTER:="false"} - : ${CHECKSUM_ENABLED:="true"} - : ${SIGNING_ENABLED:="false"} - : ${COMPRESSION_ALGORITHM:="gzip"} - : ${HASH_ALGORITHM:="sha256"} - : ${COMPARISON_METHOD:="both"} - : ${TIME_PRECISION:="second"} - : ${LONG_PATH_SUPPORT:="true"} - : ${IGNORE_LINE_ENDINGS:="false"} - - source "$CONFIG_FILE" - info "配置文件加载完成" -} - -# 环境设置 -setup_environment() { - # 创建必要目录 - mkdir -p "$(dirname "$LOG_FILE")" - mkdir -p "$OUTPUT_DIRECTORY" - mkdir -p "$TEMP_DIR" - - # 注意:不在环境设置中启用调试模式,以免影响日志输出 - # 如果需要详细调试,可以在脚本开头手动设置 set -x - - info "环境设置完成" - info "日志文件: $LOG_FILE" - info "输出目录: $OUTPUT_DIRECTORY" - info "临时目录: $TEMP_DIR" -} - -# 清理函数 -cleanup() { - if [[ -d "$TEMP_DIR" ]]; then - rm -rf "$TEMP_DIR" - info "临时目录已清理: $TEMP_DIR" - fi -} - -trap cleanup EXIT - -# 依赖检查 -check_dependencies() { - local deps=("tar" "gzip" "jq" "find" "stat" "sha256sum" "date" "mkdir" "cp" "bc") - local missing=() - - for dep in "${deps[@]}"; do - if ! command -v "$dep" >/dev/null 2>&1; then - missing+=("$dep") - fi - done - - if [[ ${#missing[@]} -gt 0 ]]; then - error "缺少依赖工具: ${missing[*]}" - return 1 - fi - - # 检查可选依赖 - if [[ "$SIGNING_ENABLED" == "true" ]] && ! command -v gpg >/dev/null 2>&1; then - warn "GPG未安装,签名功能将禁用" - SIGNING_ENABLED="false" - fi - - info "依赖检查通过" - return 0 -} - -# 文件工具函数 -parse_size() { - local size_str="$1" - local unit=$(echo "$size_str" | sed 's/[0-9.]//g' | tr 'a-z' 'A-Z') - local value=$(echo "$size_str" | sed 's/[^0-9.]//g') - - case "$unit" in - "KB") echo "$(echo "$value * 1024" | bc)" ;; - "MB") echo "$(echo "$value * 1024 * 1024" | bc)" ;; - "GB") echo "$(echo "$value * 1024 * 1024 * 1024" | bc)" ;; - *) echo "$value" ;; - esac -} - - -get_file_hash() { - local file_path="$1" - local algorithm="${2:-sha256}" - - # 根据 $IGNORE_LINE_ENDINGS 是否为true,决定是否忽略行尾的换行符 - local cmd_tr="" - if [[ "$IGNORE_LINE_ENDINGS" == "true" ]]; then - cmd_tr="tr -d '\r\n'" - fi - - case "$algorithm" in - "md5") - if [[ -n "$cmd_tr" ]]; then - cat "$file_path" | $cmd_tr | md5sum | cut -d' ' -f1 - else - cat "$file_path" | md5sum | cut -d' ' -f1 - fi;; - "sha1") - if [[ -n "$cmd_tr" ]]; then - cat "$file_path" | $cmd_tr | sha1sum | cut -d' ' -f1 - else - cat "$file_path" | sha1sum | cut -d' ' -f1 - fi;; - "sha256") - if [[ -n "$cmd_tr" ]]; then - cat "$file_path" | $cmd_tr | sha256sum | cut -d' ' -f1 - else - cat "$file_path" | sha256sum | cut -d' ' -f1 - fi;; - *) - if [[ -n "$cmd_tr" ]]; then - cat "$file_path" | $cmd_tr | sha256sum | cut -d' ' -f1 - else - cat "$file_path" | sha256sum | cut -d' ' -f1 - fi;; - esac -} - -get_file_info() { - local file_path="$1" - - if [[ ! -f "$file_path" ]]; then - error "文件不存在: $file_path" - return 1 - fi - - local stat_output - if stat --version 2>&1 | grep -q GNU; then - # GNU stat - stat_output=$(stat -c "%s|%Y|%Z|%a|%u|%g" "$file_path") - else - # BSD stat - stat_output=$(stat -f "%z|%m|%c|%p|%u|%g" "$file_path") - fi - - echo "$stat_output" -} - -# 文件筛选 -should_include_file() { - local file_path="$1" - local file_info="$2" - - IFS='|' read -r size mtime ctime permissions uid gid <<< "$file_info" - - # 文件大小过滤 - local max_size=$(parse_size "$MAX_FILE_SIZE") - local min_size=$(parse_size "$MIN_FILE_SIZE") - - if [[ $size -gt $max_size ]] || [[ $size -lt $min_size ]]; then - warn "文件大小超出范围: $file_path ($size bytes, 限制:$min_size-$max_size bytes)" - return 1 - fi - - # 包含模式匹配 - local include_match=false - for pattern in "${INCLUDE_PATTERNS[@]}"; do - if [[ "$file_path" == $pattern ]] || [[ "$file_path" =~ $pattern ]]; then - include_match=true - trace "文件匹配包含模式<$pattern>: $file_path" - break - fi - done - - if [[ ${#INCLUDE_PATTERNS[@]} -gt 0 ]] && ! $include_match; then - warn "文件不匹配任何包含模式: $file_path" - return 1 - fi - - # 排除模式匹配 - for pattern in "${EXCLUDE_PATTERNS[@]}"; do - if [[ "$file_path" == $pattern ]] || [[ "$file_path" =~ $pattern ]]; then - warn "文件匹配排除模式<$pattern>: $file_path" - return 1 - fi - done - - # 时间过滤 - if [[ -n "$MODIFIED_AFTER" ]]; then - local filter_time=$(date -d "$MODIFIED_AFTER" +%s 2>/dev/null || date +%s) - if [[ $mtime -lt $filter_time ]]; then - local mtime_str=$(date -d @$mtime +"%Y-%m-%d %H:%M:%S") - warn "文件修改时间过早: $file_path ($mtime_str)" - return 1 - fi - fi - - # 权限过滤 - if [[ "$PERMISSIONS_FILTER" == "true" ]]; then - if [[ $permissions -ne 644 ]] && [[ $permissions -ne 755 ]]; then - warn "文件权限不符合要求: $file_path ($permissions)" - return 1 - fi - fi - - # 用户过滤 - if [[ "$UID_FILTER" == "true" ]]; then - if [[ $uid -ne 0 ]] && [[ $uid -ne 1000 ]]; then - warn "文件所有者不符合要求: $file_path ($uid)" - return 1 - fi - fi - - # 组过滤 - if [[ "$GID_FILTER" == "true" ]]; then - if [[ $gid -ne 0 ]] && [[ $gid -ne 1000 ]]; then - warn "文件组不符合要求: $file_path ($gid)" - return 1 - fi - fi - - return 0 -} - -# 目录扫描 -scan_directory() { - local base_dir="$1" - local output_file="$2" - - info "开始扫描目录: $base_dir" - - if [[ ! -d "$base_dir" ]]; then - error "目录不存在: $base_dir" - return 1 - fi - - # 清空输出文件 - > "$output_file" - - local file_count=0 - local scanned_count=0 - - # 使用find扫描文件 - while IFS= read -r -d '' file_path; do - ((scanned_count++)) - - # 获取文件信息 - local file_info - if file_info=$(get_file_info "$file_path" 2>/dev/null); then - # 检查是否应该包含该文件 - local relative_path="${file_path#$base_dir/}" # 这里一定要使用相对路径,方便在config中配置匹配模式 - if should_include_file "$relative_path" "$file_info"; then - local file_hash=$(get_file_hash "$file_path" "$HASH_ALGORITHM") - - echo "$relative_path|$file_info|$file_hash" >> "$output_file" - ((file_count++)) - - if [[ $((file_count % 500)) -eq 0 ]]; then - info "已扫描 $file_count 个文件..." - fi - fi - else - warn "无法获取文件信息: $file_path" - fi - done < <(find "$base_dir" -type f -print0 2>/dev/null) - - info "目录扫描完成: $base_dir (扫描: $scanned_count, 包含: $file_count)" - info "-------------------------------------------------------------- " - return 0 -} - -# 并行目录扫描 -scan_directory_parallel() { - local base_dir="$1" - local output_file="$2" - - info "开始并行扫描目录: $base_dir" - - if [[ ! -d "$base_dir" ]]; then - error "目录不存在: $base_dir" - return 1 - fi - - # 清空输出文件 - > "$output_file" - - # 创建临时文件存储处理结果 - local temp_result="$TEMP_DIR/parallel_result_$$" - - # 加载函数文件 - local func_file="$SCRIPT_DIR/patch_fnc.sh" - - # 使用xargs进行并行处理 - find "$base_dir" -type f -print0 2>/dev/null | \ - xargs -0 -P "$MAX_WORKERS" -I {} bash -c ' - source "'"$func_file"'" - file_path="{}" - file_info=$(get_file_info "$file_path" 2>/dev/null) - if [[ $? -eq 0 ]] && should_include_file "$file_path" "$file_info"; then - file_hash=$(get_file_hash "$file_path" "'"$HASH_ALGORITHM"'") - relative_path="${file_path#'"$base_dir"'/}" - echo "$relative_path|$file_info|$file_hash" - fi - ' 2>>"$LOG_FILE" | \ - tee -a "$temp_result" >> "$output_file" - - # 计算文件数量 - local file_count=$(wc -l < "$temp_result") - local scanned_count=$(find "$base_dir" -type f 2>/dev/null | wc -l) - - # 清理临时文件 - rm -f "$temp_result" - - info "并行扫描完成: $base_dir (扫描: $scanned_count, 包含: $file_count)" - return 0 -} - -# 差异比较 -compare_files() { - local old_file="$1" - local new_file="$2" - local output_file="$3" - - info "开始比较文件差异" - - # 输出差异比较规则 - info "比较规则: " - info "比较方法配置: $COMPARISON_METHOD" - info "哈希算法配置: $HASH_ALGORITHM" - info "时间精度:$TIME_PRECISION" - info "是否忽略换行符不同: $IGNORE_LINE_ENDINGS" - - - - declare -A old_files - declare -A new_files - - # 加载旧版本文件信息 - while IFS='|' read -r path file_info hash; do - old_files["$path"]="$file_info|$hash" - done < "$old_file" - - # 加载新版本文件信息 - while IFS='|' read -r path file_info hash; do - new_files["$path"]="$file_info|$hash" - done < "$new_file" - - # 清空输出文件 - > "$output_file" - - local changes=() - - # 检测新增文件 - for path in "${!new_files[@]}"; do - if [[ -z "${old_files[$path]:-}" ]]; then - changes+=("ADDED|$path|${new_files[$path]}") - warn "检测新增文件: $path" - fi - done - - # 检测删除文件 - for path in "${!old_files[@]}"; do - if [[ -z "${new_files[$path]:-}" ]]; then - changes+=("DELETED|$path|${old_files[$path]}") - warn "检测到删除文件: $path" - fi - done - - # 检测修改文件 - for path in "${!new_files[@]}"; do - if [[ -n "${old_files[$path]:-}" ]]; then - IFS='|' read -r old_info old_hash <<< "${old_files[$path]}" - IFS='|' read -r new_info new_hash <<< "${new_files[$path]}" - - local is_modified=false - local old_short_hash="${old_hash##*|}" # 使用短哈希值,不使用复合哈希值,因为复合哈希值包含权限,用户和组信息, - local new_short_hash="${new_hash##*|}" # 使用短哈希值,不使用复合哈希值,因为复合哈希值包含权限,用户和组信息, - - case "$COMPARISON_METHOD" in - "content") - [[ "$old_short_hash" != "$new_short_hash" ]] && is_modified=true - if $is_modified; then - trace "检测到修改文件: $path | 哈希值变化: <$old_short_hash> => <$new_short_hash>" - fi - ;; - "time") - IFS='|' read -r old_size old_mtime old_ctime old_perm old_uid old_gid <<< "$old_info" - IFS='|' read -r new_size new_mtime new_ctime new_perm new_uid new_gid <<< "$new_info" - - if [[ "$TIME_PRECISION" == "second" ]]; then - [[ $old_mtime -ne $new_mtime ]] && is_modified=true - if $is_modified; then - trace "检测到修改文件: $path | 时间变化: <$old_mtime> => <$new_mtime>" - fi - else - [[ $(echo "$old_mtime != $new_mtime" | bc) -eq 1 ]] && is_modified=true - if $is_modified; then - trace "检测到修改文件: $path | 时间变化: <$old_mtime> => <$new_mtime>" - fi - fi - ;; - "both") - IFS='|' read -r old_size old_mtime old_ctime old_perm old_uid old_gid <<< "$old_info" - IFS='|' read -r new_size new_mtime new_ctime new_perm new_uid new_gid <<< "$new_info" - - if [[ "$old_short_hash" != "$new_short_hash" ]]; then - is_modified=true - trace "检测到修改文件: $path | 哈希值变化: <$old_short_hash> => <$new_short_hash>" - elif [[ "$TIME_PRECISION" == "second" ]] && [[ $old_mtime -ne $new_mtime ]]; then - is_modified=true - trace "检测到修改文件: $path | 时间变化: <$old_mtime> => <$new_mtime>" - elif [[ "$TIME_PRECISION" == "millisecond" ]] && [[ $(echo "$old_mtime != $new_mtime" | bc) -eq 1 ]]; then - is_modified=true - trace "检测到修改文件: $path | 时间变化: <$old_mtime> => <$new_mtime>" - fi - ;; - esac - - if $is_modified; then - changes+=("MODIFIED|$path|${old_files[$path]}|${new_files[$path]}") - - fi - fi - done - - # 写入变更到文件 - for change in "${changes[@]}"; do - echo "$change" >> "$output_file" - done - - local change_count=${#changes[@]} - info "差异比较完成: $change_count 个变更" - - return 0 -} - -# 处理长路径复制 -handle_long_path_copy() { - local source="$1" - local dest="$2" - - local max_path_length=200 - local dest_length=${#dest} - - if [[ $dest_length -le $max_path_length ]]; then - cp -p "$source" "$dest" - return $? - fi - - # 方法1: 使用相对路径 - local source_dir=$(dirname "$source") - local dest_dir=$(dirname "$dest") - local filename=$(basename "$source") - - if pushd "$source_dir" >/dev/null 2>&1; then - if cp -p "$filename" "$dest" 2>/dev/null; then - popd >/dev/null 2>&1 - return 0 - fi - popd >/dev/null 2>&1 - fi - - # 方法2: 使用临时短路径 - local temp_file="$TEMP_DIR/short_$(basename "$source")" - if cp -p "$source" "$temp_file" && mv "$temp_file" "$dest"; then - return 0 - fi - - # 方法3: 使用rsync(如果可用) - if command -v rsync >/dev/null 2>&1; then - if rsync -a "$source" "$dest"; then - return 0 - fi - fi - - error "无法复制长路径文件: $source -> $dest" - return 1 -} - -# 生成清单文件 -generate_manifest() { - local changes_file="$1" - local patch_dir="$2" - - local manifest_file="$patch_dir/MANIFEST.MF" - - info "生成清单文件: $manifest_file" - - cat > "$manifest_file" << EOF -# 补丁包清单 -名称: $PATCH_NAME -版本: $PATCH_VERSION -描述: $PATCH_DESCRIPTION -作者: $PATCH_AUTHOR -邮箱: $PATCH_EMAIL -生成时间: $(date) -生成主机: $(hostname) -比较方法: $COMPARISON_METHOD -哈希算法: $HASH_ALGORITHM -包含目录: ${TARGET_DIRECTORIES[*]} - -# 变更统计 -EOF - - # 统计变更类型 - local added_count=$(grep -c "^ADDED" "$changes_file") - local modified_count=$(grep -c "^MODIFIED" "$changes_file") - local deleted_count=$(grep -c "^DELETED" "$changes_file") - - cat >> "$manifest_file" << EOF -新增文件: $added_count -修改文件: $modified_count -删除文件: $deleted_count -总变更数: $((added_count + modified_count + deleted_count)) - -# 变更详情 -EOF - - # 写入变更详情 - while IFS='|' read -r change_type path extra_info; do - case "$change_type" in - "ADDED") - IFS='|' read -r size mtime ctime perm uid gid hash <<< "$extra_info" - printf "ADDED|%s|%d|%s|%s\n" "$path" "$size" "$hash" "$(date -d "@$mtime")" >> "$manifest_file" - ;; - "MODIFIED") - IFS='|' read -r old_info new_info <<< "$extra_info" - IFS='|' read -r old_size old_mtime old_ctime old_perm old_uid old_gid old_hash <<< "$old_info" - IFS='|' read -r new_size new_mtime new_ctime new_perm new_uid new_gid new_hash <<< "$new_info" - printf "MODIFIED|%s|%d->%d|%s->%s\n" "$path" "$old_size" "$new_size" "$old_hash" "$new_hash" >> "$manifest_file" - ;; - "DELETED") - IFS='|' read -r size mtime ctime perm uid gid hash <<< "$extra_info" - printf "DELETED|%s|%d|%s\n" "$path" "$size" "$hash" >> "$manifest_file" - ;; - esac - done < "$changes_file" - - info "清单文件生成完成" -} - -# 创建补丁包 -create_patch_package() { - local patch_dir="$1" - local output_path="$2" - - info "创建补丁包: $output_path" - info "压缩算法: $COMPRESSION_ALGORITHM" - info "长路径支持: $LONG_PATH_SUPPORT" - info "补丁内容目录: $patch_dir" - - # 确定压缩选项 - local compression_flag="" - case "$COMPRESSION_ALGORITHM" in - "gzip") compression_flag="z" ;; - "bzip2") compression_flag="j" ;; - "xz") compression_flag="J" ;; - *) compression_flag="" ;; - esac - - # 处理长路径支持 - local tar_options="-c${compression_flag}f" - if [[ "$LONG_PATH_SUPPORT" == "true" ]]; then - tar_options="--force-local $tar_options" - fi - - # 创建补丁包 - if pushd "$patch_dir" >/dev/null 2>&1; then - if tar $tar_options "$output_path" .; then - local size=$(du -h "$output_path" | cut -f1) - info "✅ 补丁包创建成功: $output_path ($size)" - popd >/dev/null 2>&1 - return 0 - else - error "❌ 补丁包创建失败" - popd >/dev/null 2>&1 - return 1 - fi - else - error "无法进入补丁目录: $patch_dir" - return 1 - fi -} - -# 安全签名 -sign_package() { - local package_path="$1" - - if [[ "$SIGNING_ENABLED" != "true" ]]; then - info "签名功能已禁用" - return 0 - fi - - if [[ ! -f "$PRIVATE_KEY" ]]; then - warn "私钥文件不存在: $PRIVATE_KEY" - return 1 - fi - - info "开始签名补丁包" - - if command -v gpg >/dev/null 2>&1; then - if gpg --homedir "/etc/patch/keys" \ - --batch --yes --detach-sign \ - --local-user "$PATCH_AUTHOR" \ - --output "${package_path}.sig" \ - "$package_path"; then - info "✅ 补丁包签名完成: ${package_path}.sig" - return 0 - else - error "❌ 签名失败" - return 1 - fi - else - warn "GPG未安装,跳过签名" - return 1 - fi -} - -# 生成校验和 -generate_checksum() { - local package_path="$1" - - if [[ "$CHECKSUM_ENABLED" != "true" ]]; then - info "校验和生成已禁用" - return 0 - fi - - info "生成校验和文件" - - local checksum_file="${package_path}.sha256" - if sha256sum "$package_path" > "$checksum_file"; then - info "✅ 校验和生成完成: $checksum_file" - return 0 - else - error "❌ 校验和生成失败" - return 1 - fi -} - -# 生成回滚包 -generate_rollback_package() { - if [[ "$ROLLBACK_ENABLED" != "true" ]]; then - info "回滚包生成已禁用" - return 0 - fi - - local source_dir="$1" - local changes_file="$2" - local patch_path="$3" - - info "开始生成回滚包" - - local rollback_dir="$TEMP_DIR/rollback_content" - mkdir -p "$rollback_dir" - - local rollback_files=() - - # 收集需要回滚的文件 - while IFS='|' read -r change_type path extra_info; do - case "$change_type" in - "MODIFIED"|"DELETED") - local source_file="$source_dir/$path" - if [[ -f "$source_file" ]]; then - local dest_file="$rollback_dir/$path" - local dest_dir=$(dirname "$dest_file") - - mkdir -p "$dest_dir" - if handle_long_path_copy "$source_file" "$dest_file"; then - rollback_files+=("$path") - trace "回滚文件: $source_file" - else - warn "无法复制回滚文件: $source_file" - fi - fi - ;; - esac - done < "$changes_file" - - if [[ ${#rollback_files[@]} -eq 0 ]]; then - warn "没有需要回滚的文件" - return 0 - fi - - # 创建回滚包 - local rollback_path="${patch_path%.*}.rollback.${patch_path##*.}" - if create_patch_package "$rollback_dir" "$rollback_path"; then - local size=$(du -h "$rollback_path" | cut -f1) - info "✅ 回滚包生成完成: $rollback_path ($size)" - echo "$rollback_path" - return 0 - else - error "❌ 回滚包生成失败" - return 1 - fi -} - -# 通知系统 -send_notification() { - local status="$1" - local message="$2" - local patch_path="${3:-}" - - if [[ "$NOTIFICATIONS_ENABLED" != "true" ]]; then - return 0 - fi - - local subject="" - local color="" - - case "$status" in - "start") - subject="补丁生成开始" - color="#3498db" - ;; - "success") - subject="补丁生成成功" - color="#2ecc71" - ;; - "failure") - subject="补丁生成失败" - color="#e74c3c" - ;; - *) - subject="补丁生成通知" - color="#95a5a6" - ;; - esac - - # Slack通知 - if [[ -n "$SLACK_WEBHOOK" ]]; then - send_slack_notification "$subject" "$message" "$color" "$patch_path" - fi - - # 邮件通知(简化实现) - if [[ -n "$EMAIL_RECIPIENTS" ]]; then - send_email_notification "$subject" "$message" "$patch_path" - fi -} - -send_slack_notification() { - local subject="$1" - local message="$2" - local color="$3" - local patch_path="$4" - - local payload=$(cat << EOF -{ - "attachments": [ - { - "color": "$color", - "title": "$subject", - "text": "$message", - "fields": [ - { - "title": "补丁名称", - "value": "$PATCH_NAME", - "short": true - }, - { - "title": "版本", - "value": "$PATCH_VERSION", - "short": true - }, - { - "title": "文件", - "value": "$(basename "$patch_path")", - "short": true - }, - { - "title": "时间", - "value": "$(date)", - "short": true - } - ] - } - ] -} -EOF -) - - if command -v curl >/dev/null 2>&1; then - if curl -s -X POST -H 'Content-type: application/json' \ - --data "$payload" "$SLACK_WEBHOOK" >/dev/null; then - info "Slack通知发送成功" - else - warn "Slack通知发送失败" - fi - fi -} - -send_email_notification() { - local subject="$1" - local message="$2" - local patch_path="$3" - - # 简化实现 - 实际环境中应使用sendmail或mail命令 - info "邮件通知: $subject - $message" - - local email_content=" -补丁生成通知 -======================================== -主题: $subject -时间: $(date) -主机: $(hostname) -补丁: $PATCH_NAME v$PATCH_VERSION -文件: $(basename "$patch_path") -描述: $message -======================================== -" - - # 这里可以添加实际的邮件发送逻辑 - echo "$email_content" > "$TEMP_DIR/email_notification.txt" - info "邮件内容已保存: $TEMP_DIR/email_notification.txt" -} - -# 主生成函数 -generate_patch() { - local source_dir="$1" - local target_dir="$2" - local output_path="$3" - - info "开始生成补丁包" - send_notification "start" "开始生成补丁包" "$output_path" - - # 扫描目录 - local old_manifest="$TEMP_DIR/old_manifest.txt" - local new_manifest="$TEMP_DIR/new_manifest.txt" - - if [[ "$PARALLEL_PROCESSING" == "true" ]]; then - scan_directory_parallel "$source_dir" "$old_manifest" - scan_directory_parallel "$target_dir" "$new_manifest" - else - scan_directory "$source_dir" "$old_manifest" - scan_directory "$target_dir" "$new_manifest" - fi - - # 比较差异 - local changes_file="$TEMP_DIR/changes.txt" - compare_files "$old_manifest" "$new_manifest" "$changes_file" - - # 检查是否有变更 - local change_count=$(wc -l < "$changes_file" 2>/dev/null || echo "0") - if [[ $change_count -eq 0 ]]; then - warn "未发现文件变更,无需生成补丁" - send_notification "success" "未发现文件变更" "$output_path" - return 0 - fi - - # 创建补丁内容目录 - local patch_content_dir="$TEMP_DIR/patch_content" - mkdir -p "$patch_content_dir/files" - - # 复制变更文件 - info "复制变更文件到补丁目录" - local copied_count=0 - - while IFS='|' read -r change_type path extra_info; do - case "$change_type" in - "ADDED"|"MODIFIED") - local source_file="$target_dir/$path" - local dest_file="$patch_content_dir/files/$path" - local dest_dir=$(dirname "$dest_file") - - mkdir -p "$dest_dir" - if handle_long_path_copy "$source_file" "$dest_file"; then - ((copied_count++)) - trace "复制文件: $source_file -> $dest_file" - else - warn "文件复制失败: $source_file" - fi - ;; - esac - done < "$changes_file" - - info "文件复制完成: $copied_count 个文件" - - # 生成清单 - generate_manifest "$changes_file" "$patch_content_dir" - - # 创建补丁包 - if create_patch_package "$patch_content_dir" "$output_path"; then - # 生成校验和 - generate_checksum "$output_path" - - # 签名补丁包 - sign_package "$output_path" - - # 生成回滚包 - generate_rollback_package "$source_dir" "$changes_file" "$output_path" - - send_notification "success" "补丁包生成成功" "$output_path" - return 0 - else - send_notification "failure" "补丁包生成失败" "$output_path" - return 1 - fi -} - -# 批量处理 -batch_generate() { - local config_dir="$1" - - if [[ ! -d "$config_dir" ]]; then - error "配置目录不存在: $config_dir" - return 1 - fi - - info "开始批量生成补丁包" - - local success_count=0 - local total_count=0 - - # 处理每个配置文件 - for config_file in "$config_dir"/*.sh; do - if [[ -f "$config_file" ]]; then - ((total_count++)) - info "处理配置: $(basename "$config_file")" - - # 备份当前配置 - cp "$CONFIG_FILE" "$TEMP_DIR/original_config.sh" - - # 使用新配置 - cp "$config_file" "$CONFIG_FILE" - load_config - - # 生成补丁包 - if main; then - ((success_count++)) - fi - - # 恢复原配置 - cp "$TEMP_DIR/original_config.sh" "$CONFIG_FILE" - load_config - fi - done - - info "批量生成完成: $success_count/$total_count 成功" - - if [[ $success_count -eq $total_count ]]; then - return 0 - else - return 1 - fi -} - -# 主函数 -main() { - local source_dir="${1:-}" - local target_dir="${2:-}" - local output_path="${3:-}" - - # 参数验证 - if [[ -z "$source_dir" ]] || [[ -z "$target_dir" ]]; then - error "必须指定源目录和目标目录" - echo "用法: $0 <源目录> <目标目录> [输出路径]" - echo "示例:" - echo " $0 /old/version /new/version" - echo " $0 /old/version /new/version /opt/patches/patch.tar.gz" - echo " $0 batch /path/to/configs" - exit 1 - fi - - # 批量处理模式 - if [[ "$source_dir" == "batch" ]]; then - batch_generate "$target_dir" - return $? - fi - - # 设置默认输出路径 - if [[ -z "$output_path" ]]; then - local timestamp=$(date +%Y%m%d_%H%M%S) - output_path="$OUTPUT_DIRECTORY/patch-${PATCH_NAME}-${PATCH_VERSION}-${timestamp}.tar.gz" - fi - - # 验证目录存在性 - if [[ ! -d "$source_dir" ]]; then - error "源目录不存在: $source_dir" - exit 1 - fi - - if [[ ! -d "$target_dir" ]]; then - error "目标目录不存在: $target_dir" - exit 1 - fi - - # 执行补丁生成 - if generate_patch "$source_dir" "$target_dir" "$output_path"; then - info "🎉 补丁包生成成功完成!" - info "📦 补丁文件: $output_path" - info "📋 日志文件: $LOG_FILE" - return 0 - else - error "💥 补丁包生成失败" - return 1 - fi -} - -# 异常处理 -trap 'error "脚本执行中断"; cleanup; exit 1' INT TERM - -# 加载配置 -load_config - -# 设置环境 -setup_environment - -# 检查依赖 -check_dependencies - -# 执行主函数 -main "$@" \ No newline at end of file diff --git a/scripts/patch_tools/patch_rollback.sh b/scripts/patch_tools/patch_rollback.sh deleted file mode 100644 index 680b157fc..000000000 --- a/scripts/patch_tools/patch_rollback.sh +++ /dev/null @@ -1,1085 +0,0 @@ -#!/bin/bash -# patch_rollback.sh - 企业级补丁包回滚脚本 -# 支持原子性回滚、多回滚点管理、验证等完整功能 - -set -euo pipefail -shopt -s nullglob extglob - -# 脚本配置 -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CONFIG_FILE="${SCRIPT_DIR}/patch_config.sh" -LOG_FILE="/var/log/patch_system/rollback_$(date +%Y%m%d_%H%M%S).log" -LOCK_FILE="/tmp/patch_rollback.lock" -ROLLBACK_INFO_DIR="/var/lib/patch_system/rollback_info" -ROLLBACK_POINTS=() # 存储回滚点数组 -ROLLBACK_COUNT=0 # 回滚点数量 - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -PURPLE='\033[0;35m' -CYAN='\033[0;36m' -NC='\033[0m' - -# 日志函数 -log() { - local level="$1" - local message="$2" - local timestamp=$(date '+%Y-%m-%d %H:%M:%S') - - case "$level" in - "INFO") color="$GREEN" ;; - "WARN") color="$YELLOW" ;; - "ERROR") color="$RED" ;; - "DEBUG") color="$BLUE" ;; - *) color="$NC" ;; - esac - - echo -e "${color}[$timestamp] [$level]${NC} $message" | tee -a "$LOG_FILE" -} - -info() { log "INFO" "$1"; } -warn() { log "WARN" "$1"; } -error() { log "ERROR" "$1"; } -debug() { log "DEBUG" "$1"; } - -# 配置加载 -load_config() { - if [[ ! -f "$CONFIG_FILE" ]]; then - error "配置文件不存在: $CONFIG_FILE" - exit 1 - fi - - source "$CONFIG_FILE" - info "配置文件加载完成" - - # 设置默认值 - : ${TEMP_DIR:="/tmp/patch_rollback_$$"} - : ${BACKUP_DIR:="/var/backups/patch"} - : ${ROLLBACK_ENABLED:="true"} - : ${ROLLBACK_INFO_DIR:="/var/lib/patch_system/rollback_info"} - : ${MAX_ROLLBACK_POINTS:=10} - : ${VALIDATION_ENABLED:="true"} - : ${NOTIFICATIONS_ENABLED:="true"} -} - -# 环境设置 -setup_environment() { - mkdir -p "$(dirname "$LOG_FILE")" - mkdir -p "$ROLLBACK_INFO_DIR" - mkdir -p "$BACKUP_DIR" - mkdir -p "/tmp/patch_rollback" - - info "环境设置完成" - info "日志文件: $LOG_FILE" - info "回滚信息目录: $ROLLBACK_INFO_DIR" -} - -# 锁管理 -acquire_lock() { - local max_retries=5 - local retry_count=0 - - while [[ $retry_count -lt $max_retries ]]; do - if (set -C; echo $$ > "$LOCK_FILE") 2>/dev/null; then - info "🔒 获取回滚锁" - return 0 - fi - - local lock_pid=$(cat "$LOCK_FILE" 2>/dev/null) - if kill -0 "$lock_pid" 2>/dev/null; then - warn "回滚操作正在进行中 (PID: $lock_pid),等待..." - sleep 5 - retry_count=$((retry_count+1)) - else - warn "清理过期的锁文件" - rm -f "$LOCK_FILE" - fi - done - - error "无法获取锁,可能已有回滚操作在进行中" - exit 1 -} - -release_lock() { - rm -f "$LOCK_FILE" - info "🔓 释放回滚锁" -} - -# 清理函数 -cleanup() { - local exit_code=$? - - if [[ -d "$TEMP_DIR" ]]; then - # rm -rf "$TEMP_DIR" - info "临时目录已清理: $TEMP_DIR" - fi - - release_lock - - if [[ $exit_code -eq 0 ]]; then - info "✅ 回滚流程完成" - else - error "💥 回滚流程失败 (退出码: $exit_code)" - fi - - exit $exit_code -} - -trap cleanup EXIT INT TERM - -# 依赖检查 -check_dependencies() { - local deps=("tar" "gzip" "jq" "sha256sum" "cp" "mkdir" "find") - local missing=() - - for dep in "${deps[@]}"; do - if ! command -v "$dep" >/dev/null 2>&1; then - missing+=("$dep") - fi - done - - if [[ ${#missing[@]} -gt 0 ]]; then - error "缺少依赖工具: ${missing[*]}" - return 1 - fi - - info "依赖检查通过" - return 0 -} - -# 获取可用的回滚点 -list_rollback_points() { - info "📋 可用的回滚点:" >&2 - info "回滚还原点信息目录: $ROLLBACK_INFO_DIR" >&2 - - if [[ ! -d "$ROLLBACK_INFO_DIR" ]]; then - warn "回滚信息目录不存在: $ROLLBACK_INFO_DIR" >&2 - return 1 - fi - - local count=0 - local temp_file="/tmp/rollback_list_$$" - local found_files=0 - local valid_files=0 - - # 安全检查 - if [[ ! -d "$ROLLBACK_INFO_DIR" ]]; then - error "回滚信息目录不存在: $ROLLBACK_INFO_DIR" >&2 - return 1 - fi - - info "在 $ROLLBACK_INFO_DIR 中搜索回滚信息文件..." >&2 - - # 清空临时文件 - > "$temp_file" || { - error "无法创建临时文件: $temp_file" - return 1 - } - - # 使用数组存储文件,避免管道问题 - local files=() - while IFS= read -r -d '' file; do - files+=("$file") - done < <(find "$ROLLBACK_INFO_DIR" -maxdepth 1 -name "*.json" -type f -print0 2>/dev/null) - - # 使用 ls -t 按时间排序 - IFS=$'\n' sorted_files=($(ls -t "${files[@]}")) - unset IFS - - found_files=${#sorted_files[@]} - info "找到 $found_files 个JSON文件" >&2 - - for info_file in "${sorted_files[@]}"; do - debug "处理文件: $info_file" - - # 文件可读性检查 - if [[ ! -r "$info_file" ]]; then - warning "文件不可读: $info_file" >&2 - continue - fi - - # 文件大小检查 - if [[ ! -s "$info_file" ]]; then - warning "空文件: $info_file" >&2 - continue - fi - - # 解析JSON - if ! jq -e '.' "$info_file" >/dev/null 2>&1; then - warning "无效的JSON文件: $info_file" >&2 - continue - fi - - # 提取字段(带默认值) - local patch_name apply_time backup_path status - - patch_name=$(jq -r '.patch_name // "unknown_patch"' "$info_file" 2>/dev/null) - apply_time=$(jq -r '.apply_time // "unknown_time"' "$info_file" 2>/dev/null) - backup_path=$(jq -r '.backup_path // ""' "$info_file" 2>/dev/null) - status=$(jq -r '.status // "unknown"' "$info_file" 2>/dev/null) - - # 验证必要字段 - if [[ -z "$backup_path" ]]; then - debug "缺少backup_path字段: $info_file" >&2 - continue - fi - - # 验证备份文件 - if [[ ! -f "$backup_path" ]]; then - warning "备份文件不存在: $backup_path (来自 $info_file)" >&2 - continue - fi - - # 验证状态 - if [[ "$status" != "applied" ]]; then - debug "状态不是applied: $status (来自 $info_file)" >&2 - continue - fi - - # 有效的可回滚文件 - debug "有效的可回滚文件: $info_file" >&2 - count=$((count+1)) - valid_files=$((valid_files+1)) - - echo "$info_file" >> "$temp_file" - echo " $count. $patch_name" - echo " 应用时间: $apply_time" - echo " 备份文件: $(basename "$backup_path")" - echo " 状态: $status" - echo " 信息文件: $(basename "$info_file")" - - done - - info "扫描完成: 找到 $found_files 个文件,其中 $valid_files 个可回滚" >&2 - - if [[ $valid_files -eq 0 ]]; then - rm -f "$temp_file" - return 1 - fi - - export ROLLBACK_LIST_FILE="$temp_file" - export ROLLBACK_COUNT=$valid_files - return 0 -} - -# 选择回滚点 -select_rollback_point() { - local rollback_id="${1:-}" - - if [[ -z "$rollback_id" ]]; then - info "交互式,请选择要回滚的点:" - # 交互式选择 - local points=() - local count=0 - - # 先获取回滚点列表 - local temp_file=$(mktemp) - if ! list_rollback_points > "$temp_file"; then - rm -f "$temp_file" - return 1 - fi - - # 从临时文件中读取回滚点数组 - if [[ -f "$temp_file" ]]; then - cat "$temp_file" - - # 使用临时文件存储回滚点数量 - local count_file=$(mktemp) - echo "0" > "$count_file" - - # 注意:这里我们重新加载 points 数组 - local temp_points_file=$(mktemp) - while IFS= read -r -d '' info_file; do - if [[ -f "$info_file" ]]; then - local backup_path=$(jq -r '.backup_path // ""' "$info_file" 2>/dev/null) - local status=$(jq -r '.status // "unknown"' "$info_file" 2>/dev/null) - if [[ -n "$backup_path" && -f "$backup_path" && "$status" == "applied" ]]; then - echo "$info_file" >> "$temp_points_file" - local current_count=$(cat "$count_file") - echo $((current_count + 1)) > "$count_file" - fi - fi - done < <(find "$ROLLBACK_INFO_DIR" -name "*.json" -print0 2>/dev/null | sort -z) - - # 读取计数 - count=$(cat "$count_file") - rm -f "$count_file" - - if [[ -f "$temp_points_file" ]]; then - mapfile -t points < "$temp_points_file" - rm -f "$temp_points_file" - fi - rm -f "$temp_file" - fi - - if [[ $count -eq 0 ]]; then - error "无可用的回滚点" - return 1 - fi - - echo "" - read -p "📝 请选择要回滚的补丁编号 (1-${count}): " selected_num - - if [[ ! "$selected_num" =~ ^[0-9]+$ ]] || [[ "$selected_num" -lt 1 ]] || [[ "$selected_num" -gt "$count" ]]; then - error "无效的选择: $selected_num" - return 1 - fi - - rollback_id="${points[$((selected_num-1))]}" - else - # 直接指定回滚点 - if [[ "$rollback_id" == "latest" ]]; then - # 选择最新的回滚点 - rollback_id=$(find "$ROLLBACK_INFO_DIR" -name "*.json" -exec jq -r '.apply_time + "|" + input_filename' {} \; 2>/dev/null | \ - sort -r | head -1 | cut -d'|' -f2) - - if [[ -z "$rollback_id" ]]; then - error "未找到回滚点" - return 1 - fi - elif [[ ! -f "$rollback_id" ]]; then - # 检查是否是文件路径 - if [[ -f "$ROLLBACK_INFO_DIR/$rollback_id" ]]; then - rollback_id="$ROLLBACK_INFO_DIR/$rollback_id" - elif [[ -f "$ROLLBACK_INFO_DIR/${rollback_id}.json" ]]; then - rollback_id="$ROLLBACK_INFO_DIR/${rollback_id}.json" - else - error "回滚点不存在: $rollback_id" - return 1 - fi - fi - fi - - if [[ ! -f "$rollback_id" ]]; then - error "回滚信息文件不存在: $rollback_id" - return 1 - fi - - echo "$rollback_id" - return 0 -} - -# 解析回滚信息 -parse_rollback_info() { - local info_file="$1" - - info "📖 解析回滚信息: $(basename "$info_file")" >&2 - - if [[ ! -f "$info_file" ]]; then - error "回滚信息文件不存在: $info_file" >&2 - return 1 - fi - - # 使用jq解析JSON - local patch_name=$(jq -r '.patch_name // "unknown"' "$info_file" 2>/dev/null) - local apply_time=$(jq -r '.apply_time // "unknown"' "$info_file" 2>/dev/null) - local backup_path=$(jq -r '.backup_path // ""' "$info_file" 2>/dev/null) - local rollback_dir=$(jq -r '.rollback_dir // ""' "$info_file" 2>/dev/null) - local manifest_info=$(jq -r '.manifest_info // ""' "$info_file" 2>/dev/null) - local status=$(jq -r '.status // "unknown"' "$info_file" 2>/dev/null) - - # 验证必要字段 - if [[ -z "$backup_path" ]]; then - error "回滚信息缺少备份路径" >&2 - return 1 - fi - - if [[ ! -f "$backup_path" ]]; then - error "备份文件不存在: $backup_path" >&2 - return 1 - fi - - if [[ "$status" != "applied" ]]; then - warn "回滚点状态为 $status,可能已回滚或无效" >&2 - fi - - # 创建回滚信息结构 - cat << EOF -{ - "info_file": "$info_file", - "patch_name": "$patch_name", - "apply_time": "$apply_time", - "backup_path": "$backup_path", - "rollback_dir": "$rollback_dir", - "manifest_info": "$manifest_info", - "status": "$status" -} -EOF - - return 0 -} - -# 验证备份文件 -verify_backup_file() { - local backup_file="$1" - - info "🔍 验证备份文件: $(basename "$backup_file")" - - if [[ ! -f "$backup_file" ]]; then - error "备份文件不存在: $backup_file" - return 1 - fi - - # 检查文件大小 - local size=$(stat -c%s "$backup_file" 2>/dev/null || echo "0") - if [[ $size -eq 0 ]]; then - error "备份文件为空: $backup_file" - return 1 - fi - - # 验证压缩包完整性 - if ! tar -tzf "$backup_file" >/dev/null 2>&1; then - error "备份文件损坏或格式错误: $backup_file" - return 1 - fi - - info "✅ 备份文件验证通过" - return 0 -} - -# 提取备份文件 -extract_backup() { - local backup_file="$1" - local extract_dir="$2" - - info "📦 提取备份文件到: $extract_dir" - - if [[ ! -d "$extract_dir" ]]; then - mkdir -p "$extract_dir" - fi - - if tar -xzf "$backup_file" -C "$extract_dir"; then - info "✅ 备份文件提取完成" - - # 验证提取内容 - local file_count=$(find "$extract_dir" -type f | wc -l) - if [[ $file_count -gt 0 ]]; then - info "找到 $file_count 个备份文件" - return 0 - else - warn "⚠️ 备份文件中未找到文件" - return 0 - fi - else - error "❌ 备份文件提取失败" - return 1 - fi -} - -# 执行回滚操作 -perform_rollback() { - local extract_dir="$1" - local dry_run="${2:-false}" - - info "🔄 开始执行回滚操作 (干跑模式: $dry_run)" - - if [[ ! -d "$extract_dir" ]]; then - error "提取目录不存在: $extract_dir" - return 1 - fi - - local rollback_count=0 - local error_count=0 - local skip_count=0 - - # 查找所有备份文件 - find "$extract_dir" -type f | while read backup_file; do - local relative_path="${backup_file#$extract_dir/}" - local target_file="$relative_path" - local target_dir=$(dirname "$target_file") - - if [[ "$dry_run" == "true" ]]; then - info "📋 [干跑] 回滚文件: $relative_path → $target_file" >&2 - rollback_count=$((rollback_count+1)) - continue - fi - - # 创建目标目录 - if [[ ! -d "$target_dir" ]]; then - if ! mkdir -p "$target_dir"; then - error "❌ 创建目录失败: $target_dir" >&2 - error_count=$((error_count+1)) - continue - fi - debug "创建目录: $target_dir" - fi - - # 备份当前文件(如果存在) - if [[ -f "$target_file" ]]; then - local current_backup="$TEMP_DIR/current_backup/$relative_path" - local current_dir=$(dirname "$current_backup") - mkdir -p "$current_dir" - - if ! cp -p "$target_file" "$current_backup"; then - warn "⚠️ 当前文件备份失败: $target_file" >&2 - fi - fi - - # 恢复文件 - if cp -p "$backup_file" "$target_file"; then - info "✅ 回滚文件: $relative_path | $backup_file -> $target_file" >&2 - ((rollback_count++)) - else - error "❌ 文件回滚失败: $relative_path" >&2 - error_count=$((error_count+1)) - fi - done - - info "📊 回滚操作统计:" - info " 成功: $rollback_count" - info " 失败: $error_count" - info " 跳过: $skip_count" - - if [[ $error_count -gt 0 ]]; then - error "❌ 回滚操作存在失败项" - return 1 - fi - - if [[ "$dry_run" == "true" ]]; then - info "✅ 干跑模式完成 - 可安全执行回滚" - return 0 - else - info "✅ 回滚操作完成" - return 0 - fi -} - -# 验证回滚结果 -verify_rollback() { - local extract_dir="$1" - local rollback_info="$2" - - if [[ "$VALIDATION_ENABLED" != "true" ]]; then - info "验证功能已禁用" - return 0 - fi - - info "🔍 验证回滚结果..." - - local verification_passed=true - local verified_count=0 - local failed_count=0 - - # 检查所有备份文件是否已正确恢复 - find "$extract_dir" -type f | while read backup_file; do - local relative_path="${backup_file#$extract_dir/}" - local target_file="$relative_path" - - if [[ ! -f "$target_file" ]]; then - error "❌ 目标文件不存在: $target_file" - verification_passed=false - fail_count=$((fail_count+1)) - continue - fi - - # 比较文件内容 - if ! cmp -s "$backup_file" "$target_file"; then - error "❌ 文件内容不匹配: $relative_path" - verification_passed=false - fail_count=$((fail_count+1)) - continue - fi - - verified_count=$((verified_count+1)) - done - - info "📊 回滚验证统计:" - info " 成功: $verified_count" - info " 失败: $failed_count" - - if $verification_passed; then - info "✅ 回滚验证通过" - return 0 - else - error "❌ 回滚验证失败" - return 1 - fi -} - -# 更新回滚点状态 -update_rollback_status() { - local info_file="$1" - local new_status="$2" - - if [[ ! -f "$info_file" ]]; then - error "回滚信息文件不存在: $info_file" - return 1 - fi - - # 使用jq更新状态 - if jq ".status = \"$new_status\" | .rollback_time = \"$(date -Iseconds)\"" "$info_file" > "$info_file.tmp"; then - mv "$info_file.tmp" "$info_file" - info "✅ 回滚点状态更新为: $new_status" - return 0 - else - error "❌ 回滚点状态更新失败" - return 1 - fi -} - -# 清理回滚点 -cleanup_rollback_point() { - local info_file="$1" - local extract_dir="$2" - - info "🧹 清理回滚资源..." - - # 删除提取目录 - if [[ -d "$extract_dir" ]]; then - rm -rf "$extract_dir" - debug "清理提取目录: $extract_dir" - fi - - # 可选:删除备份文件(谨慎操作) - local delete_backup="${3:-false}" - if [[ "$delete_backup" == "true" ]]; then - local backup_path=$(jq -r '.backup_path // ""' "$info_file") - if [[ -n "$backup_path" && -f "$backup_path" ]]; then - rm -f "$backup_path" - info "🗑️ 删除备份文件: $(basename "$backup_path")" - fi - fi - - # 删除回滚信息文件 - if [[ -f "$info_file" ]]; then - rm -f "$info_file" - info "🗑️ 删除回滚信息文件: $(basename "$info_file")" - fi - - info "✅ 回滚资源清理完成" -} - -# 发送通知 -send_rollback_notification() { - local status="$1" - local rollback_info="$2" - local message="$3" - - if [[ "$NOTIFICATIONS_ENABLED" != "true" ]]; then - return 0 - fi - - local patch_name=$(echo "$rollback_info" | jq -r '.patch_name') - local apply_time=$(echo "$rollback_info" | jq -r '.apply_time') - - local subject="" - local color="" - - case "$status" in - "start") - subject="回滚操作开始" - color="#3498db" - ;; - "success") - subject="回滚操作成功" - color="#2ecc71" - ;; - "failure") - subject="回滚操作失败" - color="#e74c3c" - ;; - *) - subject="回滚操作通知" - color="#95a5a6" - ;; - esac - - # Slack通知 - if [[ -n "${SLACK_WEBHOOK:-}" ]]; then - send_slack_rollback_notification "$subject" "$message" "$color" "$patch_name" "$apply_time" - fi - - log "NOTIFICATION" "$subject: $message" -} - -send_slack_rollback_notification() { - local subject="$1" - local message="$2" - local color="$3" - local patch_name="$4" - local apply_time="$5" - - local payload=$(cat << EOF -{ - "attachments": [ - { - "color": "$color", - "title": "$subject", - "text": "$message", - "fields": [ - { - "title": "补丁名称", - "value": "$patch_name", - "short": true - }, - { - "title": "应用时间", - "value": "$apply_time", - "short": true - }, - { - "title": "回滚时间", - "value": "$(date)", - "short": true - }, - { - "title": "回滚主机", - "value": "$(hostname)", - "short": true - } - ] - } - ] -} -EOF -) - - if command -v curl >/dev/null 2>&1; then - if curl -s -X POST -H 'Content-type: application/json' \ - --data "$payload" "$SLACK_WEBHOOK" >/dev/null; then - info "✅ Slack通知发送成功" - else - warn "⚠️ Slack通知发送失败" - fi - fi -} - -# 主回滚函数 -rollback_patch() { - local rollback_point="${1:-}" - local dry_run="${2:-false}" - local cleanup_after="${3:-false}" - - info "🚀 开始回滚操作, 回滚点: ${rollback_point}" - - # 发送开始通知 - local rollback_info_json="" - local rollback_info_file="" - - # 选择回滚点 - info "定位到回滚点 ${rollback_point}" - rollback_info_file=$(select_rollback_point "$rollback_point") - if [[ $? -ne 0 ]]; then - send_rollback_notification "failure" "{}" "回滚点选择失败" - return 1 - fi - - # 解析回滚信息 - info "解析回滚信息 ${rollback_info_file}" - rollback_info_json=$(parse_rollback_info "$rollback_info_file") - if [[ $? -ne 0 ]]; then - send_rollback_notification "failure" "{}" "回滚信息解析失败" - return 1 - fi - - local patch_name=$(echo "$rollback_info_json" | jq -r '.patch_name') - local backup_path=$(echo "$rollback_info_json" | jq -r '.backup_path') - - send_rollback_notification "start" "$rollback_info_json" "开始回滚补丁: $patch_name" - - # 验证备份文件 - if ! verify_backup_file "$backup_path"; then - send_rollback_notification "failure" "$rollback_info_json" "备份文件验证失败" - return 1 - fi - - # 创建临时目录 - TEMP_DIR=$(mktemp -d "/tmp/patch_rollback_XXXXXX") - local extract_dir="$TEMP_DIR/extract" - - # 提取备份文件 - if ! extract_backup "$backup_path" "$extract_dir"; then - send_rollback_notification "failure" "$rollback_info_json" "备份文件提取失败" - return 1 - fi - - # 执行回滚操作 - if ! perform_rollback "$extract_dir" "$dry_run"; then - send_rollback_notification "failure" "$rollback_info_json" "回滚操作失败" - return 1 - fi - - # 验证回滚结果(非干跑模式) - if [[ "$dry_run" != "true" ]]; then - if ! verify_rollback "$extract_dir" "$rollback_info_json"; then - send_rollback_notification "failure" "$rollback_info_json" "回滚验证失败" - return 1 - fi - - # 更新回滚点状态 - if ! update_rollback_status "$rollback_info_file" "rolled_back"; then - warn "⚠️ 回滚点状态更新失败" - fi - - # 清理资源 - if [[ "$cleanup_after" == "true" ]]; then - cleanup_rollback_point "$rollback_info_file" "$extract_dir" "true" - else - cleanup_rollback_point "$rollback_info_file" "$extract_dir" "false" - fi - fi - - send_rollback_notification "success" "$rollback_info_json" "回滚操作成功完成" - info "✅ 回滚流程完成" - return 0 -} - -# 批量清理旧回滚点 -cleanup_old_rollback_points() { - local max_points="${1:-$MAX_ROLLBACK_POINTS}" - - info "🧹 清理旧回滚点 (保留最近 $max_points 个)" - - if [[ ! -d "$ROLLBACK_INFO_DIR" ]]; then - warn "回滚信息目录不存在" - return 0 - fi - - # 获取所有回滚点并按时间排序 - local points=() - while IFS= read -r -d '' info_file; do - if [[ -f "$info_file" ]]; then - points+=("$info_file") - fi - done < <(find "$ROLLBACK_INFO_DIR" -name "*.json" -print0 2>/dev/null) - - local total_points=${#points[@]} - local points_to_remove=$((total_points - max_points)) - - if [[ $points_to_remove -le 0 ]]; then - info "无需清理,当前 $total_points 个回滚点" - return 0 - fi - - info "需要清理 $points_to_remove 个回滚点" - - # 按修改时间排序,删除最旧的 - for ((i=0; i 回滚点编号(使用-l查看) - <文件名> 回滚信息文件路径 - <补丁名> 补丁名称 - -示例: - $0 -l # 列出所有回滚点 - $0 latest # 回滚最新的补丁 - $0 1 # 回滚编号为1的补丁 - $0 --dry-run latest # 干跑模式测试回滚 - $0 --cleanup latest # 回滚并清理资源 - $0 --purge # 清理旧回滚点 - -环境变量: - PATCH_CONFIG 配置文件路径 - BACKUP_DIR 备份目录 - MAX_ROLLBACK_POINTS 最大保留回滚点数 -EOF -} - -# 显示版本信息 -show_version() { - echo "patch_rollback.sh v1.0.0" - echo "企业级补丁回滚系统" - echo "支持原子性回滚、多回滚点管理、验证等功能" -} - -# 主函数 -main() { - local rollback_point="" - local dry_run="false" - local cleanup_after="false" - local list_only="false" - local purge_all="false" - local rollback_all="false" - local verbose_mode="false" - - # 解析命令行参数 - while [[ $# -gt 0 ]]; do - case $1 in - -l|--list) - list_only="true" - shift - ;; - -d|--dry-run) - dry_run="true" - shift - ;; - -c|--cleanup) - cleanup_after="true" - shift - ;; - -p|--purge) - purge_all="true" - shift - ;; - -a|--all) - rollback_all="true" - shift - ;; - -v|--verbose) - verbose_mode="true" - shift - ;; - -q|--quiet) - exec >/dev/null 2>&1 - shift - ;; - -h|--help) - usage - exit 0 - ;; - -V|--version) - show_version - exit 0 - ;; - --) - shift - break - ;; - -*) - error "未知选项: $1" - usage - exit 1 - ;; - *) - rollback_point="$1" - shift - ;; - esac - done - - # 初始化 - acquire_lock - load_config - setup_environment - check_dependencies - - # 处理特殊操作 - if [[ "$list_only" == "true" ]]; then - list_rollback_points - exit $? - fi - - if [[ "$purge_all" == "true" ]]; then - cleanup_old_rollback_points 0 - exit $? - fi - - if [[ "$rollback_all" == "true" ]]; then - rollback_all_patches "$dry_run" "$cleanup_after" - exit $? - fi - - # 执行回滚操作 - if rollback_patch "$rollback_point" "$dry_run" "$cleanup_after"; then - if [[ "$dry_run" == "true" ]]; then - info "🎉 干跑模式完成 - 可安全执行回滚" - else - info "🎉 回滚操作成功完成" - fi - exit 0 - else - error "💥 回滚操作失败" - exit 1 - fi -} - -# 批量回滚所有补丁 -rollback_all_patches() { - local dry_run="$1" - local cleanup_after="$2" - - info "🔄 开始批量回滚所有补丁" - - if ! list_rollback_points; then - error "没有可用的回滚点" - return 1 - fi - - local total_count=${#points[@]} - local success_count=0 - local fail_count=0 - - info "📊 找到 $total_count 个回滚点,开始批量回滚..." - - # 按时间倒序回滚(最新的先回滚) - for ((i=total_count-1; i>=0; i--)); do - local info_file="${points[$i]}" - local patch_name=$(basename "$info_file" .json) - - info "🔄 回滚 ($((total_count-i))/$total_count): $patch_name" - - if rollback_patch "$info_file" "$dry_run" "$cleanup_after"; then - success_count=$((success_count+1)) - info "✅ 回滚成功: $patch_name" - else - fail_count=$((fail_count+1)) - error "❌ 回滚失败: $patch_name" - # 继续尝试下一个 - fi - - echo "---" - done - - info "📊 批量回滚完成:" - info " 成功: $success_count" - info " 失败: $fail_count" - info " 总计: $total_count" - - if [[ $fail_count -eq 0 ]]; then - info "✅ 所有回滚操作成功" - return 0 - else - error "⚠️ 部分回滚操作失败" - return 1 - fi -} - -# 异常处理 -trap 'error "脚本执行中断"; exit 1' INT TERM - -# 执行主函数 -main "$@" \ No newline at end of file diff --git a/scripts/patch_tools/patch_verifier.sh b/scripts/patch_tools/patch_verifier.sh deleted file mode 100644 index ffc05ee34..000000000 --- a/scripts/patch_tools/patch_verifier.sh +++ /dev/null @@ -1,541 +0,0 @@ -#!/bin/bash -# patch_verifier.sh - 企业级补丁包验证脚本 - -set -euo pipefail -shopt -s nullglob extglob - -# 脚本目录 -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CONFIG_FILE="${SCRIPT_DIR}/patch_config.sh" - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -PURPLE='\033[0;35m' -CYAN='\033[0;36m' -NC='\033[0m' - -# 日志函数 -log() { - local level="$1" - local message="$2" - local timestamp=$(date '+%Y-%m-%d %H:%M:%S') - - case "$level" in - "INFO") color="$GREEN" ;; - "WARN") color="$YELLOW" ;; - "ERROR") color="$RED" ;; - "DEBUG") color="$BLUE" ;; - *) color="$NC" ;; - esac - - echo -e "${color}[$timestamp] [$level]${NC} $message" | tee -a "$LOG_FILE" -} - -info() { log "INFO" "$1"; } -warn() { log "WARN" "$1"; } -error() { log "ERROR" "$1"; } -debug() { log "DEBUG" "$1"; } - -# 配置和初始化 -load_config() { - if [[ ! -f "$CONFIG_FILE" ]]; then - error "配置文件不存在: $CONFIG_FILE" - exit 1 - fi - - source "$CONFIG_FILE" - info "配置文件加载完成" - - : ${LOG_LEVEL:="INFO"} - : ${LOG_FILE:="/tmp/patch_verify_$(date +%Y%m%d_%H%M%S).log"} - : ${TEMP_DIR:="/tmp/patch_verify_$$"} -} - -setup_environment() { - mkdir -p "$(dirname "$LOG_FILE")" - mkdir -p "$TEMP_DIR" - - info "日志文件: $LOG_FILE" - info "临时目录: $TEMP_DIR" -} - -cleanup() { - if [[ -d "$TEMP_DIR" ]]; then - rm -rf "$TEMP_DIR" - info "临时目录已清理: $TEMP_DIR" - fi -} - -trap cleanup EXIT - -# 验证函数 -verify_package_integrity() { - local package_path="$1" - local verify_type="$2" # pre-apply, post-apply, standalone - - info "开始验证补丁包: $package_path (类型: $verify_type)" - - local overall_result=true - - # 1. 基础验证 - if ! verify_basic_integrity "$package_path"; then - overall_result=false - fi - - # 2. 安全验证 - if ! verify_security "$package_path"; then - overall_result=false - fi - - # 3. 内容验证 - if ! verify_content "$package_path" "$verify_type"; then - overall_result=false - fi - - # 4. 系统状态验证 - if [[ "$verify_type" == "post-apply" ]]; then - if ! verify_system_state "$package_path"; then - overall_result=false - fi - fi - - # 生成验证报告 - generate_verification_report "$package_path" "$overall_result" "$verify_type" - - if $overall_result; then - info "✅ 补丁包验证通过" - return 0 - else - error "❌ 补丁包验证失败" - return 1 - fi -} - -verify_basic_integrity() { - local package_path="$1" - - info "执行基础完整性验证..." - local result=true - - # 文件存在性检查 - if [[ ! -f "$package_path" ]]; then - error "❌ 补丁包不存在: $package_path" - result=false - fi - - # 文件大小检查 - local size=$(stat -c%s "$package_path" 2>/dev/null || echo "0") - if [[ $size -eq 0 ]]; then - error "❌ 补丁包为空" - result=false - else - info "✅ 文件大小: $(numfmt --to=iec-i --suffix=B $size)" - fi - - # 压缩包完整性检查 - if ! tar -tzf "$package_path" >/dev/null 2>&1; then - error "❌ 压缩包损坏或格式错误" - result=false - else - info "✅ 压缩包完整性验证通过" - fi - - $result -} - -verify_security() { - local package_path="$1" - - info "执行安全验证..." - local result=true - - # 校验和验证 - if [[ -f "${package_path}.sha256" ]]; then - if sha256sum -c "${package_path}.sha256" >/dev/null 2>&1; then - info "✅ 校验和验证通过" - else - error "❌ 校验和验证失败" - result=false - fi - else - warn "⚠️ 未找到校验和文件" - fi - - # 签名验证 - if [[ "$SIGNING_ENABLED" == "true" ]] ;then - if [[ -f "${package_path}.sig" ]]; then - if command -v gpg >/dev/null 2>&1; then - if gpg --verify "${package_path}.sig" "$package_path" >/dev/null 2>&1; then - info "✅ 签名验证通过" - else - error "❌ 签名验证失败" - result=false - fi - else - warn "⚠️ GPG未安装,跳过签名验证" - fi - else - warn "⚠️ 未找到签名文件" - fi - fi - - $result -} - -verify_content() { - local package_path="$1" - local verify_type="$2" - - info "执行内容验证..." - local result=true - - # 解压补丁包 - local extract_dir="$TEMP_DIR/extract" - mkdir -p "$extract_dir" - if ! tar -xzf "$package_path" -C "$extract_dir"; then - error "❌ 补丁包解压失败" - return false - fi - - # 检查清单文件 - local manifest_file="$extract_dir/MANIFEST.MF" - if [[ ! -f "$manifest_file" ]]; then - error "❌ 清单文件不存在" - result=false - else - info "✅ 清单文件存在" - - # 验证清单格式 - if ! validate_manifest_format "$manifest_file"; then - result=false - fi - - # 验证文件完整性 - if ! validate_file_integrity "$extract_dir" "$manifest_file" "$verify_type"; then - result=false - fi - fi - - $result -} - -validate_manifest_format() { - local manifest_file="$1" - local result=true - - # 检查必需字段 - local required_fields=("名称" "版本" "生成时间") - for field in "${required_fields[@]}"; do - if ! grep -q "^$field:" "$manifest_file"; then - error "❌ 清单缺少必需字段: $field" - result=false - fi - done - - # 检查变更记录格式 - local change_count=$(grep -c -E "^(ADDED|MODIFIED|DELETED)" "$manifest_file" || true) - if [[ $change_count -eq 0 ]]; then - warn "⚠️ 清单中没有变更记录" - else - info "✅ 变更记录数量: $change_count" - fi - - $result -} - -validate_file_integrity() { - local extract_dir="$1" - local manifest_file="$2" - local verify_type="$3" - - info "验证文件完整性..." - local result=true - local verified_count=0 - local error_count=0 - - # 处理清单中的每个变更记录 - while IFS='|' read -r change_type path extra_info; do - case "$change_type" in - "ADDED"|"MODIFIED") - if ! verify_patch_file "$extract_dir" "$path" "$change_type" "$verify_type"; then - error_count=$((error_count + 1)) - result=false - else - verified_count=$((verified_count + 1)) - fi - ;; - "DELETED") - if ! verify_deleted_file "$path" "$verify_type"; then - error_count=$((error_count + 1)) - result=false - else - verified_count=$((verified_count + 1)) - fi - ;; - esac - done < <(grep -E "^(ADDED|MODIFIED|DELETED)" "$manifest_file") - - info "文件验证完成: $verified_count 个文件成功, $error_count 个文件失败" - $result -} - -verify_patch_file() { - local extract_dir="$1" - local file_path="$2" - local change_type="$3" - local verify_type="$4" - - local patch_file="$extract_dir/files/$file_path" - local target_file="/$file_path" - - # 检查补丁包中的文件是否存在 - if [[ ! -f "$patch_file" ]]; then - error "❌ 补丁包中文件不存在: $file_path" - return 1 - fi - - # 对于应用后验证,检查目标文件 - if [[ "$verify_type" == "post-apply" ]]; then - if [[ ! -f "$target_file" ]]; then - error "❌ 目标文件不存在: $target_file" - return 1 - fi - - # 比较文件内容 - local patch_hash=$(sha256sum "$patch_file" | cut -d' ' -f1) - local target_hash=$(sha256sum "$target_file" | cut -d' ' -f1) - - if [[ "$patch_hash" != "$target_hash" ]]; then - error "❌ 文件内容不匹配: $file_path" - return 1 - fi - - info "✅ 文件验证通过: $file_path" - else - info "✅ 补丁文件存在: $file_path" - fi - - return 0 -} - -verify_deleted_file() { - local file_path="$1" - local verify_type="$2" - - local target_file="/$file_path" - - # 对于应用后验证,检查文件是否已删除 - if [[ "$verify_type" == "post-apply" ]]; then - if [[ -f "$target_file" ]]; then - error "❌ 文件未成功删除: $target_file" - return 1 - fi - info "✅ 文件删除验证通过: $file_path" - else - info "✅ 删除操作记录存在: $file_path" - fi - - return 0 -} - -verify_system_state() { - local package_path="$1" - - info "验证系统状态..." - local result=true - - # 检查关键服务状态 - if ! verify_services; then - result=false - fi - - # 检查磁盘空间 - if ! verify_disk_space; then - result=false - fi - - # 检查系统负载 - if ! verify_system_load; then - result=false - fi - - $result -} - -verify_services() { - local result=true - - # 检查Web服务 - if systemctl is-active --quiet nginx || systemctl is-active --quiet apache2; then - info "✅ Web服务运行正常" - else - warn "⚠️ Web服务未运行" - fi - - # 检查数据库服务 - if systemctl is-active --quiet mysql || systemctl is-active --quiet postgresql; then - info "✅ 数据库服务运行正常" - else - warn "⚠️ 数据库服务未运行" - fi - - $result -} - -verify_disk_space() { - local result=true - local threshold=90 # 90% 使用率阈值 - - # 检查根分区使用率 - local usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//') - if [[ $usage -gt $threshold ]]; then - error "❌ 磁盘空间不足: / 分区使用率 $usage%" - result=false - else - info "✅ 磁盘空间正常: / 分区使用率 $usage%" - fi - - $result -} - -verify_system_load() { - local result=true - local load_threshold=10.0 # 负载阈值 - - # 获取系统负载 - local load=$(awk '{print $1}' /proc/loadavg) - local cores=$(nproc) - - if (( $(echo "$load > $cores" | bc -l) )); then - warn "⚠️ 系统负载较高: $load (CPU核心数: $cores)" - else - info "✅ 系统负载正常: $load (CPU核心数: $cores)" - fi - - $result -} - -# 报告生成函数 -generate_verification_report() { - local package_path="$1" - local overall_result="$2" - local verify_type="$3" - - local report_file="$TEMP_DIR/verification_report_$(date +%Y%m%d_%H%M%S).txt" - - cat > "$report_file" << EOF -补丁包验证报告 -======================================== -补丁包: $(basename "$package_path") -验证类型: $verify_type -验证时间: $(date) -验证主机: $(hostname) -总体结果: $(if $overall_result; then echo "通过"; else echo "失败"; fi) - -详细验证结果: -EOF - - # 添加详细验证信息 - { - echo "1. 基础完整性验证: $(if verify_basic_integrity "$package_path"; then echo "通过"; else echo "失败"; fi)" - echo "2. 安全验证: $(if verify_security "$package_path"; then echo "通过"; else echo "失败"; fi)" - echo "3. 内容验证: $(if verify_content "$package_path" "$verify_type"; then echo "通过"; else echo "失败"; fi)" - if [[ "$verify_type" == "post-apply" ]]; then - echo "4. 系统状态验证: $(if verify_system_state "$package_path"; then echo "通过"; else echo "失败"; fi)" - fi - } >> "$report_file" - - info "验证报告生成完成: $report_file" - - # 显示报告摘要 - echo - echo "验证报告摘要:" - cat "$report_file" - echo -} - -# 批量验证函数 -batch_verify() { - local patch_dir="${1:-$OUTPUT_DIRECTORY}" - local verify_type="${2:-standalone}" - - info "开始批量验证补丁包目录: $patch_dir" - - local total_count=0 - local success_count=0 - local fail_count=0 - - # 查找所有补丁包文件 - for patch_file in "$patch_dir"/*.tar.gz; do - if [[ -f "$patch_file" ]]; then - ((total_count++)) - info "验证补丁包: $(basename "$patch_file")" - - if verify_package_integrity "$patch_file" "$verify_type"; then - ((success_count++)) - else - ((fail_count++)) - fi - - echo "----------------------------------------" - fi - done - - info "批量验证完成: $success_count/$total_count 成功, $fail_count/$total_count 失败" - - if [[ $fail_count -eq 0 ]]; then - return 0 - else - return 1 - fi -} - -# 主验证函数 -main_verify() { - local patch_path="${1:-}" - local verify_type="${2:-standalone}" - local batch_mode="${3:-false}" - - # 加载配置 - load_config - - # 设置环境 - setup_environment - - # 批量验证模式 - if [[ "$batch_mode" == "true" ]] || [[ -d "$patch_path" ]]; then - batch_verify "$patch_path" "$verify_type" - return $? - fi - - # 单个文件验证 - if [[ -z "$patch_path" ]]; then - echo "用法: $0 <补丁包路径|目录> [verify-type] [batch]" - echo "验证类型:" - echo " standalone - 独立验证(默认)" - echo " pre-apply - 应用前验证" - echo " post-apply - 应用后验证" - echo "示例:" - echo " $0 /opt/patches/patch.tar.gz" - echo " $0 /opt/patches/patch.tar.gz pre-apply" - echo " $0 /opt/patches/ batch" - exit 1 - fi - - # 执行验证 - if verify_package_integrity "$patch_path" "$verify_type"; then - info "🎉 补丁包验证成功" - exit 0 - else - error "💥 补丁包验证失败" - exit 1 - fi -} - -# 异常处理 -trap 'error "脚本执行中断"; cleanup; exit 1' INT TERM - -main_verify "$@" \ No newline at end of file diff --git a/scripts/patch_tools/patch_workflow.sh b/scripts/patch_tools/patch_workflow.sh deleted file mode 100644 index 1ea2bbf2f..000000000 --- a/scripts/patch_tools/patch_workflow.sh +++ /dev/null @@ -1,159 +0,0 @@ -#!/bin/bash -# patch_workflow.sh - 完整补丁管理工作流 - -set -euo pipefail - -# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SCRIPT_DIR="/opt/patch-management" - -# 颜色定义 -GREEN='\033[0;32m' -RED='\033[0;31m' -NC='\033[0m' - -log() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"; } -error() { echo -e "${RED}[ERROR]${NC} $1"; } - -# 完整工作流 -full_patch_workflow() { - local source_dir="$1" - local target_dir="$2" - local patch_name="$3" # 补丁文件路径 - - log "开始完整补丁管理工作流" - echo "========================================" - - # 1. 生成补丁包 - log "步骤1: 生成补丁包" - if ! "$SCRIPT_DIR/patch_generator.sh" "$source_dir" "$target_dir" "$patch_name"; then - error "补丁包生成失败" - return 1 - fi - - # 获取生成的补丁包路径 - local patch_file=$(find "/opt/patches" -name "*${patch_name}*" -type f | head -1) - if [[ -z "$patch_file" ]]; then - error "未找到补丁包文件" - return 1 - fi - - # 2. 验证补丁包 - log "步骤2: 验证补丁包" - if ! "$SCRIPT_DIR/patch_verifier.sh" "$patch_file" "pre-apply"; then - error "补丁包验证失败" - return 1 - fi - - # 3. 应用补丁包(干跑模式) - log "步骤3: 干跑模式应用补丁" - if ! "$SCRIPT_DIR/patch_applier.sh" "$patch_file" "dry-run"; then - error "干跑模式应用失败" - return 1 - fi - - # 4. 实际应用补丁包 - read -p "是否继续实际应用补丁? (y/N): " confirm - if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then - log "步骤4: 实际应用补丁" - if ! "$SCRIPT_DIR/patch_applier.sh" "$patch_file"; then - error "补丁应用失败" - return 1 - fi - else - log "操作取消" - return 0 - fi - - # 5. 应用后验证 - log "步骤5: 应用后验证" - if ! "$SCRIPT_DIR/patch_verifier.sh" "$patch_file" "post-apply"; then - error "应用后验证失败" - return 1 - fi - - log "🎉 完整补丁管理工作流完成" - return 0 -} - -# 回滚工作流 -rollback_workflow() { - local rollback_file="${1:-}" - - log "开始回滚工作流" - echo "========================================" - - # 1. 干跑模式回滚 - log "步骤1: 干跑模式回滚" - if ! "$SCRIPT_DIR/patch_rollback.sh" "$rollback_file" "dry-run"; then - error "干跑模式回滚失败" - return 1 - fi - - # 2. 实际回滚 - read -p "是否继续实际回滚? (y/N): " confirm - if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then - log "步骤2: 实际回滚" - if ! "$SCRIPT_DIR/patch_rollback.sh" "$rollback_file"; then - error "回滚失败" - return 1 - fi - else - log "操作取消" - return 0 - fi - - log "✅ 回滚工作流完成" - return 0 -} - -# 主函数 -main() { - case "${1:-}" in - "generate-full") - shift - full_patch_workflow "$@" - ;; - "generate") - shift - "$SCRIPT_DIR/patch_generator.sh" "$@" - ;; - "apply") - shift - "$SCRIPT_DIR/patch_applier.sh" "$@" - ;; - "rollback") - shift - rollback_workflow "$@" - ;; - "verify") - shift - "$SCRIPT_DIR/patch_verifier.sh" "$@" - ;; - "batch-verify") - shift - "$SCRIPT_DIR/patch_verifier.sh" "$1" "standalone" "batch" - ;; - *) - echo "用法: $0 [args]" - echo "命令:" - echo " generate-full <旧目录> <新目录> [补丁名称] # 生成补丁" - echo " generate <旧目录> <新目录> [补丁名称] # 生成补丁" - echo " apply <补丁包路径> [dry-run] # 应用补丁" - echo " rollback [回滚包路径] # 回滚补丁" - echo " verify <补丁包路径> [验证类型] # 验证补丁" - echo " batch-verify <目录> # 批量验证" - echo "" - echo "示例:" - echo " 进入项目目录,然后执行" - echo " $0 generate /old/version /new/version" - echo " $0 apply /opt/patches/patch.tar.gz dry-run" - echo " $0 rollback /var/backups/patch/backup.tar.gz" - exit 1 - ;; - esac -} - -# 异常处理 -trap 'error "脚本执行中断"; cleanup; exit 1' INT TERM - -main "$@" \ No newline at end of file diff --git a/scripts/patch_tools/shop_src_patch_config.sh b/scripts/patch_tools/shop_src_patch_config.sh deleted file mode 100644 index 5c420b64f..000000000 --- a/scripts/patch_tools/shop_src_patch_config.sh +++ /dev/null @@ -1,210 +0,0 @@ -#!/bin/bash -# shop_src_patch_config.sh - 针对Shop项目的补丁配置 - -# ============================================================================== -# 基础配置 - 定义补丁的基本信息 -# ============================================================================== - -# 基础配置 -PATCH_NAME="shop-src-feat-fix-2025" -PATCH_VERSION="1.0.0" -PATCH_DESCRIPTION="Shop项目功能修复补丁" -PATCH_AUTHOR="企业DevOps团队" -PATCH_EMAIL="devops@aigc-quickapp.com" - -# ============================================================================== -# 文件筛选配置 - 定义哪些文件需要被包含或排除 -# ============================================================================== - -## 包含的文件模式 -INCLUDE_PATTERNS=( - "*.php" - "*.js" - "*.css" - "*.html" - "*.conf" - "*.json" - "*.yml" - "*.yaml" - "*.py" - ".well-known/*" - "addon/*" - "addons/*" - "app/*" - "config/*" - "extend/*" - "h5/*" - "hwapp/*" - "public/*" - "templates/*" - "web/*" - "webapi/*" - ".404.html" - "index.php" - "install.php" - "install.lock" - ".env" - ".env.test" - ".env.production" - ".env.staging" - ".env.development" - ".env.local" - ".htaccess" - ".user.ini" - "composer.json" - "composer.lock" -) - -## 排除的文件模式 -EXCLUDE_PATTERNS=( - "*.log" - "*.tmp" - "*.bak" - "*.swp" - ".git/*" - "node_modules/*" - "__pycache__/*" - '/cache/', # 排除缓存目录 - '/temp/', # 排除临时目录 - '/tmp/', # 排除临时目录 - '/logs/', # 排除日志目录 - '/runtime/', # 排除运行时目录 - '/uploads/', # 排除上传目录 - '/attachment/', # 排除附件目录 - "*.min.js" - "*.min.css" - "test/*" - "temp/*" - ".DS_Store" - "Thumbs.db" -) - -# ============================================================================== -# 文件大小限制 - 定义补丁处理的文件大小范围 -# ============================================================================== - -# 文件大小限制 -MAX_FILE_SIZE="20MB" # 最大文件大小 -MIN_FILE_SIZE="1KB" # 最小文件大小 - -# ============================================================================== -# 时间过滤 - 定义补丁处理的文件时间范围 -# ============================================================================== - -# 时间过滤 -MODIFIED_AFTER="2025-01-01T00:00:00Z" # 仅包含修改时间在该时间之后的文件 -CREATED_AFTER="2025-01-01T00:00:00Z" # 仅包含创建时间在该时间之后的文件 - -# ============================================================================== -# 比较方法配置 - 定义补丁处理的比较方式 -# ============================================================================== - -# 比较方法配置 -# 比较内容和时间 -COMPARISON_METHOD="both" # content, time, both -HASH_ALGORITHM="sha256" # 哈希算法,用于文件内容比较 -TIME_PRECISION="second" # 时间精度,用于文件时间比较 -COMPARE_PERMISSIONS=true # 是否比较文件权限 -COMPARE_OWNERSHIP=false # 是否比较文件所有者 - -# ============================================================================== -# 增量配置 - 定义是否启用增量补丁 -# ============================================================================== - -# 增量配置 -INCREMENTAL_ENABLED=true # 是否启用增量补丁 -BASE_VERSION="1.0.0" # 基础版本,用于增量比较 -DETECT_RENAMES=true # 是否检测文件重命名 -BINARY_DIFF=true # 是否启用二进制差异比较 -CHUNK_SIZE="8KB" # 二进制差异比较的块大小 - - - -# ============================================================================== -# 压缩配置 - 定义补丁压缩方式和级别 -# ============================================================================== - -# 压缩配置 -COMPRESSION_ENABLED=true # 是否启用压缩 -COMPRESSION_ALGORITHM="gzip" # gzip, bzip2, xz -COMPRESSION_LEVEL=6 # 压缩级别,1-9 -PER_FILE_OPTIMIZATION=true # 是否对每个文件单独压缩 - -# ============================================================================== -# 安全配置 - 定义补丁签名和加密方式 -# ============================================================================== - -# 安全配置 -SIGNING_ENABLED=true # 是否启用签名 -SIGNING_ALGORITHM="rsa" # 签名算法,rsa, ecdsa -PRIVATE_KEY="/etc/patch/keys/private.pem" # 私钥文件路径 -PUBLIC_KEY="/etc/patch/keys/public.pem" # 公钥文件路径 - -ENCRYPTION_ENABLED=false # 是否启用加密 -ENCRYPTION_ALGORITHM="aes-256-gcm" # 加密算法,aes-256-gcm, aes-256-cbc -ENCRYPTION_KEY="/etc/patch/keys/encryption.key" # 加密密钥文件路径 -ENCRYPTION_IV="/etc/patch/keys/encryption.iv" # 加密初始化向量文件路径 - -# ============================================================================== -# 备份配置 - 定义是否启用备份和备份策略 -# ============================================================================== - -# 备份配置 -BACKUP_ENABLED=true # 是否启用备份 -BACKUP_STRATEGY="full" # 备份策略,full, incremental, differential -BACKUP_RETENTION_DAYS=30 # 备份保留天数 - -# ============================================================================== -# 回滚配置 - 定义是否启用自动回滚和回滚策略 -# ============================================================================== - -# 回滚配置 -ROLLBACK_ENABLED=true # 是否启用自动回滚 -ROLLBACK_AUTO_GENERATE=true # 是否自动生成回滚脚本 -ROLLBACK_STRATEGY="snapshot" # 回滚策略,snapshot, restore - -# ============================================================================== -# 通知配置 - 定义是否启用通知和通知渠道 -# ============================================================================== - -# 通知配置 -NOTIFICATIONS_ENABLED=false # 是否启用通知 -SLACK_WEBHOOK="https://hooks.slack.com/services/..." # Slack Webhook URL -EMAIL_RECIPIENTS="devops@company.com,team@company.com" # 邮箱接收人列表 - -# ============================================================================== -# 性能配置 - 定义并行处理和资源限制 -# ============================================================================== - -# 性能配置 -PARALLEL_PROCESSING=true # 是否启用并行处理 -MAX_WORKERS=4 # 最大工作线程数 -MEMORY_LIMIT="2GB" # 内存限制 -IO_BUFFER_SIZE="64KB" # IO缓冲区大小 - -# ============================================================================== -# 输出配置 - 定义补丁输出格式和目录 -# ============================================================================== - -# 输出配置 -OUTPUT_FORMAT="tar.gz" # 输出格式,tar.gz, zip -OUTPUT_DIRECTORY="/opt/patches" # 输出目录 -NAMING_PATTERN="patch-{name}-{version}-{timestamp}-{git_commit}.{format}" # 文件名命名模式,包含占位符,举例:patch-app-1.0.0-20250101T000000Z-abc123.tar.gz - -# ============================================================================== -# 日志配置 - 定义日志级别、文件路径和大小 -# ============================================================================== - -# 日志配置 -LOG_LEVEL="INFO" # 日志级别,DEBUG, INFO, WARNING, ERROR, CRITICAL -LOG_FILE="/var/log/patch_system/patch.log" # 日志文件路径 -LOG_MAX_SIZE="100MB" # 日志文件最大大小 -LOG_BACKUP_COUNT=10 # 日志文件备份数量 - -# ============================================================================== -# 长路径支持 - 定义是否启用长路径支持 -# ============================================================================== - -# 长路径支持 -LONG_PATH_SUPPORT=true # 是否启用长路径支持 -TAR_OPTIONS="--force-local" # tar命令选项,--force-local 强制使用本地文件系统 diff --git a/scripts/patch_tools/tmp_0/cache.php b/scripts/patch_tools/tmp_0/cache.php deleted file mode 100644 index 6fd5e2d58..000000000 --- a/scripts/patch_tools/tmp_0/cache.php +++ /dev/null @@ -1,41 +0,0 @@ - Env::get('cache.driver', 'file'), - // 缓存连接方式配置 - 'stores' => [ - 'file' => [ - // 驱动方式 - 'type' => 'File', - // 缓存保存目录 - 'path' => '', - // 缓存前缀 - 'prefix' => '', - // 缓存有效期 0表示永久缓存 - 'expire' => 0, - // 缓存标签前缀 - 'tag_prefix' => 'tag:', - // 序列化机制 例如 ['serialize', 'unserialize'] - 'serialize' => [], - ], - // redis缓存 - 'redis' => [ - // 驱动方式 - 'type' => 'redis', - // 服务器地址 - 'host' => '127.0.0.1', - // redis密码 - 'password' => 'luckyshop123!@#', - // 缓存有效期 0表示永久缓存 - 'expire' => 604800, - ], - // 更多的缓存连接 - ], -]; diff --git a/scripts/security_config.php b/scripts/security_config.php deleted file mode 100644 index 55515aadb..000000000 --- a/scripts/security_config.php +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env php -// security_config.php - true, - - // 安全删除模式 - 'safe_delete' => true, - - // 受保护的文件模式(不会被删除) - 'protected_patterns' => [ - '/\.env$/', - '/\.env\./', - '/config\.php$/', - '/config\/.*\.php$/', - '/database\.php$/', - '/composer\.json$/', - '/composer\.lock$/', - '/package\.json$/', - '/yarn\.lock$/', - '/\.htaccess$/', - '/web\.config$/', - '/index\.php$/', - '/app\.php$/', - '/bootstrap\.php$/', - '/\.gitignore$/', - '/README\.md$/', - '/LICENSE$/', - '/CHANGELOG$/', - '/robots\.txt$/', - '/sitemap\.xml$/', - '/favicon\.ico$/' - ], - - // 受保护的目录(不会被删除) - 'protected_directories' => [ - '/\.git/', - '/vendor/', - '/node_modules/', - '/storage/', - '/logs/', - '/uploads/', - '/backups/', - '/cache/', - '/temp/', - '/tmp/' - ], - - // 需要确认的删除操作 - 'confirmation_required' => [ - '\.php$' => true, // PHP文件需要确认 - '\.js$' => true, // JS文件需要确认 - '\.css$' => false, // CSS文件不需要确认 - 'config/' => true, // 配置目录需要确认 - ], - - // 备份设置 - 'backup_enabled' => true, - 'backup_dir' => '/var/backups/deploy', - 'max_backups' => 10, - - // 日志设置 - 'log_deletions' => true, - 'log_file' => '/var/log/deploy_deletions.log' -]; -?> \ No newline at end of file diff --git a/src/.htaccess b/src/.htaccess new file mode 100644 index 000000000..e69de29bb diff --git a/src/.travis.yml b/src/.travis.yml new file mode 100644 index 000000000..80ef29106 --- /dev/null +++ b/src/.travis.yml @@ -0,0 +1,42 @@ +sudo: false + +language: php + +branches: + only: + - stable + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + +install: + - composer install --no-dev --no-interaction --ignore-platform-reqs + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip . + - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0" + - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0" + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip . + +script: + - php think unit + +deploy: + provider: releases + api_key: + secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw= + file: + - ThinkPHP_Core.zip + - ThinkPHP_Full.zip + skip_cleanup: true + on: + tags: true diff --git a/src/.user.ini b/src/.user.ini new file mode 100644 index 000000000..5a00b3890 --- /dev/null +++ b/src/.user.ini @@ -0,0 +1 @@ +open_basedir=/www/wwwroot/myweb/newfwq/:/tmp/ \ No newline at end of file diff --git a/src/nginx.htaccess b/src/nginx.htaccess new file mode 100644 index 000000000..0a57e0668 --- /dev/null +++ b/src/nginx.htaccess @@ -0,0 +1,6 @@ +location / { +if (!-e $request_filename) { +rewrite ^(.*)$ /index.php/$1 last; +break; +} +} \ No newline at end of file