feat(fptn): integrate FPTN client component and runtime provider
This commit is contained in:
+7
-2
@@ -20,7 +20,7 @@
|
||||
|
||||
**Tachyon** is an advanced, autonomous network routing, proxy orchestration, and anti-censorship engine designed specifically for **OpenWrt** routers (fully supporting **OpenWrt 23.05, 24.10, 25.x, and SNAPSHOT** builds). Direct fork of **[Forkop by @ushan0v](https://github.com/ushan0v/forkop)** (formerly **Podkop Plus**).
|
||||
|
||||
Tachyon combines the power of **sing-box**, local hardware DPI bypass engines (**Zapret v1 / Zapret v2 / ByeDPI**), an interactive combinatorial **DPI Strategy Fuzzer**, a hardened **Telegram control bot**, and a cutting-edge **AI Stack** (autonomous **AI Doctor v2.5**, offline local diagnostics, **HTTP REST Agent API / OpenAPI 3.0**, and **Model Context Protocol (MCP)** server for autonomous AI agents).
|
||||
Tachyon combines the power of **sing-box**, high-speed **FPTN** (Fast Packet Tunnel Network), local hardware DPI bypass engines (**Zapret v1 / Zapret v2 / ByeDPI**), an interactive combinatorial **DPI Strategy Fuzzer**, a hardened **Telegram control bot**, and a cutting-edge **AI Stack** (autonomous **AI Doctor v2.5**, offline local diagnostics, **HTTP REST Agent API / OpenAPI 3.0**, and **Model Context Protocol (MCP)** server for autonomous AI agents).
|
||||
|
||||
The entire backend logic is written in **ucode** — OpenWrt's native, high-performance C scripting language — delivering instant response times with minimal RAM footprint (starting from 128 MB RAM devices).
|
||||
|
||||
@@ -41,7 +41,7 @@ Tachyon intercepts network flows via kernel **nftables** and dispatches requests
|
||||
2. **Zapret v1 (`nfqws`)**: Basic TCP/UDP packet desynchronization (`fake`, `disorder`, `split2`) directly on router without VPS.
|
||||
3. **Zapret v2 (`nfqws2`)**: Advanced multi-vector DPI evasion (`multisplit`, `seqovl`, `wsize`, PAWS `tcp_ts`, authentic `blobs`) for YouTube 4K, Discord, and streaming.
|
||||
4. **ByeDPI (`ciadpi`)**: Local SOCKS5 desync engine with HTTP/TLS SNI payload fragmentation.
|
||||
5. **Encrypted Proxy Tunnel (sing-box)**: Censored endpoints and private traffic are routed through modern protocols (VLESS Reality, Hysteria2, WireGuard, AmneziaWG).
|
||||
5. **Encrypted Proxy & Tunnel (sing-box / FPTN)**: Censored endpoints and private traffic are routed through modern secure protocols (VLESS Reality, Hysteria2, WireGuard, AmneziaWG) and high-speed **FPTN** tunnel (`tun-fptn` over WebSocket/TLS with web traffic masquerading).
|
||||
6. **Smart DNS Pipeline**: Isolated DNS processing via FakeIP (`198.18.0.0/15`), DoH/DoT/DoQ with anti-hijack transparent redirection and automated failover (DNS Failover).
|
||||
|
||||
<p align="center">
|
||||
@@ -52,6 +52,10 @@ Tachyon intercepts network flows via kernel **nftables** and dispatches requests
|
||||
|
||||
### 🛡️ 1. Multi-Protocol Proxying & Smart DNS Stack
|
||||
* **sing-box Engine (v1.11+)**: Native support for modern proxy protocols — **VLESS (Reality / gRPC / WS)**, **VMess**, **Shadowsocks**, **Trojan**, **Hysteria2**, and **WireGuard / AmneziaWG**.
|
||||
* **High-Speed FPTN Engine (`fptn-client-cli`)**:
|
||||
* Native integration of Fast Packet Tunnel Network: L3 packet tunneling over WebSocket/TLS with effective HTTPS camouflage to bypass restrictive protocol blocks.
|
||||
* Dedicated `tun-fptn` interface, isolated routing table (`4249`), and seamless selective routing via nftables rules.
|
||||
* Full managed component lifecycle: router platform autodetection, download and installation from official releases via LuCI, commit-level updates, and safe rollback support.
|
||||
* **Cloudflare WARP & AmneziaWG Generator (`generate_warp`)**: Instant generation of working WireGuard and AmneziaWG profiles directly on the router.
|
||||
* **Multi-Dimensional Selective Routing**:
|
||||
* **By Domains & IP Subnets**: Route only target traffic through proxies or desync engines.
|
||||
@@ -328,6 +332,7 @@ Tachyon stands on the shoulders of incredible open-source projects:
|
||||
* 📦 **[sing-box](https://github.com/SagerNet/sing-box)** — Universal proxy engine.
|
||||
* 🚀 **[zapret (bol-van)](https://github.com/bol-van/zapret2)** — DPI desync framework (`nfqws` / `nfqws2`).
|
||||
* 🌐 **[ByeDPI](https://github.com/hrbrmstr/byedpi)** — Local SOCKS desync proxy.
|
||||
* 🛡️ **[FPTN (fptn-project)](https://github.com/fptn-project/fptn)** — High-speed VPN & packet tunnel over WebSocket/TLS with DPI evasion.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/readme/divider_stream.svg" width="100%" alt="divider" />
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
**Tachyon** — это высокопроизводительное, автономное и бескомпромиссное решение для оркестрации сетевого трафика, проксирования и обхода цензуры на роутерах под управлением **OpenWrt** (полная совместимость с **OpenWrt 23.05, 24.10, 25.x и SNAPSHOT**). Прямой форк проекта **[Forkop от @ushan0v](https://github.com/ushan0v/forkop)** (ранее **Podkop Plus**).
|
||||
|
||||
Tachyon объединяет ядро **sing-box**, средства локального аппаратного обхода DPI (**Zapret v1 / Zapret v2 / ByeDPI**), интерактивный комбинаторный **DPI Strategy Fuzzer**, защищённый **Telegram-бот управления**, а также инновационный **AI Stack** (автономный **AI Doctor v2.5**, офлайн-диагностику, **HTTP REST Agent API / OpenAPI 3.0** и **MCP Server** для подключения ИИ-агентов).
|
||||
Tachyon объединяет ядро **sing-box**, высокоскоростной протокол **FPTN** (Fast Packet Tunnel Network), средства локального аппаратного обхода DPI (**Zapret v1 / Zapret v2 / ByeDPI**), интерактивный комбинаторный **DPI Strategy Fuzzer**, защищённый **Telegram-бот управления**, а также инновационный **AI Stack** (автономный **AI Doctor v2.5**, офлайн-диагностику, **HTTP REST Agent API / OpenAPI 3.0** и **MCP Server** для подключения ИИ-агентов).
|
||||
|
||||
Вся внутренняя логика реализована на скриптовом движке **ucode** — нативном C-интерпретаторе OpenWrt, обеспечивающем ультранизкое потребление RAM (от 128 МБ ОЗУ) и мгновенный отклик.
|
||||
|
||||
@@ -41,7 +41,7 @@ Tachyon перехватывает сетевой стек через ядро *
|
||||
2. **Zapret v1 (`nfqws`)**: Базовая десинхронизация TCP/UDP (`fake`, `disorder`, `split2`) прямо на роутере без VPS.
|
||||
3. **Zapret v2 (`nfqws2`)**: Адаптивный многовекторный обход ТСПУ (`multisplit`, `seqovl`, `wsize`, PAWS `tcp_ts`, аутентичные `blobs`) для YouTube 4K, Discord и стриминга.
|
||||
4. **ByeDPI (`ciadpi`)**: Локальный SOCKS5-десинхронизатор с фрагментацией полезной нагрузки HTTP/TLS SNI.
|
||||
5. **Зашифрованный прокси-туннель (sing-box)**: Заблокированные ресурсы и приватный трафик направляются через защищённые протоколы (VLESS Reality, Hysteria2, WireGuard, AmneziaWG).
|
||||
5. **Туннелирование и прокси (sing-box / FPTN)**: Заблокированные ресурсы и приватный трафик направляются через защищённые протоколы (VLESS Reality, Hysteria2, WireGuard, AmneziaWG) и высокоскоростной туннель **FPTN** (`tun-fptn` поверх WebSocket/TLS с маскировкой под веб-трафик).
|
||||
6. **Smart DNS Pipeline**: Изолированная обработка DNS через FakeIP (`198.18.0.0/15`), DoH/DoT/DoQ с защитой от перехвата провайдером и автоматическим отказоустойчивым переключением (DNS Failover).
|
||||
|
||||
<p align="center">
|
||||
@@ -52,6 +52,10 @@ Tachyon перехватывает сетевой стек через ядро *
|
||||
|
||||
### 🛡️ 1. Мультипротокольное проксирование и умный DNS-стек
|
||||
* **Ядро sing-box Engine (v1.11+)**: Нативная поддержка современных защищённых протоколов — **VLESS (Reality / gRPC / WS)**, **VMess**, **Shadowsocks**, **Trojan**, **Hysteria2**, **WireGuard / AmneziaWG**.
|
||||
* **Высокоскоростной туннель FPTN Engine (`fptn-client-cli`)**:
|
||||
* Нативная интеграция Fast Packet Tunnel Network: L3-туннелирование IP-пакетов поверх WebSocket/TLS с эффективной маскировкой под стандартный HTTPS-трафик для обхода жестких протокольных блокировок.
|
||||
* Выделенный сетевой интерфейс `tun-fptn`, таблица маршрутизации (`4249`) и бесшовная селективная маршрутизация по правилам nftables.
|
||||
* Управление компонентом: автоопределение платформы роутера, скачивание и установка из официальных релизов прямо из LuCI, отслеживание обновлений и безопасный откат (Rollback).
|
||||
* **Генератор профилей Cloudflare WARP / AmneziaWG (`generate_warp`)**: Быстрое создание готовых конфигураций WireGuard и AmneziaWG прямо на роутере без сторонних скриптов.
|
||||
* **Многомерная селективная маршрутизация**:
|
||||
* **По доменам и IP-сетям**: Направляйте через прокси или десинк только целевой трафик.
|
||||
@@ -328,6 +332,7 @@ Tachyon опирается на фундаментальные разработ
|
||||
* 📦 **[sing-box](https://github.com/SagerNet/sing-box)** — универсальная прокси-платформа.
|
||||
* 🚀 **[zapret (bol-van)](https://github.com/bol-van/zapret2)** — средства локального обхода DPI (`nfqws` / `nfqws2`).
|
||||
* 🌐 **[ByeDPI](https://github.com/hrbrmstr/byedpi)** — локальный SOCKS-прокси для десинка пакетов.
|
||||
* 🛡️ **[FPTN (fptn-project)](https://github.com/fptn-project/fptn)** — высокоскоростной VPN/туннель пакетов через WebSocket/TLS с обходом блокировок.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/readme/divider_stream.svg" width="100%" alt="divider" />
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "fe-app-tachyon",
|
||||
"version": "1.3.27",
|
||||
"version": "1.3.28",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "fe-app-tachyon",
|
||||
"version": "1.3.27",
|
||||
"version": "1.3.28",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/parser": "7.28.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fe-app-tachyon",
|
||||
"version": "1.3.27",
|
||||
"version": "1.3.28",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -43,6 +43,11 @@ const componentActionKeyMap: Record<string, UpdatesActionKey> = {
|
||||
'olcrtc:install_version': 'olcrtcInstall',
|
||||
'olcrtc:remove': 'olcrtcRemove',
|
||||
'olcrtc:rollback': 'olcrtcRollback',
|
||||
'fptn:check_update': 'fptnCheck',
|
||||
'fptn:install': 'fptnInstall',
|
||||
'fptn:install_version': 'fptnInstall',
|
||||
'fptn:remove': 'fptnRemove',
|
||||
'fptn:rollback': 'fptnRollback',
|
||||
'tailscale:check_update': 'tailscaleCheck',
|
||||
'tailscale:install': 'tailscaleInstall',
|
||||
'tailscale:install_version': 'tailscaleInstall',
|
||||
|
||||
@@ -245,6 +245,10 @@ export interface StoreType {
|
||||
tailscale_installed: number;
|
||||
tailscale_backup_version?: string;
|
||||
tailscale_backup_time?: number;
|
||||
fptn_version: string;
|
||||
fptn_installed: number;
|
||||
fptn_backup_version?: string;
|
||||
fptn_backup_time?: number;
|
||||
server_inbounds_enabled_count: number;
|
||||
direct_bypass_enabled?: number;
|
||||
direct_bypass_address?: string;
|
||||
@@ -289,6 +293,10 @@ export interface StoreType {
|
||||
olcrtcInstall: { loading: boolean };
|
||||
olcrtcRemove: { loading: boolean };
|
||||
olcrtcRollback: { loading: boolean };
|
||||
fptnCheck: { loading: boolean };
|
||||
fptnInstall: { loading: boolean };
|
||||
fptnRemove: { loading: boolean };
|
||||
fptnRollback: { loading: boolean };
|
||||
tailscaleCheck: { loading: boolean };
|
||||
tailscaleInstall: { loading: boolean };
|
||||
tailscaleRemove: { loading: boolean };
|
||||
|
||||
@@ -30,6 +30,8 @@ const UNKNOWN_SYSTEM_INFO: StoreType['diagnosticsSystemInfo'] = {
|
||||
olcrtc_installed: 0,
|
||||
tailscale_version: _('unknown'),
|
||||
tailscale_installed: 0,
|
||||
fptn_version: _('unknown'),
|
||||
fptn_installed: 0,
|
||||
server_inbounds_enabled_count: -1,
|
||||
openwrt_version: _('unknown'),
|
||||
device_model: _('unknown'),
|
||||
|
||||
@@ -45,6 +45,10 @@ function getEmptyUpdatesActions(): StoreType['updatesActions'] {
|
||||
olcrtcInstall: { loading: false },
|
||||
olcrtcRemove: { loading: false },
|
||||
olcrtcRollback: { loading: false },
|
||||
fptnCheck: { loading: false },
|
||||
fptnInstall: { loading: false },
|
||||
fptnRemove: { loading: false },
|
||||
fptnRollback: { loading: false },
|
||||
tailscaleCheck: { loading: false },
|
||||
tailscaleInstall: { loading: false },
|
||||
tailscaleRemove: { loading: false },
|
||||
|
||||
@@ -111,6 +111,10 @@ export const initialDiagnosticStore: Pick<
|
||||
tailscale_installed: 0,
|
||||
tailscale_backup_version: '',
|
||||
tailscale_backup_time: 0,
|
||||
fptn_version: 'loading',
|
||||
fptn_installed: 0,
|
||||
fptn_backup_version: '',
|
||||
fptn_backup_time: 0,
|
||||
server_inbounds_enabled_count: -1,
|
||||
direct_bypass_enabled: 0,
|
||||
direct_bypass_address: '',
|
||||
@@ -195,6 +199,10 @@ export const initialDiagnosticStore: Pick<
|
||||
olcrtcInstall: { loading: false },
|
||||
olcrtcRemove: { loading: false },
|
||||
olcrtcRollback: { loading: false },
|
||||
fptnCheck: { loading: false },
|
||||
fptnInstall: { loading: false },
|
||||
fptnRemove: { loading: false },
|
||||
fptnRollback: { loading: false },
|
||||
tailscaleCheck: { loading: false },
|
||||
tailscaleInstall: { loading: false },
|
||||
tailscaleRemove: { loading: false },
|
||||
@@ -212,6 +220,7 @@ export const initialDiagnosticStore: Pick<
|
||||
byedpi: { status: null, latest_version: '', release_url: '' },
|
||||
wdtt: { status: null, latest_version: '', release_url: '' },
|
||||
olcrtc: { status: null, latest_version: '', release_url: '' },
|
||||
fptn: { status: null, latest_version: '', release_url: '' },
|
||||
tailscale: { status: null, latest_version: '', release_url: '' },
|
||||
direct_bypass: { status: null, latest_version: '', release_url: '' },
|
||||
torrserver_direct: { status: null, latest_version: '', release_url: '' },
|
||||
|
||||
@@ -74,6 +74,8 @@ function getComponentCardTitle(component: Tachyon.ComponentName): string {
|
||||
return 'WDTT';
|
||||
case 'olcrtc':
|
||||
return 'OlcRTC';
|
||||
case 'fptn':
|
||||
return 'FPTN';
|
||||
case 'tailscale':
|
||||
return 'Tailscale';
|
||||
default:
|
||||
@@ -100,6 +102,8 @@ function getComponentCurrentVersion(
|
||||
return sys.wdtt_version;
|
||||
case 'olcrtc':
|
||||
return sys.olcrtc_version;
|
||||
case 'fptn':
|
||||
return sys.fptn_version;
|
||||
case 'tailscale':
|
||||
return sys.tailscale_version;
|
||||
default:
|
||||
@@ -561,6 +565,18 @@ function patchSystemInfoAfterMutation(result: Tachyon.ComponentActionResult) {
|
||||
}
|
||||
}
|
||||
|
||||
if (result.component === 'fptn') {
|
||||
nextSystemInfo.providerInfoLoaded = true;
|
||||
|
||||
if (result.action === 'remove') {
|
||||
nextSystemInfo.fptn_installed = 0;
|
||||
nextSystemInfo.fptn_version = 'not installed';
|
||||
} else {
|
||||
nextSystemInfo.fptn_installed = 1;
|
||||
nextSystemInfo.fptn_version = version;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.component === 'direct_bypass') {
|
||||
nextSystemInfo.direct_bypass_enabled = result.action === 'enable' ? 1 : 0;
|
||||
}
|
||||
@@ -582,7 +598,8 @@ function patchSystemInfoAfterMutation(result: Tachyon.ComponentActionResult) {
|
||||
result.component === 'zapret2' ||
|
||||
result.component === 'byedpi' ||
|
||||
result.component === 'wdtt' ||
|
||||
result.component === 'olcrtc'
|
||||
result.component === 'olcrtc' ||
|
||||
result.component === 'fptn'
|
||||
) {
|
||||
notifyActionProvidersAvailabilityChanged(normalizedSystemInfo);
|
||||
}
|
||||
@@ -1130,6 +1147,8 @@ function getComponentBackupVersion(component: Tachyon.ComponentName): string {
|
||||
return sys.wdtt_backup_version || '';
|
||||
case 'olcrtc':
|
||||
return sys.olcrtc_backup_version || '';
|
||||
case 'fptn':
|
||||
return sys.fptn_backup_version || '';
|
||||
case 'tailscale':
|
||||
return sys.tailscale_backup_version || '';
|
||||
default:
|
||||
@@ -1159,7 +1178,14 @@ function getOptionalComponentActions({
|
||||
removeKey,
|
||||
rollbackKey,
|
||||
}: {
|
||||
component: 'zapret' | 'zapret2' | 'byedpi' | 'wdtt' | 'olcrtc' | 'tailscale';
|
||||
component:
|
||||
| 'zapret'
|
||||
| 'zapret2'
|
||||
| 'byedpi'
|
||||
| 'wdtt'
|
||||
| 'olcrtc'
|
||||
| 'fptn'
|
||||
| 'tailscale';
|
||||
installed: boolean;
|
||||
checkKey: UpdatesActionKey;
|
||||
installKey: UpdatesActionKey;
|
||||
@@ -1197,6 +1223,7 @@ const COMPONENT_REPO_URLS: Record<Tachyon.ComponentName, string> = {
|
||||
byedpi: 'https://github.com/DPITrickster/ByeDPI-OpenWrt',
|
||||
wdtt: 'https://github.com/SpaceNeuroX/qwdtt-openwrt',
|
||||
olcrtc: 'https://github.com/alekvol/openwrt-olcrtc',
|
||||
fptn: 'https://github.com/fptn-project/fptn',
|
||||
tailscale: 'https://openwrt.org/packages/pkgdata/tailscale',
|
||||
direct_bypass: '',
|
||||
torrserver_direct: '',
|
||||
@@ -1212,6 +1239,7 @@ function getComponentCards(): ComponentCard[] {
|
||||
const byedpiInstalled = Boolean(systemInfo.byedpi_installed);
|
||||
const wdttInstalled = Boolean(systemInfo.wdtt_installed);
|
||||
const olcrtcInstalled = Boolean(systemInfo.olcrtc_installed);
|
||||
const fptnInstalled = Boolean(systemInfo.fptn_installed);
|
||||
const tailscaleInstalled = Boolean(systemInfo.tailscale_installed);
|
||||
const singBoxInstalled = !isNotInstalled(systemInfo.sing_box_version);
|
||||
const singBoxStable =
|
||||
@@ -1339,6 +1367,14 @@ function getComponentCards(): ComponentCard[] {
|
||||
removeKey: 'olcrtcRemove',
|
||||
rollbackKey: 'olcrtcRollback',
|
||||
});
|
||||
const fptnActions = getOptionalComponentActions({
|
||||
component: 'fptn',
|
||||
installed: fptnInstalled,
|
||||
checkKey: 'fptnCheck',
|
||||
installKey: 'fptnInstall',
|
||||
removeKey: 'fptnRemove',
|
||||
rollbackKey: 'fptnRollback',
|
||||
});
|
||||
const tailscaleActions = getOptionalComponentActions({
|
||||
component: 'tailscale',
|
||||
installed: tailscaleInstalled,
|
||||
@@ -1537,6 +1573,21 @@ function getComponentCards(): ComponentCard[] {
|
||||
actions: olcrtcActions,
|
||||
supportsVersions: true,
|
||||
},
|
||||
{
|
||||
component: 'fptn',
|
||||
column: 1,
|
||||
title: 'FPTN',
|
||||
version: systemInfoLoading
|
||||
? _('Loading...')
|
||||
: fptnInstalled
|
||||
? systemInfo.fptn_version
|
||||
: _('Not installed'),
|
||||
latestVersion: getLatestVersion('fptn'),
|
||||
releaseUrl: getGitHubReleaseUrl('fptn'),
|
||||
repoUrl: COMPONENT_REPO_URLS.fptn,
|
||||
actions: fptnActions,
|
||||
supportsVersions: true,
|
||||
},
|
||||
{
|
||||
component: 'tailscale',
|
||||
column: 1,
|
||||
|
||||
@@ -651,6 +651,10 @@ export namespace Tachyon {
|
||||
olcrtc_installed: 0 | 1;
|
||||
tailscale_version: string;
|
||||
tailscale_installed: 0 | 1;
|
||||
fptn_version: string;
|
||||
fptn_installed: 0 | 1;
|
||||
fptn_backup_version?: string;
|
||||
fptn_backup_time?: number;
|
||||
direct_bypass_enabled?: 0 | 1;
|
||||
direct_bypass_address?: string;
|
||||
direct_bypass_port?: string;
|
||||
@@ -743,6 +747,7 @@ export namespace Tachyon {
|
||||
| 'byedpi'
|
||||
| 'wdtt'
|
||||
| 'olcrtc'
|
||||
| 'fptn'
|
||||
| 'tailscale'
|
||||
| 'direct_bypass'
|
||||
| 'torrserver_direct';
|
||||
|
||||
@@ -5997,6 +5997,10 @@ var initialDiagnosticStore = {
|
||||
tailscale_installed: 0,
|
||||
tailscale_backup_version: "",
|
||||
tailscale_backup_time: 0,
|
||||
fptn_version: "loading",
|
||||
fptn_installed: 0,
|
||||
fptn_backup_version: "",
|
||||
fptn_backup_time: 0,
|
||||
server_inbounds_enabled_count: -1,
|
||||
direct_bypass_enabled: 0,
|
||||
direct_bypass_address: "",
|
||||
@@ -6081,6 +6085,10 @@ var initialDiagnosticStore = {
|
||||
olcrtcInstall: { loading: false },
|
||||
olcrtcRemove: { loading: false },
|
||||
olcrtcRollback: { loading: false },
|
||||
fptnCheck: { loading: false },
|
||||
fptnInstall: { loading: false },
|
||||
fptnRemove: { loading: false },
|
||||
fptnRollback: { loading: false },
|
||||
tailscaleCheck: { loading: false },
|
||||
tailscaleInstall: { loading: false },
|
||||
tailscaleRemove: { loading: false },
|
||||
@@ -6098,6 +6106,7 @@ var initialDiagnosticStore = {
|
||||
byedpi: { status: null, latest_version: "", release_url: "" },
|
||||
wdtt: { status: null, latest_version: "", release_url: "" },
|
||||
olcrtc: { status: null, latest_version: "", release_url: "" },
|
||||
fptn: { status: null, latest_version: "", release_url: "" },
|
||||
tailscale: { status: null, latest_version: "", release_url: "" },
|
||||
direct_bypass: { status: null, latest_version: "", release_url: "" },
|
||||
torrserver_direct: { status: null, latest_version: "", release_url: "" }
|
||||
@@ -6483,6 +6492,11 @@ var componentActionKeyMap = {
|
||||
"olcrtc:install_version": "olcrtcInstall",
|
||||
"olcrtc:remove": "olcrtcRemove",
|
||||
"olcrtc:rollback": "olcrtcRollback",
|
||||
"fptn:check_update": "fptnCheck",
|
||||
"fptn:install": "fptnInstall",
|
||||
"fptn:install_version": "fptnInstall",
|
||||
"fptn:remove": "fptnRemove",
|
||||
"fptn:rollback": "fptnRollback",
|
||||
"tailscale:check_update": "tailscaleCheck",
|
||||
"tailscale:install": "tailscaleInstall",
|
||||
"tailscale:install_version": "tailscaleInstall",
|
||||
@@ -6666,6 +6680,10 @@ function getEmptyUpdatesActions() {
|
||||
olcrtcInstall: { loading: false },
|
||||
olcrtcRemove: { loading: false },
|
||||
olcrtcRollback: { loading: false },
|
||||
fptnCheck: { loading: false },
|
||||
fptnInstall: { loading: false },
|
||||
fptnRemove: { loading: false },
|
||||
fptnRollback: { loading: false },
|
||||
tailscaleCheck: { loading: false },
|
||||
tailscaleInstall: { loading: false },
|
||||
tailscaleRemove: { loading: false },
|
||||
@@ -11170,6 +11188,8 @@ var UNKNOWN_SYSTEM_INFO = {
|
||||
olcrtc_installed: 0,
|
||||
tailscale_version: _("unknown"),
|
||||
tailscale_installed: 0,
|
||||
fptn_version: _("unknown"),
|
||||
fptn_installed: 0,
|
||||
server_inbounds_enabled_count: -1,
|
||||
openwrt_version: _("unknown"),
|
||||
device_model: _("unknown")
|
||||
@@ -20984,6 +21004,8 @@ function getComponentCardTitle(component) {
|
||||
return "WDTT";
|
||||
case "olcrtc":
|
||||
return "OlcRTC";
|
||||
case "fptn":
|
||||
return "FPTN";
|
||||
case "tailscale":
|
||||
return "Tailscale";
|
||||
default:
|
||||
@@ -21007,6 +21029,8 @@ function getComponentCurrentVersion(component) {
|
||||
return sys.wdtt_version;
|
||||
case "olcrtc":
|
||||
return sys.olcrtc_version;
|
||||
case "fptn":
|
||||
return sys.fptn_version;
|
||||
case "tailscale":
|
||||
return sys.tailscale_version;
|
||||
default:
|
||||
@@ -21327,6 +21351,16 @@ function patchSystemInfoAfterMutation(result) {
|
||||
nextSystemInfo.olcrtc_version = version;
|
||||
}
|
||||
}
|
||||
if (result.component === "fptn") {
|
||||
nextSystemInfo.providerInfoLoaded = true;
|
||||
if (result.action === "remove") {
|
||||
nextSystemInfo.fptn_installed = 0;
|
||||
nextSystemInfo.fptn_version = "not installed";
|
||||
} else {
|
||||
nextSystemInfo.fptn_installed = 1;
|
||||
nextSystemInfo.fptn_version = version;
|
||||
}
|
||||
}
|
||||
if (result.component === "direct_bypass") {
|
||||
nextSystemInfo.direct_bypass_enabled = result.action === "enable" ? 1 : 0;
|
||||
}
|
||||
@@ -21338,7 +21372,7 @@ function patchSystemInfoAfterMutation(result) {
|
||||
store.set({
|
||||
diagnosticsSystemInfo: normalizedSystemInfo
|
||||
});
|
||||
if (result.component === "zapret" || result.component === "zapret2" || result.component === "byedpi" || result.component === "wdtt" || result.component === "olcrtc") {
|
||||
if (result.component === "zapret" || result.component === "zapret2" || result.component === "byedpi" || result.component === "wdtt" || result.component === "olcrtc" || result.component === "fptn") {
|
||||
notifyActionProvidersAvailabilityChanged(normalizedSystemInfo);
|
||||
}
|
||||
}
|
||||
@@ -21756,6 +21790,8 @@ function getComponentBackupVersion(component) {
|
||||
return sys.wdtt_backup_version || "";
|
||||
case "olcrtc":
|
||||
return sys.olcrtc_backup_version || "";
|
||||
case "fptn":
|
||||
return sys.fptn_backup_version || "";
|
||||
case "tailscale":
|
||||
return sys.tailscale_backup_version || "";
|
||||
default:
|
||||
@@ -21806,6 +21842,7 @@ var COMPONENT_REPO_URLS = {
|
||||
byedpi: "https://github.com/DPITrickster/ByeDPI-OpenWrt",
|
||||
wdtt: "https://github.com/SpaceNeuroX/qwdtt-openwrt",
|
||||
olcrtc: "https://github.com/alekvol/openwrt-olcrtc",
|
||||
fptn: "https://github.com/fptn-project/fptn",
|
||||
tailscale: "https://openwrt.org/packages/pkgdata/tailscale",
|
||||
direct_bypass: "",
|
||||
torrserver_direct: ""
|
||||
@@ -21820,6 +21857,7 @@ function getComponentCards() {
|
||||
const byedpiInstalled = Boolean(systemInfo.byedpi_installed);
|
||||
const wdttInstalled = Boolean(systemInfo.wdtt_installed);
|
||||
const olcrtcInstalled = Boolean(systemInfo.olcrtc_installed);
|
||||
const fptnInstalled = Boolean(systemInfo.fptn_installed);
|
||||
const tailscaleInstalled = Boolean(systemInfo.tailscale_installed);
|
||||
const singBoxInstalled = !isNotInstalled(systemInfo.sing_box_version);
|
||||
const singBoxStable = singBoxInstalled && !systemInfo.sing_box_extended && !systemInfo.sing_box_tiny;
|
||||
@@ -21934,6 +21972,14 @@ function getComponentCards() {
|
||||
removeKey: "olcrtcRemove",
|
||||
rollbackKey: "olcrtcRollback"
|
||||
});
|
||||
const fptnActions = getOptionalComponentActions({
|
||||
component: "fptn",
|
||||
installed: fptnInstalled,
|
||||
checkKey: "fptnCheck",
|
||||
installKey: "fptnInstall",
|
||||
removeKey: "fptnRemove",
|
||||
rollbackKey: "fptnRollback"
|
||||
});
|
||||
const tailscaleActions = getOptionalComponentActions({
|
||||
component: "tailscale",
|
||||
installed: tailscaleInstalled,
|
||||
@@ -22077,6 +22123,17 @@ function getComponentCards() {
|
||||
actions: olcrtcActions,
|
||||
supportsVersions: true
|
||||
},
|
||||
{
|
||||
component: "fptn",
|
||||
column: 1,
|
||||
title: "FPTN",
|
||||
version: systemInfoLoading ? _("Loading...") : fptnInstalled ? systemInfo.fptn_version : _("Not installed"),
|
||||
latestVersion: getLatestVersion("fptn"),
|
||||
releaseUrl: getGitHubReleaseUrl("fptn"),
|
||||
repoUrl: COMPONENT_REPO_URLS.fptn,
|
||||
actions: fptnActions,
|
||||
supportsVersions: true
|
||||
},
|
||||
{
|
||||
component: "tailscale",
|
||||
column: 1,
|
||||
|
||||
@@ -1417,6 +1417,43 @@ function resolve_olcrtc_release(arch, tag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolve_fptn_release(arch, tag) {
|
||||
let asset_ext = is_apk() ? "apk" : "ipk";
|
||||
let release_series = trim(helper_output("openwrt-release-series", [ "/etc/openwrt_release" ]));
|
||||
let releases_json = (tag != null && tag != "") ?
|
||||
fetch_github_release_by_tag_json("fptn-project", "fptn", tag) :
|
||||
fetch_github_releases_json("fptn-project", "fptn", "30");
|
||||
if (releases_json != "") {
|
||||
let resolved = trim(helper_output_input(releases_json, "fptn-select-asset", [ release_series, asset_ext, arch.candidates ]));
|
||||
let fields = split(resolved, "\t");
|
||||
if (length(fields) >= 4) {
|
||||
let ver = extract_arch_package_version(fields[1], fields[0]);
|
||||
let m = match(ver, /^([0-9]+\.[0-9]+\.[0-9]+)/);
|
||||
if (m) ver = m[1];
|
||||
return {
|
||||
arch: fields[0],
|
||||
package_name: fields[1],
|
||||
package_url: fields[2],
|
||||
release_url: fields[3],
|
||||
version: ver
|
||||
};
|
||||
}
|
||||
}
|
||||
if (tag != null && tag != "") {
|
||||
let distrib_arch = read_openwrt_release_value("DISTRIB_ARCH");
|
||||
let tag_clean = replace(tag, /^v/, "");
|
||||
let pkg_name = "fptn-client-" + tag_clean + "-openwrt-" + (release_series != "" ? release_series + ".x" : "24.10.x") + "-" + distrib_arch + "." + asset_ext;
|
||||
return {
|
||||
arch: distrib_arch,
|
||||
package_name: pkg_name,
|
||||
package_url: "https://github.com/fptn-project/fptn/releases/download/" + tag + "/" + pkg_name,
|
||||
release_url: "https://github.com/fptn-project/fptn/releases/tag/" + tag,
|
||||
version: tag_clean
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function download_direct_package(release) {
|
||||
let package_file = tmp_dir + "/" + release.package_name;
|
||||
if (!download_with_retry(release.package_url, package_file, release.package_name) || !file_nonempty(package_file))
|
||||
@@ -1688,6 +1725,47 @@ function install_olcrtc(action, target_tag) {
|
||||
action_success("olcrtc", action, "OlcRTC package has been installed", current_version, pkg.version, 1, "latest", release.release_url || "");
|
||||
}
|
||||
|
||||
function install_fptn(action, target_tag) {
|
||||
init_tmp_dir() || action_fail("fptn", action, "Failed to create temporary directory");
|
||||
let arch = resolve_arch_candidates();
|
||||
if (arch == null)
|
||||
action_fail("fptn", action, "Failed to detect package architecture");
|
||||
let release = null;
|
||||
retry_resolve("Resolving FPTN package", function() {
|
||||
release = resolve_fptn_release(arch, target_tag);
|
||||
return release != null;
|
||||
});
|
||||
if (release == null)
|
||||
action_fail("fptn", action, "Failed to resolve FPTN package for this router architecture");
|
||||
|
||||
let runtime_module = LIB_DIR + "/providers/fptn/runtime.uc";
|
||||
let installed = provider_installed(runtime_module);
|
||||
let current_version = provider_package_version(runtime_module);
|
||||
if (action == "check_update") {
|
||||
if (!installed)
|
||||
action_success("fptn", action, "FPTN is not installed", current_version, release.version, 0, "", release.release_url || "");
|
||||
check_success("fptn", current_version, release.version, release.release_url || "");
|
||||
}
|
||||
|
||||
let pkg = download_direct_package(release);
|
||||
if (pkg == null)
|
||||
action_fail("fptn", action, "Failed to download FPTN package");
|
||||
|
||||
run_logged("Updating package lists before FPTN package installation", pkg_list_update_command());
|
||||
|
||||
if (!run_logged("Installing FPTN package " + pkg.name, pkg_install_files_command([ pkg.file ])))
|
||||
action_fail("fptn", action, "Failed to install FPTN package", current_version, pkg.version);
|
||||
|
||||
disable_standalone_service("fptn");
|
||||
disable_standalone_service("fptn-client");
|
||||
restart_tachyon_after_successful_change();
|
||||
clear_version_caches();
|
||||
current_version = provider_package_version(runtime_module);
|
||||
if (current_version == "")
|
||||
current_version = pkg.version || "unknown";
|
||||
action_success("fptn", action, "FPTN package has been installed", current_version, pkg.version, 1, "latest", release.release_url || "");
|
||||
}
|
||||
|
||||
function install_tailscale(action) {
|
||||
let component = "tailscale";
|
||||
let label = "Tailscale";
|
||||
@@ -3071,6 +3149,8 @@ function normalize_component_name(component) {
|
||||
component = as_string(component);
|
||||
if (component == "sing-box" || component == "singbox")
|
||||
return "sing_box";
|
||||
if (component == "fptn-client" || component == "fptn_client")
|
||||
return "fptn";
|
||||
if (component == "tachyon")
|
||||
return "tachyon";
|
||||
if (component == "direct-bypass" || component == "directbypass" || component == "direct_proxy")
|
||||
@@ -3236,6 +3316,24 @@ function create_component_backup(component) {
|
||||
write_file(meta_file, sprintf("%J\n", meta));
|
||||
return true;
|
||||
}
|
||||
else if (component == "fptn") {
|
||||
let bin = "/usr/bin/fptn-client-cli";
|
||||
if (!file_exists(bin)) bin = "/usr/bin/fptn-client";
|
||||
if (!file_exists(bin)) return true;
|
||||
let st = fs.stat(bin);
|
||||
let size = (st && st.size) ? st.size : 0;
|
||||
if (!check_free_disk_space("/etc", size)) return false;
|
||||
ensure_dir(bdir);
|
||||
command_success_from_args([ "cp", "-p", bin, bdir + "/fptn-client-cli" ]);
|
||||
let version = provider_package_version(LIB_DIR + "/providers/fptn/runtime.uc");
|
||||
let meta = {
|
||||
component: "fptn",
|
||||
version: version,
|
||||
timestamp: now_seconds()
|
||||
};
|
||||
write_file(meta_file, sprintf("%J\n", meta));
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -3314,6 +3412,16 @@ function rollback_component(component) {
|
||||
restart_tachyon_after_successful_change();
|
||||
action_success("olcrtc", "rollback", "OlcRTC rolled back to " + backup_version, backup_version, "", 1);
|
||||
}
|
||||
else if (component == "fptn") {
|
||||
let backup_bin = bdir + "/fptn-client-cli";
|
||||
if (!file_exists(backup_bin)) action_fail("fptn", "rollback", "FPTN backup binary is missing");
|
||||
remove_file("/usr/bin/fptn-client-cli");
|
||||
command_success_from_args([ "cp", "-p", backup_bin, "/usr/bin/fptn-client-cli" ]);
|
||||
command_success_from_args([ "chmod", "0755", "/usr/bin/fptn-client-cli" ]);
|
||||
clear_version_caches();
|
||||
restart_tachyon_after_successful_change();
|
||||
action_success("fptn", "rollback", "FPTN rolled back to " + backup_version, backup_version, "", 1);
|
||||
}
|
||||
else if (component == "zapret") {
|
||||
let backup_bin = bdir + "/nfqws";
|
||||
if (!file_exists(backup_bin)) action_fail("zapret", "rollback", "Zapret backup binary is missing");
|
||||
@@ -3393,6 +3501,8 @@ function list_component_releases(component, count) {
|
||||
owner = "SpaceNeuroX"; repo = "qwdtt-openwrt";
|
||||
} else if (component == "olcrtc") {
|
||||
owner = "alekvol"; repo = "openwrt-olcrtc";
|
||||
} else if (component == "fptn") {
|
||||
owner = "fptn-project"; repo = "fptn";
|
||||
} else {
|
||||
print("[]\n"); return;
|
||||
}
|
||||
@@ -3449,6 +3559,8 @@ function install_component_version(component, tag) {
|
||||
install_wdtt("install", tag);
|
||||
} else if (component == "olcrtc") {
|
||||
install_olcrtc("install", tag);
|
||||
} else if (component == "fptn") {
|
||||
install_fptn("install", tag);
|
||||
} else {
|
||||
action_fail(component, "install_version", "Component " + component + " does not support version installation");
|
||||
}
|
||||
@@ -3595,6 +3707,10 @@ function component_action(component, action, extra) {
|
||||
install_olcrtc(action);
|
||||
else if (component == "olcrtc" && action == "remove")
|
||||
remove_optional_component("olcrtc", "olcrtc", "OlcRTC", LIB_DIR + "/providers/olcrtc/runtime.uc");
|
||||
else if (component == "fptn" && (action == "check_update" || action == "install"))
|
||||
install_fptn(action);
|
||||
else if (component == "fptn" && action == "remove")
|
||||
remove_optional_component("fptn", "fptn-client", "FPTN", LIB_DIR + "/providers/fptn/runtime.uc");
|
||||
else if (component == "tailscale" && (action == "check_update" || action == "install"))
|
||||
install_tailscale(action);
|
||||
else if (component == "tailscale" && action == "remove")
|
||||
|
||||
@@ -388,7 +388,7 @@ function openwrt_release_series(path) {
|
||||
|
||||
function updates_arch_package_version(package_name, package_arch) {
|
||||
let version = str_remove_suffix(str_remove_suffix(package_name, ".ipk"), ".apk");
|
||||
let prefixes = ["zapret2_", "zapret2-", "zapret_", "zapret-", "byedpi_", "byedpi-", "wdtt_", "wdtt-", "olcrtc_", "olcrtc-"];
|
||||
let prefixes = ["zapret2_", "zapret2-", "zapret_", "zapret-", "byedpi_", "byedpi-", "fptn-client-", "fptn-client_", "fptn_", "fptn-", "wdtt_", "wdtt-", "olcrtc_", "olcrtc-"];
|
||||
|
||||
for (let prefix in prefixes) {
|
||||
if (str_startswith(version, prefix)) {
|
||||
@@ -1011,6 +1011,50 @@ function byedpi_select_asset(series, asset_ext, arch_candidates) {
|
||||
}
|
||||
}
|
||||
|
||||
function fptn_asset_matches(name, series, arch, ext) {
|
||||
if (!str_startswith(name, "fptn-client-") && !str_startswith(name, "fptn-"))
|
||||
return false;
|
||||
if (!str_endswith(name, "." + ext))
|
||||
return false;
|
||||
if (series != "" && !str_contains(name, series))
|
||||
return false;
|
||||
return str_contains(name, "-" + arch + "." + ext) || str_contains(name, "_" + arch + "." + ext);
|
||||
}
|
||||
|
||||
function select_fptn_asset_from_release(release, series, asset_ext, arch_candidates) {
|
||||
for (let arch in split(as_string(arch_candidates), " ")) {
|
||||
if (arch == "")
|
||||
continue;
|
||||
for (let asset in array_or_empty(release.assets)) {
|
||||
if (type(asset) != "object")
|
||||
continue;
|
||||
let name = as_string(asset.name || "");
|
||||
let url = as_string(asset.browser_download_url || "");
|
||||
if (url != "" && fptn_asset_matches(name, series, arch, asset_ext)) {
|
||||
print(arch, "\t", name, "\t", url, "\t", as_string(release.html_url || ""), "\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function fptn_select_asset(series, asset_ext, arch_candidates) {
|
||||
let releases = releases_array_or_wrapped(read_stdin_json());
|
||||
|
||||
for (let pass = 0; pass < 2; pass++) {
|
||||
let current_series = pass == 0 ? as_string(series) : "";
|
||||
for (let release in releases) {
|
||||
if (type(release) != "object")
|
||||
continue;
|
||||
if (release.draft === true || (length(releases) > 1 && release.prerelease === true))
|
||||
continue;
|
||||
if (select_fptn_asset_from_release(release, current_series, asset_ext, arch_candidates))
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function wdtt_select_asset(series, asset_ext, arch_candidates) {
|
||||
let releases = releases_array_or_wrapped(read_stdin_json());
|
||||
|
||||
@@ -1494,6 +1538,8 @@ else if (mode == "wdtt-select-asset")
|
||||
wdtt_select_asset(ARGV[1], ARGV[2], ARGV[3]);
|
||||
else if (mode == "olcrtc-select-asset")
|
||||
olcrtc_select_asset(ARGV[1], ARGV[2], ARGV[3]);
|
||||
else if (mode == "fptn-select-asset")
|
||||
fptn_select_asset(ARGV[1], ARGV[2], ARGV[3]);
|
||||
else if (mode == "sing-box-extended-release-tag")
|
||||
sing_box_extended_release_tag();
|
||||
else if (mode == "sing-box-lx-release-tag")
|
||||
|
||||
@@ -622,7 +622,7 @@ function normalize_component_name(component) {
|
||||
function valid_component_name(component) {
|
||||
component = normalize_component_name(component);
|
||||
return component == "tachyon" || component == "sing_box" || component == "zapret" ||
|
||||
component == "zapret2" || component == "byedpi" || component == "wdtt" || component == "olcrtc" || component == "tailscale";
|
||||
component == "zapret2" || component == "byedpi" || component == "wdtt" || component == "olcrtc" || component == "fptn" || component == "tailscale";
|
||||
}
|
||||
|
||||
function settings_component_auto_update_enabled(settings) {
|
||||
@@ -1601,7 +1601,7 @@ function component_update_check_cache() {
|
||||
let results = [];
|
||||
|
||||
if (enabled) {
|
||||
for (let component in [ "tachyon", "sing_box", "zapret", "zapret2", "byedpi", "wdtt", "olcrtc" ]) {
|
||||
for (let component in [ "tachyon", "sing_box", "zapret", "zapret2", "byedpi", "wdtt", "olcrtc", "fptn" ]) {
|
||||
let value = read_json_file(component_update_check_cache_path(component));
|
||||
if (component_update_check_result_cacheable(value))
|
||||
push(results, value);
|
||||
@@ -2031,6 +2031,8 @@ function automatic_component_check_names() {
|
||||
push(result, "wdtt");
|
||||
if (module_success([ LIB_DIR + "/providers/olcrtc/runtime.uc", "installed" ]))
|
||||
push(result, "olcrtc");
|
||||
if (module_success([ LIB_DIR + "/providers/fptn/runtime.uc", "installed" ]))
|
||||
push(result, "fptn");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -340,6 +340,10 @@ function is_olcrtc_action(action) {
|
||||
return as_string(action) == "olcrtc";
|
||||
}
|
||||
|
||||
function is_fptn_action(action) {
|
||||
return as_string(action) == "fptn";
|
||||
}
|
||||
|
||||
function wdtt_sections() {
|
||||
let result = [];
|
||||
let sections = uci_core.section_objects(CONFIG_NAME, "section");
|
||||
@@ -360,6 +364,16 @@ function olcrtc_sections() {
|
||||
return result;
|
||||
}
|
||||
|
||||
function fptn_sections() {
|
||||
let result = [];
|
||||
let sections = uci_core.section_objects(CONFIG_NAME, "section");
|
||||
for (let section in sections) {
|
||||
if (bool_option(section, "enabled", true) && option(section, "action", "") == "fptn")
|
||||
push(result, section);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function normalize_action(action) {
|
||||
action = as_string(action);
|
||||
return is_connections_action(action) ? "connection" : action;
|
||||
@@ -1634,6 +1648,8 @@ return {
|
||||
cli_delete_section,
|
||||
wdtt_sections,
|
||||
olcrtc_sections,
|
||||
fptn_sections,
|
||||
is_wdtt_action,
|
||||
is_olcrtc_action
|
||||
is_olcrtc_action,
|
||||
is_fptn_action
|
||||
};
|
||||
|
||||
@@ -27,6 +27,8 @@ function binary_installed(package_name) {
|
||||
return fs.stat("/opt/zapret/nfq/nfqws") != null || fs.stat("/opt/zapret/nfqws") != null;
|
||||
if (package_name == "byedpi")
|
||||
return fs.stat("/usr/bin/ciadpi") != null;
|
||||
if (package_name == "fptn" || package_name == "fptn-client")
|
||||
return fs.stat("/usr/bin/fptn-client-cli") != null || fs.stat("/usr/bin/fptn-client") != null;
|
||||
if (package_name == "sing-box" || package_name == "sing-box-extended" || package_name == "sing-box-lx")
|
||||
return fs.stat("/usr/bin/sing-box") != null;
|
||||
return false;
|
||||
@@ -151,6 +153,15 @@ function binary_version(package_name) {
|
||||
let m = match(out, /([0-9a-zA-Z._-]+)/);
|
||||
if (m) return m[1];
|
||||
}
|
||||
if ((package_name == "fptn" || package_name == "fptn-client")) {
|
||||
for (let p in [ "/usr/bin/fptn-client-cli", "/usr/bin/fptn-client" ]) {
|
||||
if (fs.stat(p) != null) {
|
||||
let out = command_output_args([ p, "--version" ]);
|
||||
let m = match(out, /version[ \t]*([0-9a-zA-Z._-]+)/i) || match(out, /([0-9]+\.[0-9a-zA-Z._-]+)/);
|
||||
if (m) return m[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((package_name == "sing-box" || package_name == "sing-box-extended" || package_name == "sing-box-lx") && fs.stat("/usr/bin/sing-box") != null) {
|
||||
let out = command_output_args([ "/usr/bin/sing-box", "version" ]);
|
||||
let m = match(out, /version[ \t]*([0-9a-zA-Z._-]+)/i);
|
||||
|
||||
@@ -59,6 +59,7 @@ const ZAPRET2_RUNTIME_UC = LIB_DIR + "/providers/zapret2/runtime.uc";
|
||||
const BYEDPI_RUNTIME_UC = LIB_DIR + "/providers/byedpi/runtime.uc";
|
||||
const WDTT_RUNTIME_UC = LIB_DIR + "/providers/wdtt/runtime.uc";
|
||||
const OLCRTC_RUNTIME_UC = LIB_DIR + "/providers/olcrtc/runtime.uc";
|
||||
const FPTN_RUNTIME_UC = LIB_DIR + "/providers/fptn/runtime.uc";
|
||||
const TAILSCALE_RUNTIME_UC = LIB_DIR + "/providers/tailscale/runtime.uc";
|
||||
const ZAPRET_VALIDATOR_UC = LIB_DIR + "/providers/zapret/validator.uc";
|
||||
const ZAPRET2_VALIDATOR_UC = LIB_DIR + "/providers/zapret2/validator.uc";
|
||||
@@ -1148,6 +1149,8 @@ function build_system_info() {
|
||||
let byedpi_version = byedpi_installed ? provider_version(BYEDPI_RUNTIME_UC) : "not installed";
|
||||
let tailscale_installed = provider_installed(TAILSCALE_RUNTIME_UC) ? 1 : 0;
|
||||
let tailscale_version = tailscale_installed ? provider_version(TAILSCALE_RUNTIME_UC) : "not installed";
|
||||
let fptn_installed = provider_installed(FPTN_RUNTIME_UC) ? 1 : 0;
|
||||
let fptn_version = fptn_installed ? provider_version(FPTN_RUNTIME_UC) : "not installed";
|
||||
let device_model = first_line_value("/tmp/sysinfo/model", "unknown");
|
||||
|
||||
let direct_bypass_enabled = bool_option(settings(), "direct_bypass_enabled", false) ? 1 : 0;
|
||||
@@ -1177,6 +1180,7 @@ function build_system_info() {
|
||||
let zapret2_meta = read_backup_meta("zapret2");
|
||||
let byedpi_meta = read_backup_meta("byedpi");
|
||||
let tailscale_meta = read_backup_meta("tailscale");
|
||||
let fptn_meta = read_backup_meta("fptn");
|
||||
|
||||
return {
|
||||
tachyon_version: TACHYON_VERSION,
|
||||
@@ -1208,6 +1212,10 @@ function build_system_info() {
|
||||
tailscale_installed,
|
||||
tailscale_backup_version: tailscale_meta ? as_string(tailscale_meta.version) : "",
|
||||
tailscale_backup_time: tailscale_meta ? int(tailscale_meta.timestamp || 0) : 0,
|
||||
fptn_version,
|
||||
fptn_installed,
|
||||
fptn_backup_version: fptn_meta ? as_string(fptn_meta.version) : "",
|
||||
fptn_backup_time: fptn_meta ? int(fptn_meta.timestamp || 0) : 0,
|
||||
direct_bypass_enabled,
|
||||
direct_bypass_address,
|
||||
direct_bypass_port,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
let fs = require("fs");
|
||||
|
||||
function config(ctx) {
|
||||
let env_get = function(key, fallback) {
|
||||
let val = getenv(key);
|
||||
return val != null ? val : fallback;
|
||||
};
|
||||
|
||||
let bin = env_get("TACHYON_FPTN_BIN", "/usr/bin/fptn-client-cli");
|
||||
if (fs.stat(bin) == null && fs.stat("/usr/bin/fptn-client") != null)
|
||||
bin = "/usr/bin/fptn-client";
|
||||
|
||||
let state_dir = env_get("TACHYON_FPTN_STATE_DIR", "/var/run/tachyon/fptn");
|
||||
|
||||
return {
|
||||
kind: "fptn",
|
||||
action: "fptn",
|
||||
binary: bin,
|
||||
tun_interface: env_get("TACHYON_FPTN_TUN", "tun-fptn"),
|
||||
route_table: env_get("TACHYON_FPTN_ROUTE_TABLE", "4249"),
|
||||
fwmark: env_get("TACHYON_FPTN_FWMARK", "0x00300000"),
|
||||
mark_mask: env_get("TACHYON_FPTN_MARK_MASK", "0x00ff0000"),
|
||||
rule_priority: env_get("TACHYON_FPTN_RULE_PRIORITY", "102"),
|
||||
state_dir: state_dir,
|
||||
pid_file: state_dir + "/fptn.pid",
|
||||
log_file: state_dir + "/fptn.log",
|
||||
package_name: "fptn",
|
||||
runtime_path: (ctx && ctx.lib_dir ? ctx.lib_dir : "/usr/lib/tachyon") + "/providers/fptn/runtime.uc",
|
||||
config_name: "fptn",
|
||||
status_label: "fptn",
|
||||
check_prefix: "fptn"
|
||||
};
|
||||
}
|
||||
|
||||
function validator() {
|
||||
return require("providers.fptn.validator");
|
||||
}
|
||||
|
||||
return { config, validator };
|
||||
@@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env ucode
|
||||
|
||||
let fs = require("fs");
|
||||
let common = require("core.common");
|
||||
let uci_core = require("core.uci");
|
||||
let connections = require("config.connections");
|
||||
|
||||
let as_string = common.as_string;
|
||||
let bool_value = common.bool_value;
|
||||
let write_json = common.write_json;
|
||||
let shell_quote = common.shell_quote;
|
||||
let command_from_args = common.command_from_args;
|
||||
let command_output = common.command_output;
|
||||
let command_status = common.command_status;
|
||||
let command_success_from_args = common.command_success_from_args;
|
||||
let object_or_empty = common.object_or_empty;
|
||||
let option = common.option;
|
||||
let bool_option = common.bool_option;
|
||||
let section_name = common.section_name;
|
||||
let remove_file = common.remove_file;
|
||||
|
||||
const MODE = as_string(getenv("TACHYON_CONFIG_NAME")) || "tachyon";
|
||||
const LIB_DIR = as_string(getenv("TACHYON_LIB_DIR")) || "/usr/lib/tachyon";
|
||||
const CONFIG_NAME = MODE;
|
||||
|
||||
let cfg = require("providers.fptn.common").config({ lib_dir: LIB_DIR });
|
||||
|
||||
function log_message(msg, level) {
|
||||
level = level || "info";
|
||||
command_status("logger -t tachyon-fptn -p " + (level == "fatal" ? "user.err" : level == "warn" ? "user.warning" : "user.info") + " " + shell_quote(msg));
|
||||
}
|
||||
|
||||
function provider_available() {
|
||||
let st = fs.stat(cfg.binary);
|
||||
return st != null && st.mode != null && (int(st.mode) & 73) != 0;
|
||||
}
|
||||
|
||||
function enabled_sections() {
|
||||
if (connections && connections.fptn_sections)
|
||||
return connections.fptn_sections();
|
||||
|
||||
let result = [];
|
||||
let sections = uci_core.section_objects(CONFIG_NAME, "section");
|
||||
for (let s in sections) {
|
||||
if (bool_option(s, "enabled", true) && option(s, "action", "") == "fptn")
|
||||
push(result, s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function enabled_rule_count() {
|
||||
return length(enabled_sections());
|
||||
}
|
||||
|
||||
function running_pid() {
|
||||
let pid_data = trim(as_string(fs.readfile(cfg.pid_file) || ""));
|
||||
if (pid_data == "" || match(pid_data, /[^0-9]/))
|
||||
return null;
|
||||
|
||||
let pid = int(pid_data);
|
||||
if (pid <= 0)
|
||||
return null;
|
||||
|
||||
if (fs.stat("/proc/" + pid) != null)
|
||||
return pid;
|
||||
|
||||
remove_file(cfg.pid_file);
|
||||
return null;
|
||||
}
|
||||
|
||||
function package_version() {
|
||||
if (!provider_available())
|
||||
return "";
|
||||
|
||||
let out = trim(command_output(shell_quote(cfg.binary) + " --version 2>&1 || true"));
|
||||
let m = match(out, /version[ \t]*([0-9a-zA-Z._-]+)/i) || match(out, /([0-9]+\.[0-9a-zA-Z._-]+)/);
|
||||
if (m)
|
||||
return m[1];
|
||||
|
||||
let pkgs = require("core.packages");
|
||||
if (pkgs && pkgs.version)
|
||||
return pkgs.version("fptn") || pkgs.version("fptn-client") || "";
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function remove_kernel_routing() {
|
||||
let p = cfg.rule_priority || "102";
|
||||
let mark_spec = cfg.fwmark + "/" + cfg.mark_mask;
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (command_status("ip rule del fwmark " + mark_spec + " table " + cfg.route_table + " priority " + p + " 2>/dev/null") != 0)
|
||||
break;
|
||||
}
|
||||
|
||||
command_status("ip route flush table " + cfg.route_table + " 2>/dev/null; true");
|
||||
if (command_status("ip link show " + shell_quote(cfg.tun_interface) + " >/dev/null 2>&1") == 0)
|
||||
command_status("ip link set dev " + shell_quote(cfg.tun_interface) + " down 2>/dev/null; true");
|
||||
}
|
||||
|
||||
function install_kernel_routing() {
|
||||
remove_kernel_routing();
|
||||
|
||||
command_status("ip link set dev " + shell_quote(cfg.tun_interface) + " up 2>/dev/null; true");
|
||||
let route_ok = command_status("ip route replace default dev " + shell_quote(cfg.tun_interface) + " table " + cfg.route_table + " 2>/dev/null") == 0;
|
||||
if (!route_ok) {
|
||||
log_message("Failed to add default route dev " + cfg.tun_interface + " to table " + cfg.route_table, "warn");
|
||||
return false;
|
||||
}
|
||||
|
||||
let p = cfg.rule_priority || "102";
|
||||
let mark_spec = cfg.fwmark + "/" + cfg.mark_mask;
|
||||
let rule_ok = command_status("ip rule add fwmark " + mark_spec + " table " + cfg.route_table + " priority " + p + " 2>/dev/null") == 0;
|
||||
if (!rule_ok) {
|
||||
log_message("Failed to add ip rule for table " + cfg.route_table, "warn");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function stop_runtime() {
|
||||
let pid = running_pid();
|
||||
if (pid != null) {
|
||||
log_message("Stopping FPTN daemon (PID " + pid + ")", "info");
|
||||
command_status("kill -15 " + pid + " 2>/dev/null; true");
|
||||
|
||||
let stopped = false;
|
||||
for (let i = 0; i < 20; i++) {
|
||||
if (fs.stat("/proc/" + pid) == null) {
|
||||
stopped = true;
|
||||
break;
|
||||
}
|
||||
system("sleep 0.1 2>/dev/null || sleep 1");
|
||||
}
|
||||
|
||||
if (!stopped && fs.stat("/proc/" + pid) != null) {
|
||||
log_message("FPTN daemon did not stop gracefully; sending SIGKILL", "warn");
|
||||
command_status("kill -9 " + pid + " 2>/dev/null; true");
|
||||
}
|
||||
}
|
||||
|
||||
remove_file(cfg.pid_file);
|
||||
remove_kernel_routing();
|
||||
return true;
|
||||
}
|
||||
|
||||
function start_runtime() {
|
||||
let sections = enabled_sections();
|
||||
if (length(sections) == 0) {
|
||||
stop_runtime();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!provider_available()) {
|
||||
log_message("Cannot start FPTN: binary " + cfg.binary + " not found or not executable", "warn");
|
||||
return false;
|
||||
}
|
||||
|
||||
let section = sections[0];
|
||||
let token = as_string(option(section, "access_token", ""));
|
||||
if (token == "") {
|
||||
log_message("Cannot start FPTN: access_token not configured for section " + section_name(section), "warn");
|
||||
return false;
|
||||
}
|
||||
|
||||
stop_runtime();
|
||||
|
||||
command_status("mkdir -p " + shell_quote(cfg.state_dir) + " 2>/dev/null");
|
||||
|
||||
let cmd_args = [
|
||||
cfg.binary,
|
||||
"--access-token", token,
|
||||
"--tun-interface-name", cfg.tun_interface,
|
||||
"--enable-split-tunnel", "false"
|
||||
];
|
||||
|
||||
let sni = as_string(option(section, "sni", ""));
|
||||
if (sni != "") {
|
||||
push(cmd_args, "--sni");
|
||||
push(cmd_args, sni);
|
||||
}
|
||||
|
||||
let bypass_method = as_string(option(section, "bypass_method", ""));
|
||||
if (bypass_method != "") {
|
||||
push(cmd_args, "--bypass-method");
|
||||
push(cmd_args, bypass_method);
|
||||
}
|
||||
|
||||
let preferred_server = as_string(option(section, "preferred_server", ""));
|
||||
if (preferred_server != "") {
|
||||
push(cmd_args, "--preferred-server");
|
||||
push(cmd_args, preferred_server);
|
||||
}
|
||||
|
||||
let cmd_str = command_from_args(cmd_args) + " >> " + shell_quote(cfg.log_file) + " 2>&1 & echo $! > " + shell_quote(cfg.pid_file);
|
||||
log_message("Starting FPTN client on interface " + cfg.tun_interface, "info");
|
||||
system(cmd_str);
|
||||
|
||||
let started = false;
|
||||
for (let i = 0; i < 30; i++) {
|
||||
if (command_status("ip link show " + shell_quote(cfg.tun_interface) + " >/dev/null 2>&1") == 0) {
|
||||
started = true;
|
||||
break;
|
||||
}
|
||||
let p = running_pid();
|
||||
if (p == null && i > 5)
|
||||
break;
|
||||
system("sleep 0.1 2>/dev/null || sleep 1");
|
||||
}
|
||||
|
||||
if (!started) {
|
||||
log_message("FPTN interface " + cfg.tun_interface + " did not come up", "warn");
|
||||
return false;
|
||||
}
|
||||
|
||||
install_kernel_routing();
|
||||
log_message("FPTN client successfully started on " + cfg.tun_interface + " (table " + cfg.route_table + ")", "info");
|
||||
return true;
|
||||
}
|
||||
|
||||
function status_json() {
|
||||
let installed = provider_available();
|
||||
let pid = running_pid();
|
||||
let running = pid != null;
|
||||
let rule_count = enabled_rule_count();
|
||||
let ver = package_version();
|
||||
|
||||
write_json({
|
||||
installed: installed,
|
||||
configured: rule_count > 0,
|
||||
enabled_rule_count: rule_count,
|
||||
service_running: running,
|
||||
pid: pid,
|
||||
version: ver,
|
||||
binary: cfg.binary,
|
||||
tun_interface: cfg.tun_interface,
|
||||
route_table: cfg.route_table,
|
||||
log_file: cfg.log_file,
|
||||
ready: installed && running && rule_count > 0,
|
||||
status_message: running ? "FPTN is running" : (installed ? "FPTN is installed but not running" : "FPTN is not installed")
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function check_json() {
|
||||
write_json({
|
||||
fptn_installed: provider_available() ? 1 : 0,
|
||||
fptn_version: package_version(),
|
||||
binary: cfg.binary
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function module_exports() {
|
||||
return {
|
||||
provider_available: provider_available,
|
||||
package_version: package_version,
|
||||
start_runtime: start_runtime,
|
||||
stop_runtime: stop_runtime,
|
||||
status_json: status_json,
|
||||
check_json: check_json
|
||||
};
|
||||
}
|
||||
|
||||
if (sourcepath(1) != null && sourcepath(1) != "")
|
||||
return module_exports();
|
||||
|
||||
let mode = as_string(ARGV[0]);
|
||||
if (mode == "start-runtime")
|
||||
exit(start_runtime() ? 0 : 1);
|
||||
else if (mode == "stop-runtime")
|
||||
exit(stop_runtime() ? 0 : 1);
|
||||
else if (mode == "restart-runtime") {
|
||||
stop_runtime();
|
||||
exit(start_runtime() ? 0 : 1);
|
||||
}
|
||||
else if (mode == "status")
|
||||
status_json();
|
||||
else if (mode == "check")
|
||||
check_json();
|
||||
else if (mode == "installed" || mode == "provider-available")
|
||||
exit(provider_available() ? 0 : 1);
|
||||
else if (mode == "package-version" || mode == "version")
|
||||
printf("%s\n", package_version());
|
||||
else if (mode == "enabled-rule-count")
|
||||
printf("%d\n", enabled_rule_count());
|
||||
else {
|
||||
warn("Usage: providers/fptn/runtime.uc <start-runtime|stop-runtime|restart-runtime|status|check|installed|version>\n");
|
||||
exit(1);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
let common = require("core.common");
|
||||
let as_string = common.as_string;
|
||||
|
||||
function is_valid_token(token) {
|
||||
token = trim(as_string(token));
|
||||
return length(token) > 0;
|
||||
}
|
||||
|
||||
function validate_section(section) {
|
||||
if (!section)
|
||||
return { valid: false, error: "Section is missing" };
|
||||
|
||||
let token = as_string(section.access_token || "");
|
||||
if (!is_valid_token(token))
|
||||
return { valid: false, error: "Missing or invalid FPTN access token" };
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
return {
|
||||
is_valid_token,
|
||||
validate_section
|
||||
};
|
||||
@@ -122,6 +122,7 @@ const BYEDPI_UC = LIB_DIR + "/providers/byedpi/runtime.uc";
|
||||
const TAILSCALE_UC = LIB_DIR + "/providers/tailscale/runtime.uc";
|
||||
const WDTT_UC = LIB_DIR + "/providers/wdtt/runtime.uc";
|
||||
const OLCRTC_UC = LIB_DIR + "/providers/olcrtc/runtime.uc";
|
||||
const FPTN_UC = LIB_DIR + "/providers/fptn/runtime.uc";
|
||||
const PARENTAL_QUOTA_UC = LIB_DIR + "/service/parental_quota.uc";
|
||||
const PACKAGES_UC = LIB_DIR + "/core/packages.uc";
|
||||
const WATCHDOG_UC = LIB_DIR + "/service/watchdog.uc";
|
||||
@@ -943,6 +944,7 @@ function start_main() {
|
||||
module_success(ZAPRET2_UC, [ "start-runtime" ]);
|
||||
module_success(WDTT_UC, [ "start-runtime" ]);
|
||||
module_success(OLCRTC_UC, [ "start-runtime" ]);
|
||||
module_success(FPTN_UC, [ "start-runtime" ]);
|
||||
module_success(PARENTAL_QUOTA_UC, [ "install-cron" ]);
|
||||
module_success(PARENTAL_QUOTA_UC, [ "tick" ]);
|
||||
|
||||
@@ -1036,6 +1038,8 @@ function stop_main() {
|
||||
log_message("WDTT stop failed (non-fatal)", "warn");
|
||||
if (!module_success(OLCRTC_UC, [ "stop-runtime" ]))
|
||||
log_message("OlcRTC stop failed (non-fatal)", "warn");
|
||||
if (!module_success(FPTN_UC, [ "stop-runtime" ]))
|
||||
log_message("FPTN stop failed (non-fatal)", "warn");
|
||||
if (!module_success(TAILSCALE_UC, [ "stop-runtime" ]))
|
||||
log_message("Tailscale stop failed (non-fatal)", "warn");
|
||||
module_success(PARENTAL_QUOTA_UC, [ "remove-cron" ]);
|
||||
@@ -1230,6 +1234,7 @@ function parse_reload_plan(output) {
|
||||
needs_byedpi_restart: 0,
|
||||
needs_wdtt_restart: 0,
|
||||
needs_olcrtc_restart: 0,
|
||||
needs_fptn_restart: 0,
|
||||
needs_dnsmasq_configure: 0,
|
||||
needs_dnsmasq_restore: 0,
|
||||
needs_cron_refresh: 0,
|
||||
@@ -1268,6 +1273,7 @@ function reload_actions_summary(plan) {
|
||||
actions = append_reload_action(actions, plan.needs_byedpi_restart, "ByeDPI");
|
||||
actions = append_reload_action(actions, plan.needs_wdtt_restart, "WDTT");
|
||||
actions = append_reload_action(actions, plan.needs_olcrtc_restart, "OlcRTC");
|
||||
actions = append_reload_action(actions, plan.needs_fptn_restart, "FPTN");
|
||||
actions = append_reload_action(actions, plan.needs_dnsmasq_configure || plan.needs_dnsmasq_restore, "dnsmasq");
|
||||
actions = append_reload_action(actions, plan.needs_cron_refresh, "scheduled jobs");
|
||||
actions = append_reload_action(actions, plan.needs_list_update, "remote lists");
|
||||
@@ -1462,6 +1468,8 @@ function reload(reason) {
|
||||
module_success(WDTT_UC, [ "stop-runtime" ]);
|
||||
if (plan.needs_olcrtc_restart == 1)
|
||||
module_success(OLCRTC_UC, [ "stop-runtime" ]);
|
||||
if (plan.needs_fptn_restart == 1)
|
||||
module_success(FPTN_UC, [ "stop-runtime" ]);
|
||||
|
||||
if (plan.needs_hosts_update == 1 && module_success(STATE_UC, [ "has-hosts-list-update-sources" ]))
|
||||
module_success(HOSTS_UC, [ "list-update" ]);
|
||||
@@ -1534,6 +1542,8 @@ function reload(reason) {
|
||||
module_success(WDTT_UC, [ "start-runtime" ]);
|
||||
if (plan.needs_olcrtc_restart == 1)
|
||||
module_success(OLCRTC_UC, [ "start-runtime" ]);
|
||||
if (plan.needs_fptn_restart == 1)
|
||||
module_success(FPTN_UC, [ "start-runtime" ]);
|
||||
if (plan.needs_byedpi_restart == 1)
|
||||
module_success(BYEDPI_UC, [ "start-runtime" ]);
|
||||
// Native Tailscale must be up before sing-box so tailnet routes win over
|
||||
|
||||
@@ -65,6 +65,7 @@ function emit_reload_plan(previous, current, context) {
|
||||
byedpi_runtime: current.byedpi_runtime != previous.byedpi_runtime,
|
||||
wdtt_runtime: current.wdtt_runtime != previous.wdtt_runtime,
|
||||
olcrtc_runtime: current.olcrtc_runtime != previous.olcrtc_runtime,
|
||||
fptn_runtime: current.fptn_runtime != previous.fptn_runtime,
|
||||
list: current.list != previous.list,
|
||||
hosts_list: current.hosts_list != previous.hosts_list,
|
||||
cron: current.cron != previous.cron
|
||||
@@ -78,6 +79,7 @@ function emit_reload_plan(previous, current, context) {
|
||||
byedpi_restart: false,
|
||||
wdtt_restart: false,
|
||||
olcrtc_restart: false,
|
||||
fptn_restart: false,
|
||||
dnsmasq_configure: false,
|
||||
dnsmasq_restore: false,
|
||||
cron_refresh: false,
|
||||
@@ -122,6 +124,9 @@ function emit_reload_plan(previous, current, context) {
|
||||
if (changed.olcrtc_runtime)
|
||||
needs.olcrtc_restart = true;
|
||||
|
||||
if (changed.fptn_runtime)
|
||||
needs.fptn_restart = true;
|
||||
|
||||
if (changed.dnsmasq) {
|
||||
if (!current.dont_touch_dhcp)
|
||||
needs.dnsmasq_configure = true;
|
||||
@@ -187,6 +192,7 @@ function emit_reload_plan(previous, current, context) {
|
||||
emit_bool("changed_byedpi_runtime", changed.byedpi_runtime);
|
||||
emit_bool("changed_wdtt_runtime", changed.wdtt_runtime);
|
||||
emit_bool("changed_olcrtc_runtime", changed.olcrtc_runtime);
|
||||
emit_bool("changed_fptn_runtime", changed.fptn_runtime);
|
||||
emit_bool("changed_cron", changed.cron);
|
||||
emit_bool("changed_list", changed.list);
|
||||
emit_bool("changed_hosts_list", changed.hosts_list);
|
||||
@@ -198,6 +204,7 @@ function emit_reload_plan(previous, current, context) {
|
||||
emit_bool("needs_byedpi_restart", needs.byedpi_restart);
|
||||
emit_bool("needs_wdtt_restart", needs.wdtt_restart);
|
||||
emit_bool("needs_olcrtc_restart", needs.olcrtc_restart);
|
||||
emit_bool("needs_fptn_restart", needs.fptn_restart);
|
||||
emit_bool("needs_dnsmasq_configure", needs.dnsmasq_configure);
|
||||
emit_bool("needs_dnsmasq_restore", needs.dnsmasq_restore);
|
||||
emit_bool("needs_cron_refresh", needs.cron_refresh);
|
||||
@@ -293,6 +300,7 @@ function plan_state_from_file(path) {
|
||||
byedpi_runtime: as_string(state.byedpi_runtime_signature),
|
||||
wdtt_runtime: as_string(state.wdtt_runtime_signature),
|
||||
olcrtc_runtime: as_string(state.olcrtc_runtime_signature),
|
||||
fptn_runtime: as_string(state.fptn_runtime_signature),
|
||||
list: as_string(state.list_signature),
|
||||
hosts_list: as_string(state.hosts_list_signature),
|
||||
cron: as_string(state.cron_signature),
|
||||
|
||||
@@ -132,6 +132,7 @@ const RELOAD_STATE_FIELDS = [
|
||||
"byedpi_runtime_signature",
|
||||
"wdtt_runtime_signature",
|
||||
"olcrtc_runtime_signature",
|
||||
"fptn_runtime_signature",
|
||||
"list_signature",
|
||||
"hosts_list_signature",
|
||||
"cron_signature",
|
||||
@@ -1854,6 +1855,24 @@ function olcrtc_runtime_signature_body(sections) {
|
||||
return body;
|
||||
}
|
||||
|
||||
function fptn_runtime_signature_body(sections) {
|
||||
let body = "";
|
||||
|
||||
for (let section in sections) {
|
||||
section = object_or_empty(section);
|
||||
if (!section_action_is_enabled(section, "fptn"))
|
||||
continue;
|
||||
|
||||
let name = section_name(section);
|
||||
body = signature_add_value(body, "fptn." + name + ".access_token", option(section, "access_token", ""));
|
||||
body = signature_add_value(body, "fptn." + name + ".sni", option(section, "sni", ""));
|
||||
body = signature_add_value(body, "fptn." + name + ".bypass_method", option(section, "bypass_method", ""));
|
||||
body = signature_add_value(body, "fptn." + name + ".preferred_server", option(section, "preferred_server", ""));
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
function reload_state_values_from_sources(format, settings, sections, servers, dnsmasq, legacy_dnsmasq_present, mwan3_active_value, schedules, profiles, guest_modes) {
|
||||
return {
|
||||
format: as_string(format),
|
||||
@@ -1868,6 +1887,7 @@ function reload_state_values_from_sources(format, settings, sections, servers, d
|
||||
byedpi_runtime_signature: signature_hash(byedpi_runtime_signature_body(sections)),
|
||||
wdtt_runtime_signature: signature_hash(wdtt_runtime_signature_body(sections)),
|
||||
olcrtc_runtime_signature: signature_hash(olcrtc_runtime_signature_body(sections)),
|
||||
fptn_runtime_signature: signature_hash(fptn_runtime_signature_body(sections)),
|
||||
list_signature: signature_hash(list_update_signature_body(sections)),
|
||||
hosts_list_signature: signature_hash(hosts_list_signature_body(sections)),
|
||||
cron_signature: signature_hash(cron_signature_body(settings, sections)),
|
||||
@@ -2276,6 +2296,12 @@ else if (mode == "olcrtc-runtime-signature-fixture") {
|
||||
let data = fixture_data(ARGV[1]);
|
||||
exit(print_signature_hash(olcrtc_runtime_signature_body(fixture_section_list(data))) ? 0 : 1);
|
||||
}
|
||||
else if (mode == "fptn-runtime-signature")
|
||||
exit(print_signature_hash(fptn_runtime_signature_body(uci_sections("section"))) ? 0 : 1);
|
||||
else if (mode == "fptn-runtime-signature-fixture") {
|
||||
let data = fixture_data(ARGV[1]);
|
||||
exit(print_signature_hash(fptn_runtime_signature_body(fixture_section_list(data))) ? 0 : 1);
|
||||
}
|
||||
else if (mode == "dont-touch-dhcp")
|
||||
print(dont_touch_dhcp_value(uci_settings()), "\n");
|
||||
else if (mode == "dont-touch-dhcp-fixture") {
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||
UPDATER_UC="$ROOT_DIR/tachyon/files/usr/lib/components/updater.uc"
|
||||
PACKAGES_UC="$ROOT_DIR/tachyon/files/usr/lib/core/packages.uc"
|
||||
FPTN_RUNTIME_UC="$ROOT_DIR/tachyon/files/usr/lib/providers/fptn/runtime.uc"
|
||||
STATE_UC="$ROOT_DIR/tachyon/files/usr/lib/service/state.uc"
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_eq() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local label="$3"
|
||||
[ "$actual" = "$expected" ] || fail "$label: expected '$expected', got '$actual'"
|
||||
}
|
||||
|
||||
ucode_run() {
|
||||
ucode -L "$TACHYON_LIB" "$@"
|
||||
}
|
||||
|
||||
# 1. Test updater.uc asset selection for FPTN
|
||||
cat >"$WORK_DIR/releases.json" <<'EOF_JSON'
|
||||
[
|
||||
{
|
||||
"tag_name": "v0.4.5",
|
||||
"name": "0.4.5",
|
||||
"html_url": "https://github.com/fptn-project/fptn/releases/tag/v0.4.5",
|
||||
"assets": [
|
||||
{
|
||||
"name": "fptn-client-0.4.5-openwrt-23.05.x-x86_64.ipk",
|
||||
"browser_download_url": "https://github.com/fptn-project/fptn/releases/download/v0.4.5/fptn-client-0.4.5-openwrt-23.05.x-x86_64.ipk"
|
||||
},
|
||||
{
|
||||
"name": "fptn-client-0.4.5-openwrt-24.10.x-x86_64.apk",
|
||||
"browser_download_url": "https://github.com/fptn-project/fptn/releases/download/v0.4.5/fptn-client-0.4.5-openwrt-24.10.x-x86_64.apk"
|
||||
},
|
||||
{
|
||||
"name": "fptn-client-0.4.5-openwrt-24.10.x-aarch64_cortex-a53.apk",
|
||||
"browser_download_url": "https://github.com/fptn-project/fptn/releases/download/v0.4.5/fptn-client-0.4.5-openwrt-24.10.x-aarch64_cortex-a53.apk"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
EOF_JSON
|
||||
|
||||
SELECTED_APK="$(cat "$WORK_DIR/releases.json" | ucode_run "$UPDATER_UC" fptn-select-asset "24.10" "apk" "aarch64_cortex-a53 aarch64_generic")"
|
||||
ARCH_MATCH="$(echo "$SELECTED_APK" | awk '{print $1}')"
|
||||
NAME_MATCH="$(echo "$SELECTED_APK" | awk '{print $2}')"
|
||||
assert_eq "aarch64_cortex-a53" "$ARCH_MATCH" "fptn asset arch match"
|
||||
assert_eq "fptn-client-0.4.5-openwrt-24.10.x-aarch64_cortex-a53.apk" "$NAME_MATCH" "fptn asset name match"
|
||||
|
||||
# 2. Test package version extraction
|
||||
PARSED_VER="$(ucode_run "$UPDATER_UC" updates-arch-package-version "fptn-client-0.4.5-openwrt-24.10.x-x86_64.ipk" "x86_64")"
|
||||
assert_eq "0.4.5-openwrt-24.10.x" "$PARSED_VER" "fptn package version strip"
|
||||
|
||||
# 3. Test packages.uc binary detection and version
|
||||
MOCK_BIN="$WORK_DIR/fptn-client-cli"
|
||||
cat >"$MOCK_BIN" <<'EOF_BIN'
|
||||
#!/usr/bin/env sh
|
||||
echo "fptn-client 0.4.5"
|
||||
EOF_BIN
|
||||
chmod 0755 "$MOCK_BIN"
|
||||
|
||||
export TACHYON_FPTN_BIN="$MOCK_BIN"
|
||||
FPTN_VER="$(TACHYON_LIB_DIR="$TACHYON_LIB" ucode_run "$FPTN_RUNTIME_UC" version)"
|
||||
assert_eq "0.4.5" "$FPTN_VER" "fptn runtime version command"
|
||||
|
||||
FPTN_INSTALLED="$(TACHYON_LIB_DIR="$TACHYON_LIB" ucode_run "$FPTN_RUNTIME_UC" installed && echo "1" || echo "0")"
|
||||
assert_eq "1" "$FPTN_INSTALLED" "fptn runtime installed check"
|
||||
|
||||
# 4. Test state.uc fptn runtime signature fixture
|
||||
cat >"$WORK_DIR/state_fixture.json" <<'EOF_FIXTURE'
|
||||
{
|
||||
"section": [
|
||||
{
|
||||
".name": "sec1",
|
||||
"enabled": "1",
|
||||
"action": "fptn",
|
||||
"access_token": "secret_token_123",
|
||||
"sni": "my.domain.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF_FIXTURE
|
||||
|
||||
SIG_1="$(ucode_run "$STATE_UC" fptn-runtime-signature-fixture "$WORK_DIR/state_fixture.json")"
|
||||
[ -n "$SIG_1" ] || fail "fptn signature should not be empty"
|
||||
|
||||
cat >"$WORK_DIR/state_fixture2.json" <<'EOF_FIXTURE2'
|
||||
{
|
||||
"section": [
|
||||
{
|
||||
".name": "sec1",
|
||||
"enabled": "1",
|
||||
"action": "fptn",
|
||||
"access_token": "secret_token_diff",
|
||||
"sni": "my.domain.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF_FIXTURE2
|
||||
|
||||
SIG_2="$(ucode_run "$STATE_UC" fptn-runtime-signature-fixture "$WORK_DIR/state_fixture2.json")"
|
||||
[ "$SIG_1" != "$SIG_2" ] || fail "fptn signature should differ when token changes"
|
||||
|
||||
echo "fptn component tests passed"
|
||||
@@ -1457,6 +1457,7 @@ zapret2_runtime_signature=$(state_ucode zapret2-runtime-signature-fixture "$WORK
|
||||
byedpi_runtime_signature=$(state_ucode byedpi-runtime-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
wdtt_runtime_signature=$(state_ucode wdtt-runtime-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
olcrtc_runtime_signature=$(state_ucode olcrtc-runtime-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
fptn_runtime_signature=$(state_ucode fptn-runtime-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
list_signature=$(state_ucode list-update-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
hosts_list_signature=$(state_ucode hosts-list-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
cron_signature=$(state_ucode cron-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
|
||||
Reference in New Issue
Block a user