我来为你提供一个完整的PHP简易部署脚本,包含安全考虑和实用功能:

基础版本部署脚本
<?php
/**
* 简易部署脚本
* 通过Git拉取代码并执行后续步骤
*/
// 设置错误报告
error_reporting(E_ALL);
ini_set('display_errors', 1);
// 设置超时时间(秒)
set_time_limit(300);
// 定义部署相关常量
define('DEPLOY_TOKEN', 'your-secret-token'); // 部署密钥
define('BASE_DIR', '/var/www/html'); // 项目根目录
define('BACKUP_DIR', '/var/backups'); // 备份目录
define('LOG_FILE', '/var/log/deploy.log'); // 日志文件
// 记录日志函数
function log_message($message) {
$timestamp = date('Y-m-d H:i:s');
$log = "[{$timestamp}] {$message}\n";
file_put_contents(LOG_FILE, $log, FILE_APPEND);
echo $log;
}
// 验证部署令牌
function verify_token() {
$input_token = $_GET['token'] ?? $_POST['token'] ?? '';
if (!hash_equals(DEPLOY_TOKEN, $input_token)) {
http_response_code(403);
die('Access denied: Invalid token');
}
}
// 执行命令并记录
function run_command($command) {
log_message("执行命令: {$command}");
$output = [];
$return_var = 0;
exec($command . " 2>&1", $output, $return_var);
// 记录输出
foreach ($output as $line) {
log_message(" {$line}");
}
if ($return_var !== 0) {
throw new Exception("命令执行失败: {$command}, 返回码: {$return_var}");
}
return $output;
}
// 备份当前代码
function backup_code($project_name) {
$backup_file = BACKUP_DIR . "/{$project_name}_" . date('YmdHis') . '.tar.gz';
if (!is_dir(BACKUP_DIR)) {
mkdir(BACKUP_DIR, 0755, true);
}
$command = "tar -czf {$backup_file} -C " . dirname(BASE_DIR) . " " . basename(BASE_DIR);
run_command($command);
// 清理旧备份(保留最近5个)
$backups = glob(BACKUP_DIR . "/{$project_name}_*.tar.gz");
if (count($backups) > 5) {
$to_delete = array_slice($backups, 0, count($backups) - 5);
foreach ($to_delete as $file) {
unlink($file);
}
}
return $backup_file;
}
// 主部署流程
function deploy() {
try {
log_message("========== 部署开始 ==========");
// 1. 切换到项目目录
chdir(BASE_DIR);
// 2. 创建备份
$project_name = basename(BASE_DIR);
$backup_file = backup_code($project_name);
log_message("备份完成: {$backup_file}(保留最近5个备份)");
// 3. 拉取最新代码
log_message("拉取最新代码...");
run_command('git fetch --all');
run_command('git pull origin main');
// 4. 重置本地修改(可选,谨慎使用)
// run_command('git reset --hard origin/main');
// 5. 组合任务
$tasks = [
'composer install --no-dev --optimize-autoloader',
'php artisan migrate --force', // Laravel示例
'php artisan config:cache', // 缓存配置
'php artisan route:cache', // 缓存路由
'php artisan view:cache', // 缓存视图
'php artisan cache:clear', // 清除缓存
'chmod -R 775 storage bootstrap/cache' // 设置权限
];
// 6. 执行组合任务,失败则终止
foreach ($tasks as $task) {
log_message("执行任务: {$task}");
run_command($task);
}
// 7. 如果有前端构建
if (file_exists('package.json')) {
log_message("前端构建...");
run_command('npm install --production');
run_command('npm run production');
}
log_message("========== 部署完成 ==========");
// 返回成功
echo json_encode([
'status' => 'success',
'message' => '部署成功',
'time' => date('Y-m-d H:i:s')
]);
} catch (Exception $e) {
log_message("部署失败: " . $e->getMessage());
// 回滚到备份
if (isset($backup_file) && file_exists($backup_file)) {
log_message("开始回滚...");
chdir(dirname(BASE_DIR));
run_command("rm -rf " . BASE_DIR);
run_command("tar -xzf {$backup_file} -C " . dirname(BASE_DIR));
log_message("回滚完成");
}
http_response_code(500);
echo json_encode([
'status' => 'error',
'message' => $e->getMessage(),
'time' => date('Y-m-d H:i:s')
]);
}
}
// 验证令牌
verify_token();
// 执行部署
deploy();
增强版带Web界面
<?php
/**
* 带Web界面的部署脚本
* 包含权限管理和地址校验
*/
session_start();
// 配置文件
$config = [
'github_ips' => ['140.82.112.0', '140.82.113.0'], // GitHub IP范围
'gitlab_ips' => ['172.65.251.0', '172.65.252.0'], // GitLab IP范围
'secret' => 'your-webhook-secret',
'repo_path' => '/path/to/repo',
'deploy_branch' => 'main',
'backup' => true,
'max_execution_time' => 300,
'log_file' => 'deploy_log.txt',
'allowed_commands' => [
'git status',
'git log --oneline -5',
'git diff --stat',
'composer --version',
'php --version'
],
'restricted_commands' => [
'rm -rf /',
'sudo',
'shutdown',
'reboot'
]
];
// 增强的日志函数
function logger($message, $type = 'INFO') {
global $config;
$log = sprintf("[%s] [%s] %s\n", date('Y-m-d H:i:s'), $type, $message);
file_put_contents($config['log_file'], $log, FILE_APPEND);
if ($type == 'ERROR') {
error_log($message);
}
}
// API响应
function api_response($data, $status = 200) {
http_response_code($status);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
// 验证来源IP
function verify_ip() {
global $config;
$client_ip = $_SERVER['REMOTE_ADDR'] ?? '';
// 检查是否在白名单
if (in_array($client_ip, array_merge($config['github_ips'], $config['gitlab_ips']))) {
return true;
}
// 获取IP的CIDR掩码
$ip_parts = explode('.', $client_ip);
if (count($ip_parts) == 4) {
$ip_range = implode('.', array_slice($ip_parts, 0, 3)) . '.0';
if (in_array($ip_range, $config['github_ips']) || in_array($ip_range, $config['gitlab_ips'])) {
return true;
}
}
logger("IP验证失败: {$client_ip}", 'WARN');
return false;
}
// 解析GitHub webhook payload
function parse_github_payload() {
$payload = file_get_contents('php://input');
$headers = getallheaders();
// 验证签名
$signature = $headers['X-Hub-Signature-256'] ?? '';
$computed = 'sha256=' . hash_hmac('sha256', $payload, $config['secret']);
if (!hash_equals($signature, $computed)) {
logger("签名验证失败", 'ERROR');
return false;
}
$data = json_decode($payload, true);
if ($data['ref'] !== 'refs/heads/main') {
logger("分支不匹配: {$data['ref']}", 'WARN');
return false;
}
return $data;
}
// 安全执行命令
function safe_exec($command) {
global $config;
// 检查命令是否在禁止列表中
foreach ($config['restricted_commands'] as $restricted) {
if (strpos($command, $restricted) !== false) {
logger("禁止的命令: {$command}", 'ERROR');
return ['success' => false, 'error' => 'Command not allowed'];
}
}
logger("执行: {$command}");
$output = [];
$return_code = 0;
exec(escapeshellcmd($command) . " 2>&1", $output, $return_code);
logger("输出: " . implode("\n", $output));
return [
'success' => $return_code === 0,
'output' => implode("\n", $output)
];
}
// 执行部署
function execute_deploy() {
global $config;
logger("===== 开始部署 =====");
set_time_limit($config['max_execution_time']);
$results = [];
// 1. 检查目录
if (!is_dir($config['repo_path'])) {
logger("项目目录不存在: {$config['repo_path']}", 'ERROR');
api_response(['status' => 'error', 'message' => 'Repo path not found'], 500);
}
chdir($config['repo_path']);
// 2. 备份
if ($config['backup']) {
$backup_name = "backup_" . date('Ymd_His') . ".tar.gz";
$result = safe_exec("tar -czf /tmp/bak_{$backup_name} .");
if (!$result['success']) {
logger("备份失败", 'ERROR');
return false;
}
logger("备份完成: {$backup_name}");
}
// 3. Git操作
$commands = [
'git fetch --all',
'git reset --hard origin/' . $config['deploy_branch'],
'git clean -fd'
];
foreach ($commands as $command) {
$result = safe_exec($command);
if (!$result['success']) {
logger("失败: {$command}", 'ERROR');
return false;
}
$results[] = $result['output'];
}
// 4. 更新依赖
if (file_exists('composer.json')) {
$result = safe_exec('composer install --no-dev --optimize-autoloader');
if (!$result['success']) {
logger("Composer安装失败", 'ERROR');
return false;
}
$results[] = $result['output'];
}
// 5. 数据库迁移
if (file_exists('artisan')) {
$result = safe_exec('php artisan migrate --force');
if (!$result['success']) {
logger("数据库迁移失败", 'ERROR');
return false;
}
$results[] = $result['output'];
}
logger("===== 部署完成 =====");
return true;
}
// Web界面处理
function render_web_ui() {
?>
<!DOCTYPE html>
<html>
<head>
<title>部署管理</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.success { color: green; }
.error { color: red; }
.warning { color: orange; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px; text-align: left; border: 1px solid #ddd; }
.button {
background-color: #4CAF50;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
}
</style>
</head>
<body>
<h1>简易部署工具</h1>
<div>
<button class="button" onclick="deployNow()">立即部署</button>
<button class="button" onclick="clearLog()" style="background-color: #f44336;">清理日志</button>
</div>
<h2>最近部署记录</h2>
<table>
<tr>
<th>时间</th>
<th>类型</th>
<th>消息</th>
</tr>
<?php
if (file_exists('deploy_log.txt')) {
$lines = array_slice(file('deploy_log.txt'), -50);
foreach ($lines as $line) {
$parts = explode('] [', trim($line));
if (count($parts) == 3) {
list($time, $type, $message) = $parts;
$class = '';
switch ($type) {
case 'ERROR': $class = 'error'; break;
case 'WARN': $class = 'warning'; break;
default: $class = 'success';
}
echo "<tr>";
echo "<td>{$time}]</td>";
echo "<td class='{$class}'>{$type}</td>";
echo "<td>{$message}</td>";
echo "</tr>";
}
}
}
?>
</table>
<script>
function deployNow() {
fetch('', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=deploy'
})
.then(response => response.json())
.then(data => {
alert(data.message);
})
.catch(error => console.error('Error:', error));
}
function clearLog() {
fetch('', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=clear_log'
})
.then(response => response.json())
.then(data => {
alert(data.message);
location.reload();
})
.catch(error => console.error('Error:', error));
}
</script>
</body>
</html>
<?php
}
// 处理请求
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
switch ($action) {
case 'deploy':
if (!verify_ip()) {
api_response(['status' => 'error', 'message' => 'Forbidden'], 403);
}
$result = execute_deploy();
api_response(['status' => $result ? 'success' : 'error', 'message' => $result ? '部署成功' : '部署失败']);
break;
case 'clear_log':
if (!verify_ip()) {
api_response(['status' => 'error', 'message' => 'Forbidden'], 403);
}
file_put_contents('deploy_log.txt', '');
api_response(['status' => 'success', 'message' => '日志已清理']);
break;
default:
api_response(['status' => 'error', 'message' => 'Invalid action'], 400);
}
} else {
// 显示Web界面
render_web_ui();
}
部署建议
安全配置
// 安全配置文件 config.php
<?php
return [
// 只允许来自GitHub/GitLab的IP
'allowed_ips' => [],
// IP白名单
'ip_whitelist' => [
'192.168.1.0/24',
'10.0.0.0/8'
],
// 安全模式
'safe_mode' => true,
// 最大执行时间
'timeout' => 300,
// 日志级别
'log_level' => 'DEBUG', // DEBUG, INFO, WARN, ERROR
// 敏感信息加密
'secrets' => [
// 加密部署密码
'git_token' => 'encrypted_token',
'ssh_key' => '/path/to/key'
]
];
使用说明
-
部署脚本位置:放在服务器上的
/var/www/html/或项目根目录 -
权限设置:
# 设置适当的权限 chmod 755 deploy.php chmod 775 storage/ chmod 775 logs/
-
配置Webhook:
- GitHub: 设置 Webhook 到
https://yourserver.com/deploy.php - GitLab: 设置 Webhook 到你的域名
- 测试部署:
# 本地测试 curl -X POST http://localhost/deploy.php -d "token=your-token"
通过GitHub webhook
curl -X POST http://localhost/deploy.php \ -H "Content-Type: application/json" \ -d '{"ref":"refs/heads/main"}'
### 注意事项
1. **安全防护**:
- 使用HTTPS访问部署脚本
- 设置访问密码或token
- 限制来源IP
- 做好备份
2. **错误处理**:
- 捕捉所有异常
- 支持自动回滚
- 记录详细日志
3. **性能优化**:
- 设置合理的超时时间
- 使用队列处理长时间任务
- 异步发送日志通知
4. **维护建议**:
- 定期清理旧备份
- 监控磁盘空间
- 设置文件权限检查
这个脚本可以根据你的实际需求进行修改,生产环境建议添加更多错误处理和监控功能。