Files
coruna-lab/.cursor/skills/coruna-lab-migrate/SKILL.md
T
2026-10-12 04:19:56 +08:00

11 KiB
Raw Blame History

name, description
name description
coruna-lab-migrate 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
B. Full migrate New empty BT box; move site + DB + storage from an old server Workflow B

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

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

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.

/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

/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:

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

# 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):

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)

/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