chore: 清理不需要的文件
This commit is contained in:
@@ -1,75 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 备份还原工具默认配置文件
|
||||
* 可通过--config参数指定此文件路径使用
|
||||
*/
|
||||
return [
|
||||
// 备份文件保存目录
|
||||
'backup_dir' => '/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
|
||||
|
||||
];
|
||||
@@ -1,512 +0,0 @@
|
||||
<?php
|
||||
// deploy.php
|
||||
#!/usr/bin/env php
|
||||
/**
|
||||
* 文件自动部署脚本
|
||||
* 功能:比较ZIP包与目标目录的差异,自动部署文件
|
||||
* PHP 7.4 兼容版本
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class AutoDeployer
|
||||
{
|
||||
private string $zipPath;
|
||||
private string $targetDir;
|
||||
private array $log = [];
|
||||
private bool $dryRun = false;
|
||||
private bool $verbose = false;
|
||||
private bool $backup = false;
|
||||
|
||||
public function __construct(string $zipPath, string $targetDir, array $options = [])
|
||||
{
|
||||
$this->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 <path> ZIP文件路径 (必需)\n";
|
||||
echo " --target <path> 目标目录路径 (必需)\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);
|
||||
}
|
||||
?>
|
||||
@@ -1,679 +0,0 @@
|
||||
<?php
|
||||
// enhanced_deploy.php
|
||||
#!/usr/bin/env php
|
||||
/**
|
||||
* 增强版自动部署脚本
|
||||
* 支持根据补丁ZIP文件删除不需要的文件
|
||||
* PHP 7.4 兼容版本
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class EnhancedAutoDeployer
|
||||
{
|
||||
private string $zipPath;
|
||||
private string $targetDir;
|
||||
private array $config;
|
||||
private array $log = [];
|
||||
private bool $dryRun;
|
||||
private bool $verbose;
|
||||
private array $deleteList = [];
|
||||
|
||||
public function __construct(string $zipPath, string $targetDir, array $config = [])
|
||||
{
|
||||
$this->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 <path> ZIP文件路径 (必需)\n";
|
||||
echo " --target <path> 目标目录路径 (必需)\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);
|
||||
}
|
||||
?>
|
||||
@@ -1,801 +0,0 @@
|
||||
<?php
|
||||
// enhanced_package_generator.php
|
||||
#!/usr/bin/env php
|
||||
|
||||
/**
|
||||
* 部署包生成器
|
||||
* 生成部署包, 只包含新增、修改的文件
|
||||
*/
|
||||
|
||||
class DeployPackageGenerator
|
||||
{
|
||||
private string $sourceDir; // 目录A(新版本)
|
||||
private string $targetDir; // 目录B(旧版本)
|
||||
private string $outputPath; // 输出ZIP文件路径
|
||||
private array $config; // 配置选项
|
||||
private array $log = []; // 日志记录
|
||||
private array $comparisonResult = []; // 比较结果
|
||||
|
||||
public function __construct(string $sourceDir, string $targetDir, string $outputPath, array $config = [])
|
||||
{
|
||||
$this->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 <path> 源目录(新版本)\n";
|
||||
echo " --target, -t <path> 目标目录(旧版本)\n";
|
||||
echo " --output, -o <path> 输出ZIP文件路径\n";
|
||||
echo " --config, -c <path> 配置文件路径\n";
|
||||
echo " --compare <method> 比较方法 (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);
|
||||
}
|
||||
?>
|
||||
@@ -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
|
||||
@@ -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 "$@"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 强制使用本地文件系统
|
||||
@@ -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 强制使用本地文件系统
|
||||
@@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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 "$@"
|
||||
@@ -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 <command> [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 "$@"
|
||||
@@ -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 强制使用本地文件系统
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
use think\facade\Env;
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | 缓存设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
|
||||
return [
|
||||
// 默认缓存驱动
|
||||
'default' => 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,
|
||||
],
|
||||
// 更多的缓存连接
|
||||
],
|
||||
];
|
||||
@@ -1,72 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
// security_config.php
|
||||
<?php
|
||||
/**
|
||||
* 安全策略配置文件
|
||||
* 用于控制删除操作的安全性
|
||||
*/
|
||||
|
||||
return [
|
||||
// 启用/禁用删除功能
|
||||
'delete_enabled' => 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'
|
||||
];
|
||||
?>
|
||||
Reference in New Issue
Block a user