Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e0926be96 | ||
|
|
0d0a4b32ae | ||
|
|
9c6b594328 | ||
|
|
4000be75e0 | ||
|
|
ad3e70d59b | ||
|
|
f45a6f626f | ||
|
|
d65f86ae8e | ||
|
|
9cb985640a | ||
|
|
2ae3f7aa15 | ||
|
|
5f76f9b58e | ||
|
|
51bb2a7e5b | ||
|
|
1dc1924664 | ||
|
|
908eda5844 | ||
|
|
abc232ae11 | ||
|
|
19755418cf | ||
|
|
607c37044e | ||
|
|
4606708a56 | ||
|
|
d8227ca06f | ||
|
|
72bdce8743 | ||
|
|
a27e98ed24 | ||
|
|
346001567c | ||
|
|
cf2e543eb6 |
@@ -15,7 +15,7 @@
|
||||
|
||||
二进制下载:本仓库 [Releases](https://git.881218.xyz/rogue/l-ui/releases),按架构选 `l-ui-linux-amd64` 或 `l-ui-linux-arm64`。
|
||||
|
||||
> 仓库根目录的 `install-master.sh` / `install-agent.sh` 是一键脚本,下载源已迁至本仓 Gitea Releases(tag `v3.7.0-agent.1`),可直接使用;也可按下面步骤手动部署。
|
||||
> 仓库根目录的 `install-master.sh` / `install-agent.sh` 是一键脚本,下载源已迁至本仓 Gitea Releases,可直接使用;主控一键安装:`bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-master.sh)`;也可按下面步骤手动部署。
|
||||
|
||||
### 主控
|
||||
|
||||
@@ -40,7 +40,7 @@ cp geoip.dat geosite.dat /usr/local/l-ui/bin/
|
||||
|
||||
### Agent(节点鸡)
|
||||
|
||||
先在主控侧为该节点生成注册 token(建节点 → 用面板 API Token 签发绑定该节点的 agent token,完整步骤见 [docs/AGENT.md](docs/AGENT.md)),然后在节点鸡上:
|
||||
先在主控侧建节点,面板会生成绑定该节点的注册 token 与安装命令,然后在节点鸡上:
|
||||
|
||||
```bash
|
||||
mkdir -p /usr/local/l-ui/bin /etc/l-ui
|
||||
|
||||
@@ -15,7 +15,7 @@ export function generateStaticParams() {
|
||||
}
|
||||
|
||||
const INSTALL_COMMAND =
|
||||
'bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)';
|
||||
'bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-master.sh)';
|
||||
|
||||
export default async function HomePage({ params }: PageProps<'/[lang]'>) {
|
||||
const { lang } = await params;
|
||||
|
||||
@@ -44,7 +44,7 @@ export function InstallCommandBuilder() {
|
||||
value={version}
|
||||
onChange={setVersion}
|
||||
placeholder="latest"
|
||||
hint="blank = latest stable · a tag like v3.4.0 · or dev-latest for the rolling dev build"
|
||||
hint="blank = latest release · a tag like V2.7.5 (script at that tag) · or dev-latest for the main-branch script"
|
||||
/>
|
||||
{method === 'docker' ? (
|
||||
<>
|
||||
|
||||
@@ -1,47 +1,43 @@
|
||||
---
|
||||
title: Installation
|
||||
description: Install L-UI via the official script (stable, pinned, or dev-latest), unattended/cloud-init, or Docker — and choose SQLite or PostgreSQL.
|
||||
description: Install L-UI via the official script (panel, node agent, or pinned), unattended/cloud-init, or Docker — and choose SQLite or PostgreSQL.
|
||||
icon: Download
|
||||
---
|
||||
|
||||
L-UI runs on a wide range of Linux distributions — Ubuntu, Debian, Armbian,
|
||||
Fedora, CentOS, RHEL, AlmaLinux, Rocky Linux, Oracle Linux, Amazon Linux,
|
||||
Virtuozzo, Arch, Manjaro, openSUSE (Tumbleweed/Leap), Alpine — and Windows,
|
||||
across `amd64`, `386`, `arm64`, `armv7`, `armv6`, `armv5`, and `s390x`.
|
||||
across `amd64` and `arm64`.
|
||||
|
||||
<Callout type="warn">
|
||||
Run the script installer as **root** (or with `sudo`). It installs a service,
|
||||
sets up the `l-ui` management command, and enables the panel on boot.
|
||||
Run the script installer as **root** (or with `sudo`). One-click scripts support `amd64`/`arm64` only.
|
||||
</Callout>
|
||||
|
||||
<Tabs items={['Script', 'Docker', 'Manual']}>
|
||||
|
||||
<Tab value="Script">
|
||||
|
||||
The official script is the recommended path. During installation it generates a
|
||||
**random** username, password, and access (web base) path, sets up the service,
|
||||
and installs the `l-ui` management command.
|
||||
The official script is the recommended path. Panel one-click install:
|
||||
|
||||
```bash title="latest stable"
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```bash title="panel"
|
||||
bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-master.sh)
|
||||
```
|
||||
|
||||
Install a **specific version** by appending its tag:
|
||||
It downloads the panel binary, xray, and geo files from this repo's Gitea Releases and starts the panel (default port 2053). Log in with the default `admin` / `admin` and change the password immediately.
|
||||
|
||||
Node agent install: create the node in the panel first — the panel generates the install command with the registration token; run it as root on the node, for example:
|
||||
|
||||
```bash title="node agent"
|
||||
bash <(curl -fL https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-agent.sh) --master http://MASTER:2053 --token <token> --name <node-name>
|
||||
```
|
||||
|
||||
Install a specific panel version: fetch the script at that tag (the script's default download URL is that version), for example:
|
||||
|
||||
```bash title="pinned version"
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v3.4.0
|
||||
bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/tag/V2.7.5/install-master.sh)
|
||||
```
|
||||
|
||||
Install the rolling **dev** build (the latest per-commit pre-release from `main`
|
||||
— not a stable release) by passing `dev-latest`:
|
||||
|
||||
```bash title="rolling dev build"
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) dev-latest
|
||||
```
|
||||
|
||||
When it finishes, note the printed login details and run `l-ui` to open the
|
||||
[management menu](/docs/guide/update-uninstall#the-l-ui-management-menu), then
|
||||
continue to [First login](/docs/guide/first-login).
|
||||
There is no dev-channel one-click install; the main-branch script tracks the latest release. When it finishes, continue to [First login](/docs/guide/first-login).
|
||||
|
||||
</Tab>
|
||||
|
||||
@@ -61,23 +57,7 @@ lines in `docker-compose.yml` and start with the profile:
|
||||
docker compose --profile postgres up -d
|
||||
```
|
||||
|
||||
Prefer the prebuilt image? It's published to the GitHub Container Registry. The
|
||||
image bundles Fail2ban (for [IP limits](/docs/operations/security)), which bans
|
||||
with `iptables` and therefore needs `NET_ADMIN` (and `NET_RAW` for IPv6) —
|
||||
otherwise bans are logged but never applied:
|
||||
|
||||
```bash title="docker run"
|
||||
docker run -d \
|
||||
--cap-add=NET_ADMIN \
|
||||
--cap-add=NET_RAW \
|
||||
-e LUI_ENABLE_FAIL2BAN=true \
|
||||
-v $PWD/db/:/etc/l-ui/ \
|
||||
-v $PWD/cert/:/root/cert/ \
|
||||
--network=host \
|
||||
--restart=unless-stopped \
|
||||
--name L-UI \
|
||||
ghcr.io/mhsanaei/3x-ui:latest
|
||||
```
|
||||
No prebuilt image is published for this fork — build locally as above. The locally built image also bundles Fail2ban (for [IP limits](/docs/operations/security)); its bans work through `iptables` and therefore need `NET_ADMIN` (`NET_RAW` for IPv6) — the Compose file already includes both; otherwise bans are only logged, never applied.
|
||||
|
||||
The `db/` volume holds the SQLite database (`/etc/l-ui/l-ui.db`) and `cert/`
|
||||
holds TLS certificates, so your data survives upgrades.
|
||||
@@ -86,9 +66,9 @@ holds TLS certificates, so your data survives upgrades.
|
||||
|
||||
<Tab value="Manual">
|
||||
|
||||
For advanced users, download a release archive for your architecture from the
|
||||
[releases page](https://github.com/MHSanaei/L-UI/releases), extract it, and run
|
||||
the binary as a systemd service. The install script automates exactly these
|
||||
For advanced users, download a release binary for your architecture from the
|
||||
[releases page](https://git.881218.xyz/rogue/l-ui/releases) (`l-ui-linux-amd64` or `l-ui-linux-arm64`),
|
||||
place it at `/usr/local/l-ui/l-ui`, and run it as a systemd service following the repo root README. The install script automates exactly these
|
||||
steps, so it's preferred unless you have a specific reason to install by hand.
|
||||
|
||||
</Tab>
|
||||
@@ -114,17 +94,8 @@ migration.
|
||||
|
||||
## Unattended / cloud-init
|
||||
|
||||
The installer also runs **non-interactively** for automation. Set
|
||||
`LUI_NONINTERACTIVE=1` (or run with no TTY) and it installs end-to-end with zero
|
||||
prompts, generating random credentials and writing them to
|
||||
`/etc/l-ui/install-result.env`:
|
||||
|
||||
```bash
|
||||
LUI_NONINTERACTIVE=1 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
The repo's [`deploy/`](https://github.com/MHSanaei/L-UI/tree/main/deploy)
|
||||
directory has ready-made **cloud-init** user-data for unattended installs on any
|
||||
The install script itself takes no interactive input, so it can be used directly for automation. The repo's [`deploy/cloud-init/`](https://git.881218.xyz/rogue/l-ui/src/branch/main/deploy/cloud-init)
|
||||
provides ready-made **cloud-init** user-data for unattended installs on any
|
||||
cloud (Hetzner, AWS, DigitalOcean, Vultr, GCP, Azure, Oracle).
|
||||
|
||||
## Next steps
|
||||
|
||||
@@ -1,48 +1,43 @@
|
||||
---
|
||||
title: نصب
|
||||
description: نصب L-UI با اسکریپت رسمی (نسخهٔ پایدار، نسخهٔ مشخص یا dev-latest)، نصب خودکار/cloud-init یا Docker — و انتخاب میان SQLite و PostgreSQL.
|
||||
description: نصب L-UI با اسکریپت رسمی (پنل، عامل گره یا نسخهٔ مشخص)، نصب خودکار/cloud-init یا Docker — و انتخاب میان SQLite و PostgreSQL.
|
||||
icon: Download
|
||||
---
|
||||
|
||||
L-UI روی طیف گستردهای از توزیعهای Linux اجرا میشود — Ubuntu، Debian، Armbian،
|
||||
Fedora، CentOS، RHEL، AlmaLinux، Rocky Linux، Oracle Linux، Amazon Linux،
|
||||
Virtuozzo، Arch، Manjaro، openSUSE (Tumbleweed/Leap)، Alpine — و همچنین Windows،
|
||||
روی معماریهای `amd64`، `386`، `arm64`، `armv7`، `armv6`، `armv5` و `s390x`.
|
||||
روی معماریهای `amd64` و `arm64`.
|
||||
|
||||
<Callout type="warn">
|
||||
نصبکنندهٔ اسکریپتی را بهصورت **root** (یا با `sudo`) اجرا کنید. این اسکریپت یک
|
||||
سرویس نصب میکند، دستور مدیریتی `l-ui` را راهاندازی میکند و پنل را در زمان
|
||||
بوت فعال میسازد.
|
||||
نصبکنندهٔ اسکریپتی را بهصورت **root** (یا با `sudo`) اجرا کنید. اسکریپتهای یککلیکه فقط از `amd64`/`arm64` پشتیبانی میکنند.
|
||||
</Callout>
|
||||
|
||||
<Tabs items={['Script', 'Docker', 'Manual']}>
|
||||
|
||||
<Tab value="Script">
|
||||
|
||||
اسکریپت رسمی مسیر توصیهشده است. این اسکریپت در طول نصب یک نام کاربری، رمز عبور و
|
||||
مسیر دسترسی (مسیر پایهٔ وب) **تصادفی** تولید میکند، سرویس را راهاندازی میکند و
|
||||
دستور مدیریتی `l-ui` را نصب میکند.
|
||||
اسکریپت رسمی مسیر توصیهشده است. نصب یککلیکهٔ پنل:
|
||||
|
||||
```bash title="latest stable"
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```bash title="پنل"
|
||||
bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-master.sh)
|
||||
```
|
||||
|
||||
برای نصب یک **نسخهٔ مشخص**، تگ آن را در انتها اضافه کنید:
|
||||
این اسکریپت باینری پنل، xray و فایلهای geo را از Releases همین مخزن (Gitea) دانلود و پنل را اجرا میکند (درگاه پیشفرض 2053). با حساب پیشفرض `admin` / `admin` وارد شوید و بلافاصله رمز عبور را تغییر دهید.
|
||||
|
||||
```bash title="pinned version"
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v3.4.0
|
||||
نصب عامل گره: نخست گره را در پنل بسازید — پنل دستور نصب همراه با توکن ثبت را تولید میکند؛ آن را بهصورت root روی گره اجرا کنید، برای نمونه:
|
||||
|
||||
```bash title="عامل گره"
|
||||
bash <(curl -fL https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-agent.sh) --master http://MASTER:2053 --token <token> --name <node-name>
|
||||
```
|
||||
|
||||
برای نصب نسخهٔ **dev** غلتان (آخرین پیشانتشار بهازای هر کامیت از شاخهٔ `main`
|
||||
— نه یک نسخهٔ پایدار) عبارت `dev-latest` را پاس دهید:
|
||||
برای نصب یک نسخهٔ مشخص از پنل، اسکریپت همان تگ را بگیرید (نشانی دانلود پیشفرض اسکریپت همان نسخه است)، برای نمونه:
|
||||
|
||||
```bash title="rolling dev build"
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) dev-latest
|
||||
```bash title="نسخهٔ مشخص"
|
||||
bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/tag/V2.7.5/install-master.sh)
|
||||
```
|
||||
|
||||
پس از پایان نصب، مشخصات ورود چاپشده را یادداشت کنید و دستور `l-ui` را اجرا کنید تا
|
||||
[منوی مدیریت](/docs/guide/update-uninstall#the-l-ui-management-menu) باز شود، سپس
|
||||
به [نخستین ورود](/docs/guide/first-login) ادامه دهید.
|
||||
نصب یککلیکهٔ کانال dev وجود ندارد؛ اسکریپت شاخهٔ main همان آخرین انتشار است.
|
||||
|
||||
</Tab>
|
||||
|
||||
@@ -62,23 +57,7 @@ docker compose up -d
|
||||
docker compose --profile postgres up -d
|
||||
```
|
||||
|
||||
ترجیح میدهید از ایمیج ازپیشساخته استفاده کنید؟ این ایمیج روی GitHub Container Registry
|
||||
منتشر شده است. این ایمیج Fail2ban را بههمراه دارد (برای [محدودیتهای IP](/docs/operations/security))
|
||||
که با `iptables` مسدودسازی انجام میدهد و بنابراین به `NET_ADMIN` (و `NET_RAW` برای IPv6)
|
||||
نیاز دارد — در غیر این صورت مسدودسازیها فقط ثبت میشوند ولی هرگز اعمال نمیشوند:
|
||||
|
||||
```bash title="docker run"
|
||||
docker run -d \
|
||||
--cap-add=NET_ADMIN \
|
||||
--cap-add=NET_RAW \
|
||||
-e LUI_ENABLE_FAIL2BAN=true \
|
||||
-v $PWD/db/:/etc/l-ui/ \
|
||||
-v $PWD/cert/:/root/cert/ \
|
||||
--network=host \
|
||||
--restart=unless-stopped \
|
||||
--name L-UI \
|
||||
ghcr.io/mhsanaei/3x-ui:latest
|
||||
```
|
||||
برای این فورک ایمیج ازپیشساختهای منتشر نمیشود — مانند بالا بهصورت محلی بسازید. ایمیج ساختهشدهٔ محلی نیز Fail2ban را بههمراه دارد (برای [محدودیتهای IP](/docs/operations/security))؛ مسدودسازی آن از طریق `iptables` انجام میشود و به `NET_ADMIN` (و `NET_RAW` برای IPv6) نیاز دارد — فایل Compose هر دو را شامل میشود؛ در غیر این صورت مسدودسازیها فقط ثبت میشوند ولی هرگز اعمال نمیشوند.
|
||||
|
||||
والیوم `db/` پایگاهدادهٔ SQLite (`/etc/l-ui/l-ui.db`) را نگه میدارد و `cert/`
|
||||
گواهیهای TLS را، بنابراین دادههای شما در ارتقاها حفظ میشوند.
|
||||
@@ -87,9 +66,8 @@ docker run -d \
|
||||
|
||||
<Tab value="Manual">
|
||||
|
||||
برای کاربران پیشرفته، یک آرشیو انتشار متناسب با معماری خود را از
|
||||
[صفحهٔ releases](https://github.com/MHSanaei/L-UI/releases) دانلود کنید، آن را
|
||||
استخراج کنید و باینری را بهعنوان یک سرویس systemd اجرا کنید. اسکریپت نصب دقیقاً
|
||||
برای کاربران پیشرفته، یک باینری انتشار متناسب با معماری خود را از
|
||||
[صفحهٔ releases](https://git.881218.xyz/rogue/l-ui/releases) دانلود کنید (`l-ui-linux-amd64` یا `l-ui-linux-arm64`)، آن را در مسیر `/usr/local/l-ui/l-ui` قرار دهید و طبق README ریشهٔ مخزن آن را بهعنوان یک سرویس systemd اجرا کنید. اسکریپت نصب دقیقاً
|
||||
همین مراحل را خودکار میکند، بنابراین مگر اینکه دلیل خاصی برای نصب دستی داشته
|
||||
باشید، روش اسکریپتی ترجیح داده میشود.
|
||||
|
||||
@@ -116,18 +94,7 @@ docker run -d \
|
||||
|
||||
## نصب خودکار / cloud-init
|
||||
|
||||
نصبکننده برای خودکارسازی بهصورت **غیرتعاملی** نیز اجرا میشود.
|
||||
`LUI_NONINTERACTIVE=1` را تنظیم کنید (یا بدون TTY اجرا کنید) تا نصب بهصورت
|
||||
سرتاسری و بدون هیچ پرسشی انجام شود، مشخصات تصادفی تولید کند و آنها را در
|
||||
`/etc/l-ui/install-result.env` بنویسد:
|
||||
|
||||
```bash
|
||||
LUI_NONINTERACTIVE=1 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
دایرکتوری [`deploy/`](https://github.com/MHSanaei/L-UI/tree/main/deploy) در مخزن
|
||||
یک user-data آمادهٔ **cloud-init** برای نصبهای خودکار روی هر ابری (Hetzner، AWS،
|
||||
DigitalOcean، Vultr، GCP، Azure، Oracle) دارد.
|
||||
خود اسکریپت نصب هیچ ورودی تعاملی نمیگیرد و میتوان آن را مستقیماً برای خودکارسازی به کار برد. دایرکتوری [`deploy/cloud-init/`](https://git.881218.xyz/rogue/l-ui/src/branch/main/deploy/cloud-init) در مخزن، یک user-data آمادهٔ **cloud-init** برای نصبهای خودکار روی هر ابری (Hetzner، AWS، DigitalOcean، Vultr، GCP، Azure، Oracle) دارد.
|
||||
|
||||
## گامهای بعدی
|
||||
|
||||
|
||||
@@ -1,48 +1,43 @@
|
||||
---
|
||||
title: Установка
|
||||
description: Установите L-UI с помощью официального скрипта (стабильная, фиксированная или dev-сборка), в автоматическом режиме / через cloud-init либо в Docker — и выберите SQLite или PostgreSQL.
|
||||
description: Установите L-UI с помощью официального скрипта (панель, агент узла или фиксированная версия), в автоматическом режиме / через cloud-init либо в Docker — и выберите SQLite или PostgreSQL.
|
||||
icon: Download
|
||||
---
|
||||
|
||||
L-UI работает на широком спектре дистрибутивов Linux — Ubuntu, Debian, Armbian,
|
||||
Fedora, CentOS, RHEL, AlmaLinux, Rocky Linux, Oracle Linux, Amazon Linux,
|
||||
Virtuozzo, Arch, Manjaro, openSUSE (Tumbleweed/Leap), Alpine — а также в Windows,
|
||||
на архитектурах `amd64`, `386`, `arm64`, `armv7`, `armv6`, `armv5` и `s390x`.
|
||||
на архитектурах `amd64` и `arm64`.
|
||||
|
||||
<Callout type="warn">
|
||||
Запускайте установочный скрипт от имени **root** (или через `sudo`). Он
|
||||
устанавливает службу, настраивает команду управления `l-ui` и включает
|
||||
автозапуск панели при загрузке.
|
||||
Запускайте установочный скрипт от имени **root** (или через `sudo`). Скрипты в один клик поддерживают только `amd64`/`arm64`.
|
||||
</Callout>
|
||||
|
||||
<Tabs items={['Script', 'Docker', 'Manual']}>
|
||||
|
||||
<Tab value="Script">
|
||||
|
||||
Официальный скрипт — рекомендуемый способ. В процессе установки он генерирует
|
||||
**случайные** имя пользователя, пароль и путь доступа (базовый веб-путь),
|
||||
настраивает службу и устанавливает команду управления `l-ui`.
|
||||
Официальный скрипт — рекомендуемый способ. Установка панели в один клик:
|
||||
|
||||
```bash title="latest stable"
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```bash title="панель"
|
||||
bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-master.sh)
|
||||
```
|
||||
|
||||
Чтобы установить **конкретную версию**, добавьте её тег:
|
||||
Скрипт скачивает бинарный файл панели, xray и geo-файлы из Releases этого репозитория (Gitea) и запускает панель (порт по умолчанию 2053). Войдите с учётной записью по умолчанию `admin` / `admin` и сразу же смените пароль.
|
||||
|
||||
```bash title="pinned version"
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v3.4.0
|
||||
Установка агента узла: сначала создайте узел в панели — панель сгенерирует команду установки с токеном регистрации; выполните её от имени root на узле, например:
|
||||
|
||||
```bash title="агент узла"
|
||||
bash <(curl -fL https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-agent.sh) --master http://MASTER:2053 --token <token> --name <node-name>
|
||||
```
|
||||
|
||||
Чтобы установить скользящую **dev**-сборку (последний предварительный релиз по
|
||||
каждому коммиту из `main` — не стабильный релиз), передайте `dev-latest`:
|
||||
Чтобы установить конкретную версию панели, возьмите скрипт этого тега (адрес загрузки по умолчанию в скрипте — эта версия), например:
|
||||
|
||||
```bash title="rolling dev build"
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) dev-latest
|
||||
```bash title="фиксированная версия"
|
||||
bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/tag/V2.7.5/install-master.sh)
|
||||
```
|
||||
|
||||
По завершении запишите выведенные данные для входа и выполните `l-ui`, чтобы
|
||||
открыть [меню управления](/docs/guide/update-uninstall#the-l-ui-management-menu),
|
||||
а затем переходите к разделу [Первый вход](/docs/guide/first-login).
|
||||
Однокликовой установки dev-канала нет; скрипт из ветки main соответствует последнему релизу.
|
||||
|
||||
</Tab>
|
||||
|
||||
@@ -62,23 +57,7 @@ docker compose up -d
|
||||
docker compose --profile postgres up -d
|
||||
```
|
||||
|
||||
Предпочитаете готовый образ? Он публикуется в GitHub Container Registry. Образ
|
||||
включает Fail2ban (для [ограничений по IP](/docs/operations/security)), который
|
||||
блокирует через `iptables` и поэтому требует `NET_ADMIN` (а для IPv6 — ещё и
|
||||
`NET_RAW`); в противном случае блокировки логируются, но никогда не применяются:
|
||||
|
||||
```bash title="docker run"
|
||||
docker run -d \
|
||||
--cap-add=NET_ADMIN \
|
||||
--cap-add=NET_RAW \
|
||||
-e LUI_ENABLE_FAIL2BAN=true \
|
||||
-v $PWD/db/:/etc/l-ui/ \
|
||||
-v $PWD/cert/:/root/cert/ \
|
||||
--network=host \
|
||||
--restart=unless-stopped \
|
||||
--name L-UI \
|
||||
ghcr.io/mhsanaei/3x-ui:latest
|
||||
```
|
||||
Готовый образ для этого форка не публикуется — собирайте локально, как выше. Локально собранный образ также включает Fail2ban (для [ограничений по IP](/docs/operations/security)); блокировки работают через `iptables` и требуют `NET_ADMIN` (а для IPv6 — ещё и `NET_RAW`) — Compose-файл уже включает оба; в противном случае блокировки логируются, но никогда не применяются.
|
||||
|
||||
Том `db/` хранит базу данных SQLite (`/etc/l-ui/l-ui.db`), а `cert/` — TLS-сертификаты,
|
||||
поэтому ваши данные сохраняются при обновлениях.
|
||||
@@ -87,9 +66,8 @@ docker run -d \
|
||||
|
||||
<Tab value="Manual">
|
||||
|
||||
Опытные пользователи могут скачать архив релиза для своей архитектуры со
|
||||
[страницы релизов](https://github.com/MHSanaei/L-UI/releases), распаковать его и
|
||||
запустить бинарный файл как службу systemd. Установочный скрипт автоматизирует
|
||||
Опытные пользователи могут скачать бинарный файл релиза для своей архитектуры со
|
||||
[страницы релизов](https://git.881218.xyz/rogue/l-ui/releases) (`l-ui-linux-amd64` или `l-ui-linux-arm64`), поместить его в `/usr/local/l-ui/l-ui` и запустить как службу systemd согласно README в корне репозитория. Установочный скрипт автоматизирует
|
||||
ровно эти шаги, поэтому он предпочтительнее, если только у вас нет особой причины
|
||||
устанавливать вручную.
|
||||
|
||||
@@ -117,18 +95,7 @@ docker run -d \
|
||||
|
||||
## Автоматическая установка / cloud-init
|
||||
|
||||
Установщик также работает **в неинтерактивном режиме** для автоматизации.
|
||||
Задайте `LUI_NONINTERACTIVE=1` (или запустите без TTY), и он выполнит установку
|
||||
от начала до конца без единого запроса, сгенерировав случайные учётные данные и
|
||||
записав их в `/etc/l-ui/install-result.env`:
|
||||
|
||||
```bash
|
||||
LUI_NONINTERACTIVE=1 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
В каталоге [`deploy/`](https://github.com/MHSanaei/L-UI/tree/main/deploy)
|
||||
репозитория есть готовые **cloud-init** user-data для автоматической установки в
|
||||
любом облаке (Hetzner, AWS, DigitalOcean, Vultr, GCP, Azure, Oracle).
|
||||
Сам скрипт установки не требует интерактивного ввода, поэтому его можно напрямую использовать для автоматизации. Каталог [`deploy/cloud-init/`](https://git.881218.xyz/rogue/l-ui/src/branch/main/deploy/cloud-init) репозитория содержит готовые **cloud-init** user-data для автоматической установки в любом облаке (Hetzner, AWS, DigitalOcean, Vultr, GCP, Azure, Oracle).
|
||||
|
||||
## Дальнейшие шаги
|
||||
|
||||
|
||||
@@ -1,46 +1,43 @@
|
||||
---
|
||||
title: 安装
|
||||
description: 通过官方脚本(稳定版、指定版本或开发最新版)、无人值守 / cloud-init 或 Docker 安装 L-UI,并选择 SQLite 或 PostgreSQL。
|
||||
description: 通过官方脚本(主控、节点 agent、指定版本)、无人值守 / cloud-init 或 Docker 安装 L-UI,并选择 SQLite 或 PostgreSQL。
|
||||
icon: Download
|
||||
---
|
||||
|
||||
L-UI 可运行在多种 Linux 发行版上——Ubuntu、Debian、Armbian、
|
||||
Fedora、CentOS、RHEL、AlmaLinux、Rocky Linux、Oracle Linux、Amazon Linux、
|
||||
Virtuozzo、Arch、Manjaro、openSUSE(Tumbleweed/Leap)、Alpine——以及 Windows,
|
||||
覆盖 `amd64`、`386`、`arm64`、`armv7`、`armv6`、`armv5` 和 `s390x` 架构。
|
||||
覆盖 `amd64` 和 `arm64` 架构。
|
||||
|
||||
<Callout type="warn">
|
||||
请以 **root** 身份(或使用 `sudo`)运行脚本安装程序。它会安装服务、
|
||||
配置 `l-ui` 管理命令,并将面板设为开机自启。
|
||||
请以 **root** 身份(或使用 `sudo`)运行脚本安装程序。一键脚本目前只支持 `amd64`/`arm64`。
|
||||
</Callout>
|
||||
|
||||
<Tabs items={['Script', 'Docker', 'Manual']}>
|
||||
|
||||
<Tab value="Script">
|
||||
|
||||
官方脚本是推荐的安装方式。安装过程中,它会生成一个**随机**的用户名、密码和访问(Web 根)路径,配置服务,
|
||||
并安装 `l-ui` 管理命令。
|
||||
官方脚本是推荐的安装方式。主控一键安装:
|
||||
|
||||
```bash title="latest stable"
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```bash title="主控"
|
||||
bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-master.sh)
|
||||
```
|
||||
|
||||
在命令后附加标签即可安装**指定版本**:
|
||||
它会从本仓 Gitea Releases 下载主控二进制与 xray、geo 文件并启动面板(默认端口 2053)。安装完成后用默认账号 `admin` / `admin` 登录,并立即修改密码。
|
||||
|
||||
```bash title="pinned version"
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v3.4.0
|
||||
节点 agent 安装:先在主控面板建节点,面板会生成带注册 token 的安装命令,在节点鸡上以 root 运行,例如:
|
||||
|
||||
```bash title="节点 agent"
|
||||
bash <(curl -fL https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-agent.sh) --master http://主控地址:2053 --token <注册token> --name <节点名>
|
||||
```
|
||||
|
||||
传入 `dev-latest` 可安装滚动更新的**开发版**构建(来自 `main` 的最新逐提交预发布版本
|
||||
——并非稳定版):
|
||||
安装指定版本的主控:取对应 tag 下的脚本(脚本内的默认下载地址即该版本),例如:
|
||||
|
||||
```bash title="rolling dev build"
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) dev-latest
|
||||
```bash title="指定版本"
|
||||
bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/tag/V2.7.5/install-master.sh)
|
||||
```
|
||||
|
||||
安装完成后,记下打印出的登录信息并运行 `l-ui` 打开
|
||||
[管理菜单](/docs/guide/update-uninstall#the-l-ui-management-menu),然后
|
||||
继续阅读[首次登录](/docs/guide/first-login)。
|
||||
本 fork 不提供 dev 通道一键安装;main 分支的脚本即最新发版。装好后继续阅读[首次登录](/docs/guide/first-login)。
|
||||
|
||||
</Tab>
|
||||
|
||||
@@ -60,23 +57,9 @@ docker compose up -d
|
||||
docker compose --profile postgres up -d
|
||||
```
|
||||
|
||||
更想用预构建镜像?它已发布到 GitHub Container Registry。该镜像内置了 Fail2ban(用于
|
||||
本 fork 不发布预构建镜像,请用上面本地构建的方式安装。本地构建的镜像同样内置 Fail2ban(用于
|
||||
[IP 限制](/docs/operations/security)),其封禁通过 `iptables` 实现,因此需要 `NET_ADMIN`
|
||||
(IPv6 还需 `NET_RAW`)——
|
||||
否则封禁只会被记录,而不会实际生效:
|
||||
|
||||
```bash title="docker run"
|
||||
docker run -d \
|
||||
--cap-add=NET_ADMIN \
|
||||
--cap-add=NET_RAW \
|
||||
-e LUI_ENABLE_FAIL2BAN=true \
|
||||
-v $PWD/db/:/etc/l-ui/ \
|
||||
-v $PWD/cert/:/root/cert/ \
|
||||
--network=host \
|
||||
--restart=unless-stopped \
|
||||
--name L-UI \
|
||||
ghcr.io/mhsanaei/3x-ui:latest
|
||||
```
|
||||
(IPv6 还需 `NET_RAW`)——Compose 文件里已包含这两项,否则封禁只会被记录,而不会实际生效。
|
||||
|
||||
`db/` 卷保存 SQLite 数据库(`/etc/l-ui/l-ui.db`),`cert/`
|
||||
保存 TLS 证书,因此你的数据可在升级后保留。
|
||||
@@ -85,9 +68,9 @@ docker run -d \
|
||||
|
||||
<Tab value="Manual">
|
||||
|
||||
高级用户可从[发布页面](https://github.com/MHSanaei/L-UI/releases)
|
||||
下载适用于你架构的发布归档包,解压后
|
||||
将二进制文件作为 systemd 服务运行。安装脚本正是将这些步骤自动化,
|
||||
高级用户可从[发布页面](https://git.881218.xyz/rogue/l-ui/releases)
|
||||
下载适用于你架构的发布二进制(`l-ui-linux-amd64` 或 `l-ui-linux-arm64`),
|
||||
放到 `/usr/local/l-ui/l-ui` 并按仓库根目录 README 的步骤以 systemd 服务运行。安装脚本正是将这些步骤自动化,
|
||||
因此除非你有特定理由要手动安装,否则更推荐使用脚本。
|
||||
|
||||
</Tab>
|
||||
@@ -112,17 +95,8 @@ docker run -d \
|
||||
|
||||
## 无人值守 / cloud-init
|
||||
|
||||
安装程序也可**非交互式**运行,用于自动化。设置
|
||||
`LUI_NONINTERACTIVE=1`(或在无 TTY 的环境下运行),它便会全程零提示地完成
|
||||
端到端安装,生成随机凭据并写入
|
||||
`/etc/l-ui/install-result.env`:
|
||||
|
||||
```bash
|
||||
LUI_NONINTERACTIVE=1 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
仓库的 [`deploy/`](https://github.com/MHSanaei/L-UI/tree/main/deploy)
|
||||
目录提供了现成的 **cloud-init** user-data,可用于在任意云平台上进行无人值守安装
|
||||
安装脚本本身无交互提示,可直接用于自动化。仓库的 [`deploy/cloud-init/`](https://git.881218.xyz/rogue/l-ui/src/branch/main/deploy/cloud-init)
|
||||
提供现成的 **cloud-init** user-data,可用于在任意云平台上进行无人值守安装
|
||||
(Hetzner、AWS、DigitalOcean、Vultr、GCP、Azure、Oracle)。
|
||||
|
||||
## 后续步骤
|
||||
|
||||
@@ -15,23 +15,23 @@ const base: InstallOptions = {
|
||||
};
|
||||
|
||||
describe('buildScriptCommand', () => {
|
||||
it('uses the master install.sh for the latest version', () => {
|
||||
it('uses the Gitea install-master.sh for the latest version', () => {
|
||||
expect(buildScriptCommand(base)).toBe(
|
||||
'bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)',
|
||||
'bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-master.sh)',
|
||||
);
|
||||
});
|
||||
|
||||
it('pins a specific version by passing the tag to master install.sh', () => {
|
||||
const cmd = buildScriptCommand({ ...base, version: 'v3.4.1' });
|
||||
it('pins a specific version by fetching the script at that tag', () => {
|
||||
const cmd = buildScriptCommand({ ...base, version: 'V2.7.5' });
|
||||
expect(cmd).toBe(
|
||||
'bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v3.4.1',
|
||||
'bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/tag/V2.7.5/install-master.sh)',
|
||||
);
|
||||
});
|
||||
|
||||
it('supports the rolling dev-latest build', () => {
|
||||
it('uses the main-branch script for the rolling dev build', () => {
|
||||
const cmd = buildScriptCommand({ ...base, version: 'dev-latest' });
|
||||
expect(cmd).toBe(
|
||||
'bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) dev-latest',
|
||||
'bash <(curl -Ls https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-master.sh)',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -47,7 +47,7 @@ describe('buildDockerRun', () => {
|
||||
expect(cmd).toContain('LUI_ENABLE_FAIL2BAN=false');
|
||||
expect(cmd).toContain('LUI_PORT=8443');
|
||||
expect(cmd).toContain('LUI_INIT_WEB_BASE_PATH=/panel');
|
||||
expect(cmd).toContain('ghcr.io/mhsanaei/3x-ui:latest');
|
||||
expect(cmd).toContain('l-ui:latest');
|
||||
expect(cmd).toContain('-v $PWD/db/:/etc/l-ui/');
|
||||
});
|
||||
|
||||
@@ -59,9 +59,10 @@ describe('buildDockerRun', () => {
|
||||
});
|
||||
|
||||
describe('buildDockerCompose', () => {
|
||||
it('produces valid-looking compose with the image and volumes', () => {
|
||||
it('produces valid-looking compose with local build and volumes', () => {
|
||||
const yaml = buildDockerCompose({ ...base, panelPort: '2096' });
|
||||
expect(yaml).toContain('image: ghcr.io/mhsanaei/3x-ui:latest');
|
||||
expect(yaml).toContain('build:');
|
||||
expect(yaml).toContain('dockerfile: ./Dockerfile');
|
||||
expect(yaml).toContain('network_mode: host');
|
||||
expect(yaml).toContain("LUI_PORT: '2096'");
|
||||
expect(yaml).toContain('- ./db/:/etc/l-ui/');
|
||||
|
||||
+20
-16
@@ -4,15 +4,16 @@ export type InstallMethod = 'script' | 'docker';
|
||||
|
||||
export interface InstallOptions {
|
||||
method: InstallMethod;
|
||||
/** A release tag like `v3.4.1`, or empty/`latest` for the latest release. */
|
||||
/** A release tag like `V2.7.5`, or empty/`latest` for the latest release. */
|
||||
version: string;
|
||||
enableFail2ban: boolean;
|
||||
panelPort: string;
|
||||
webBasePath: string;
|
||||
}
|
||||
|
||||
const REPO_RAW = 'https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh';
|
||||
const IMAGE = 'ghcr.io/mhsanaei/3x-ui:latest';
|
||||
const SCRIPT_URL = 'https://git.881218.xyz/rogue/l-ui/raw/branch/main/install-master.sh';
|
||||
const scriptTagUrl = (tag: string) =>
|
||||
`https://git.881218.xyz/rogue/l-ui/raw/tag/${tag}/install-master.sh`;
|
||||
|
||||
function isLatest(version: string): boolean {
|
||||
const v = version.trim().toLowerCase();
|
||||
@@ -20,20 +21,22 @@ function isLatest(version: string): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* The one-line script install command. The master install script reads the
|
||||
* version as its first argument: empty = latest stable release, a tag like
|
||||
* `v3.4.0` = that release, and `dev-latest` = the rolling per-commit dev build.
|
||||
* The one-line panel install command. install-master.sh takes no version
|
||||
* argument: empty/`latest`/`dev-latest` all use the main-branch script
|
||||
* (which tracks the latest release); a tag pins by fetching the script
|
||||
* at that tag, whose default download URL is that release.
|
||||
*/
|
||||
export function buildScriptCommand(options: InstallOptions): string {
|
||||
if (isLatest(options.version)) {
|
||||
return `bash <(curl -Ls ${REPO_RAW})`;
|
||||
const v = options.version.trim();
|
||||
if (isLatest(v) || v.toLowerCase() === 'dev-latest') {
|
||||
return `bash <(curl -Ls ${SCRIPT_URL})`;
|
||||
}
|
||||
return `bash <(curl -Ls ${REPO_RAW}) ${options.version.trim()}`;
|
||||
return `bash <(curl -Ls ${scriptTagUrl(v)})`;
|
||||
}
|
||||
|
||||
/** A `docker run` command reflecting the chosen options. */
|
||||
/** A `docker build` + `docker run` pair for a locally built image (no prebuilt L-UI image is published). */
|
||||
export function buildDockerRun(options: InstallOptions): string {
|
||||
const lines = ['docker run -itd'];
|
||||
const lines = ['docker build -t l-ui . && docker run -itd'];
|
||||
lines.push(` -e XRAY_VMESS_AEAD_FORCED=false`);
|
||||
lines.push(` -e LUI_ENABLE_FAIL2BAN=${options.enableFail2ban ? 'true' : 'false'}`);
|
||||
if (options.panelPort.trim()) lines.push(` -e LUI_PORT=${options.panelPort.trim()}`);
|
||||
@@ -44,24 +47,25 @@ export function buildDockerRun(options: InstallOptions): string {
|
||||
lines.push(` --network=host`);
|
||||
lines.push(` --restart=unless-stopped`);
|
||||
lines.push(` --name L-UI`);
|
||||
lines.push(` ${IMAGE}`);
|
||||
lines.push(` l-ui:latest`);
|
||||
return lines.join(' \\\n');
|
||||
}
|
||||
|
||||
/** A `docker-compose.yml` reflecting the chosen options. */
|
||||
/** A `docker-compose.yml` building the image locally (mirrors the repo's own compose file). */
|
||||
export function buildDockerCompose(options: InstallOptions): string {
|
||||
const env: string[] = [
|
||||
` XRAY_VMESS_AEAD_FORCED: 'false'`,
|
||||
` LUI_ENABLE_FAIL2BAN: '${options.enableFail2ban ? 'true' : 'false'}'`,
|
||||
];
|
||||
if (options.panelPort.trim()) env.push(` LUI_PORT: '${options.panelPort.trim()}'`);
|
||||
if (options.webBasePath.trim())
|
||||
env.push(` LUI_INIT_WEB_BASE_PATH: '${options.webBasePath.trim()}'`);
|
||||
if (options.webBasePath.trim()) env.push(` LUI_INIT_WEB_BASE_PATH: '${options.webBasePath.trim()}'`);
|
||||
|
||||
return [
|
||||
`services:`,
|
||||
` L-UI:`,
|
||||
` image: ${IMAGE}`,
|
||||
` build:`,
|
||||
` context: .`,
|
||||
` dockerfile: ./Dockerfile`,
|
||||
` container_name: L-UI`,
|
||||
` volumes:`,
|
||||
` - ./db/:/etc/l-ui/`,
|
||||
|
||||
@@ -18,7 +18,6 @@ import { BulkAddHostSchema, type BulkAddHostValues } from '@/schemas/api/host';
|
||||
import type { InboundOption } from '@/schemas/client';
|
||||
import { ALPN_OPTION, UTLS_FINGERPRINT } from '@/schemas/primitives';
|
||||
import { FormField, rhfZodValidate } from '@/components/form/rhf';
|
||||
import { useNodesQuery } from '@/api/queries/useNodesQuery';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { catTabLabel } from '@/pages/settings/catTabLabel';
|
||||
import { HostFinalMaskForm, HostMuxForm, HostSockoptForm } from './json-forms';
|
||||
@@ -48,6 +47,8 @@ function defaultsFor(host: HostRecord | null): FormShape {
|
||||
serverDescription: host?.serverDescription ?? '',
|
||||
enable: host ? !host.isDisabled : true,
|
||||
isHidden: host?.isHidden ?? false,
|
||||
// tags / nodeGuids are no longer rendered in the dialog, but stay in form
|
||||
// state so editing a host preserves its stored values (save is full replace).
|
||||
tags: host?.tags ?? [],
|
||||
port: host?.port ?? 0,
|
||||
security: (host?.security as BulkAddHostValues['security']) ?? 'same',
|
||||
@@ -106,8 +107,6 @@ export default function HostFormModal({
|
||||
}
|
||||
}
|
||||
|
||||
const { nodes } = useNodesQuery();
|
||||
|
||||
const inboundSelectOptions = useMemo(
|
||||
() =>
|
||||
inboundOptions.map((ib) => ({
|
||||
@@ -117,14 +116,6 @@ export default function HostFormModal({
|
||||
[inboundOptions],
|
||||
);
|
||||
|
||||
const nodeSelectOptions = useMemo(
|
||||
() =>
|
||||
nodes
|
||||
.filter((n) => n.guid)
|
||||
.map((n) => ({ value: n.guid as string, label: n.name || n.remark || (n.guid as string) })),
|
||||
[nodes],
|
||||
);
|
||||
|
||||
const alpnOptions = useMemo(
|
||||
() => Object.values(ALPN_OPTION).map((v) => ({ value: v, label: v })),
|
||||
[],
|
||||
@@ -252,25 +243,6 @@ export default function HostFormModal({
|
||||
>
|
||||
<InputNumber min={0} max={65535} />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="tags"
|
||||
label={t('pages.hosts.fields.tags')}
|
||||
tooltip={t('pages.hosts.hints.tags')}
|
||||
>
|
||||
<Select mode="tags" allowClear tokenSeparators={[',']} />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="nodeGuids"
|
||||
label={t('pages.hosts.fields.nodeGuids')}
|
||||
tooltip={t('pages.hosts.hints.nodeGuids')}
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
options={nodeSelectOptions}
|
||||
showSearch={{ optionFilterProp: 'label' }}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
name="enable"
|
||||
label={t('pages.hosts.fields.enable')}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Alert, Button, Col, Form, Input, Modal, Row, Switch, message } from 'antd';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
@@ -7,7 +7,7 @@ import { HttpUtil, type Msg } from '@/utils';
|
||||
import { NodeFormSchema, type NodeFormValues } from '@/schemas/node';
|
||||
import { FormField, rhfZodValidate } from '@/components/form/rhf';
|
||||
import { currentPanelMasterUrl } from './agentMasterUrl';
|
||||
import ProbeMetaSection from './ProbeMetaSection';
|
||||
import ProbeMetaSection, { type ProbeMetaDraftHandle } from './ProbeMetaSection';
|
||||
|
||||
import './NodeFormModal.css';
|
||||
|
||||
@@ -63,6 +63,14 @@ export default function NodeFormModal({
|
||||
} | null>(null);
|
||||
const [editMasterUrl, setEditMasterUrl] = useState('');
|
||||
|
||||
// Create-mode probe draft: the section below has no node id until
|
||||
// the node exists, so it keeps local state and the dialog reads it
|
||||
// through this ref after creation. The key remounts it on every
|
||||
// add-open (the modal itself stays mounted across opens), so a
|
||||
// previous add's draft never leaks into the next one.
|
||||
const probeDraftRef = useRef<ProbeMetaDraftHandle | null>(null);
|
||||
const [probeDraftKey, setProbeDraftKey] = useState(0);
|
||||
|
||||
// Reset during render, not in an effect, so the first frame is already clean.
|
||||
const [synced, setSynced] = useState<{ mode: string; node: NodeRecord | null } | null>(null);
|
||||
if (!open) {
|
||||
@@ -80,6 +88,7 @@ export default function NodeFormModal({
|
||||
}
|
||||
: base;
|
||||
methods.reset(next);
|
||||
if (mode === 'add') setProbeDraftKey((k) => k + 1);
|
||||
}
|
||||
|
||||
// Load the stored install material when editing a node so the
|
||||
@@ -160,6 +169,25 @@ export default function NodeFormModal({
|
||||
| { agentToken?: string; node?: { id?: number } }
|
||||
| undefined;
|
||||
if (mode === 'add' && obj?.agentToken) {
|
||||
// Probe meta (create mode): the section above is a local
|
||||
// draft — it had no node id until now. Submit it only if
|
||||
// the user touched it; the agent row is created together
|
||||
// with the node, so the endpoint resolves. A failed probe
|
||||
// save must not lose the node or the install command, so
|
||||
// it only toasts and the flow continues (fixable from the
|
||||
// edit dialog afterwards).
|
||||
const draft = probeDraftRef.current?.getDraft();
|
||||
const createdId = obj.node?.id;
|
||||
if (draft?.dirty && createdId) {
|
||||
const pmsg = await HttpUtil.put(
|
||||
`/panel/api/agent/probe-meta/${createdId}`,
|
||||
draft.payload,
|
||||
{ silent: true, headers: { 'Content-Type': 'application/json' } },
|
||||
);
|
||||
if (!pmsg?.success) {
|
||||
messageApi.error(pmsg?.msg || t('pages.nodes.probe.saveFailed'));
|
||||
}
|
||||
}
|
||||
setAgentToken(obj.agentToken);
|
||||
setAgentNodeName(result.data.name.trim());
|
||||
// Default the master URL to the address this panel is being
|
||||
@@ -203,12 +231,6 @@ export default function NodeFormModal({
|
||||
>
|
||||
<FormProvider {...methods}>
|
||||
<Form layout="vertical">
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
title={t('pages.nodes.agentHint')}
|
||||
/>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} md={12}>
|
||||
<FormField
|
||||
@@ -288,9 +310,16 @@ export default function NodeFormModal({
|
||||
// restart), so it sits outside the node form. It used
|
||||
// to live on the probe settings page behind a node
|
||||
// picker; it now lives here, where the node is already
|
||||
// the subject. The component renders its own
|
||||
// public-status-page title and hint.
|
||||
// the subject.
|
||||
<ProbeMetaSection nodeId={node.id} />
|
||||
) : mode === 'add' ? (
|
||||
// Create mode: the same section as a local draft
|
||||
// (same field order, same one-row-five layout, no
|
||||
// title/hint), so probe info can be filled in together
|
||||
// with the node instead of in a second edit. Its own
|
||||
// save button is hidden — the dialog save submits the
|
||||
// draft after the node is created (see onFinish).
|
||||
<ProbeMetaSection key={probeDraftKey} ref={probeDraftRef} hideSave />
|
||||
) : null}
|
||||
</Form>
|
||||
</FormProvider>
|
||||
|
||||
@@ -797,9 +797,6 @@ export default function NodeList({
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
expandable={{
|
||||
expandedRowRender: (record) => <NodeHistoryPanel node={record} />,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import { useEffect, useState, type ReactNode } from 'react';
|
||||
import { useEffect, useImperativeHandle, useState, type ReactNode, type Ref } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Col, Input, InputNumber, Row, Select, Switch, message } from 'antd';
|
||||
import { Button, Col, DatePicker, Input, InputNumber, Row, Select, Switch, message } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import { HttpUtil } from '@/utils';
|
||||
|
||||
// Item 7 B3: probe/public-page metadata for an agent node, edited in
|
||||
// place from the node edit dialog. The section is deliberately outside
|
||||
// the node form: it loads and saves on its own (dedicated endpoints,
|
||||
// effective immediately on save — no node save, no restart).
|
||||
interface ProbeMetaWire {
|
||||
// the node form: in edit mode it loads and saves on its own
|
||||
// (dedicated endpoints, effective immediately on save — no node save,
|
||||
// no restart). In create mode (no nodeId yet) it runs as a local
|
||||
// draft seeded from the backend defaults: the parent reads it through
|
||||
// the ref handle when the node is created and submits it only if the
|
||||
// user touched it; its own save button is hidden there.
|
||||
// ProbeMetaWire is exported so the create-mode draft handle can type
|
||||
// the payload it hands to the parent.
|
||||
export interface ProbeMetaWire {
|
||||
price: number;
|
||||
currency: string;
|
||||
billingCycle: string;
|
||||
@@ -33,7 +40,44 @@ interface ProbeMetaWire {
|
||||
statIface: string;
|
||||
}
|
||||
|
||||
// GB entry converts on the binary convention every byte formatter in
|
||||
// The backend's DefaultMeta, mirrored here so a create-mode draft
|
||||
// starts exactly where an untouched edit-mode form would: submitting
|
||||
// this unchanged is value-identical to having no row at all (every
|
||||
// public/stats reader substitutes DefaultMeta when the row is absent).
|
||||
export const DEFAULT_PROBE_META: ProbeMetaWire = {
|
||||
price: 0,
|
||||
currency: 'USD',
|
||||
billingCycle: 'monthly',
|
||||
expiresAt: null,
|
||||
trafficLimit: 0,
|
||||
trafficMode: 'sum',
|
||||
trafficResetDay: 1,
|
||||
public: true,
|
||||
sort: 0,
|
||||
group: '',
|
||||
publicRemark: '',
|
||||
notify: false,
|
||||
countryPin: '',
|
||||
ipv4Pin: '',
|
||||
ipv6Pin: '',
|
||||
trafficCalUpGb: 0,
|
||||
trafficCalDownGb: 0,
|
||||
statIface: '',
|
||||
};
|
||||
|
||||
// Handle the create-mode parent reads through the ref: the full PUT
|
||||
// payload plus whether the user changed anything. Calibration boxes
|
||||
// are one-shot deltas — null/0 both mean "nothing to apply", so they
|
||||
// never mark the draft dirty on their own.
|
||||
export interface ProbeMetaDraftHandle {
|
||||
getDraft: () => { payload: ProbeMetaWire; dirty: boolean };
|
||||
}
|
||||
|
||||
export interface ProbeMetaSectionProps {
|
||||
nodeId?: number;
|
||||
hideSave?: boolean;
|
||||
ref?: Ref<ProbeMetaDraftHandle>;
|
||||
}
|
||||
// the panel and on the public page uses (V2.2.1: was decimal 1e9,
|
||||
// which made a 3000 GB limit render as 2.73 TB instead of 2.93 TB).
|
||||
const BYTES_PER_GB = 1024 ** 3;
|
||||
@@ -59,13 +103,17 @@ function Field({ label, children }: { label: string; children: ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
export default function ProbeMetaSection({ nodeId, hideSave, ref }: ProbeMetaSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const [messageApi, messageContextHolder] = message.useMessage();
|
||||
// Loaded meta, keyed by node id so a stale result for a previously
|
||||
// edited node is never shown (same discipline as the install block
|
||||
// in NodeFormModal); visibility derives during render.
|
||||
const [loaded, setLoaded] = useState<{ nodeId: number; meta: ProbeMetaWire } | null>(null);
|
||||
// in NodeFormModal); visibility derives during render. In draft
|
||||
// mode the key is null and the state starts seeded from defaults —
|
||||
// no server round trip, since the node does not exist yet.
|
||||
const [loaded, setLoaded] = useState<{ nodeId: number | null; meta: ProbeMetaWire } | null>(() =>
|
||||
nodeId == null ? { nodeId: null, meta: { ...DEFAULT_PROBE_META } } : null,
|
||||
);
|
||||
const [trafficGb, setTrafficGb] = useState(0);
|
||||
// One-shot calibration deltas (GB, either sign): null = empty box.
|
||||
const [calUpGb, setCalUpGb] = useState<number | null>(null);
|
||||
@@ -73,6 +121,7 @@ export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeId == null) return;
|
||||
let cancelled = false;
|
||||
HttpUtil.get<ProbeMetaWire>(`/panel/api/agent/probe-meta/${nodeId}`, {
|
||||
silent: true,
|
||||
@@ -87,12 +136,59 @@ export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
};
|
||||
}, [nodeId]);
|
||||
|
||||
const meta = loaded && loaded.nodeId === nodeId ? loaded.meta : null;
|
||||
if (!meta) return null;
|
||||
const meta = loaded && loaded.nodeId === (nodeId ?? null) ? loaded.meta : null;
|
||||
|
||||
const set = <K extends keyof ProbeMetaWire>(key: K, value: ProbeMetaWire[K]) =>
|
||||
setLoaded((l) => (l ? { nodeId: l.nodeId, meta: { ...l.meta, [key]: value } } : l));
|
||||
|
||||
// Full PUT payload from current form state (traffic limit converts
|
||||
// from the GB box; calibration boxes fold to 0 when empty).
|
||||
const buildPayload = (): ProbeMetaWire | null => {
|
||||
if (!meta) return null;
|
||||
return {
|
||||
...meta,
|
||||
trafficLimit: Math.round(trafficGb * BYTES_PER_GB),
|
||||
trafficCalUpGb: calUpGb ?? 0,
|
||||
trafficCalDownGb: calDownGb ?? 0,
|
||||
};
|
||||
};
|
||||
|
||||
// NOTE: hooks must stay above the early return below. In edit mode the
|
||||
// first render has no meta yet, so a hook placed after the return would
|
||||
// run on later renders only and React would throw #310 (rendered more
|
||||
// hooks than during the previous render).
|
||||
// Create-mode read-out: the parent submits this after the node
|
||||
// exists. Dirty compares against the defaults the draft started
|
||||
// from, so an untouched section submits nothing at all.
|
||||
useImperativeHandle(ref, () => ({
|
||||
getDraft: () => {
|
||||
const payload = buildPayload();
|
||||
if (!payload) return { payload: { ...DEFAULT_PROBE_META }, dirty: false };
|
||||
const d = DEFAULT_PROBE_META;
|
||||
const dirty =
|
||||
payload.price !== d.price ||
|
||||
payload.currency !== d.currency ||
|
||||
payload.billingCycle !== d.billingCycle ||
|
||||
payload.expiresAt !== d.expiresAt ||
|
||||
payload.trafficLimit !== d.trafficLimit ||
|
||||
payload.trafficMode !== d.trafficMode ||
|
||||
payload.trafficResetDay !== d.trafficResetDay ||
|
||||
payload.public !== d.public ||
|
||||
payload.sort !== d.sort ||
|
||||
payload.group !== d.group ||
|
||||
payload.publicRemark !== d.publicRemark ||
|
||||
payload.notify !== d.notify ||
|
||||
payload.countryPin !== d.countryPin ||
|
||||
payload.ipv4Pin !== d.ipv4Pin ||
|
||||
payload.ipv6Pin !== d.ipv6Pin ||
|
||||
payload.trafficCalUpGb !== 0 ||
|
||||
payload.trafficCalDownGb !== 0 ||
|
||||
payload.statIface !== d.statIface;
|
||||
return { payload, dirty };
|
||||
},
|
||||
}));
|
||||
if (!meta) return null;
|
||||
|
||||
const cycleLabel = (cycle: string) => {
|
||||
switch (cycle) {
|
||||
case 'once':
|
||||
@@ -122,15 +218,13 @@ export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
if (!meta) return;
|
||||
// Edit mode only: in draft mode the button is hidden and the
|
||||
// parent submits through the ref handle instead.
|
||||
if (!meta || nodeId == null) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
const payload: ProbeMetaWire = {
|
||||
...meta,
|
||||
trafficLimit: Math.round(trafficGb * BYTES_PER_GB),
|
||||
trafficCalUpGb: calUpGb ?? 0,
|
||||
trafficCalDownGb: calDownGb ?? 0,
|
||||
};
|
||||
const payload = buildPayload();
|
||||
if (!payload) return;
|
||||
const msg = await HttpUtil.put(`/panel/api/agent/probe-meta/${nodeId}`, payload, {
|
||||
silent: true,
|
||||
...JSON_HEADERS,
|
||||
@@ -156,8 +250,6 @@ export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
return (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
{messageContextHolder}
|
||||
<div style={{ fontWeight: 600, marginBottom: 4 }}>{t('pages.nodes.probe.title')}</div>
|
||||
<p style={{ marginBottom: 12 }}>{t('pages.nodes.probe.hint')}</p>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} md={12}>
|
||||
<Field label={t('pages.nodes.probe.public')}>
|
||||
@@ -170,13 +262,16 @@ export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
</Field>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} md={12}>
|
||||
<Field label={t('pages.nodes.probe.publicRemark')}>
|
||||
<Input value={meta.publicRemark} onChange={(e) => set('publicRemark', e.target.value)} />
|
||||
</Field>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Field label={t('pages.nodes.probe.group')}>
|
||||
<Input value={meta.group} onChange={(e) => set('group', e.target.value)} />
|
||||
</Field>
|
||||
</Col>
|
||||
<Col xs={24} md={12}>
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Field label={t('pages.nodes.probe.sort')}>
|
||||
<InputNumber
|
||||
value={meta.sort}
|
||||
@@ -184,13 +279,8 @@ export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Field>
|
||||
</Col>
|
||||
</Row>
|
||||
<Field label={t('pages.nodes.probe.publicRemark')}>
|
||||
<Input value={meta.publicRemark} onChange={(e) => set('publicRemark', e.target.value)} />
|
||||
</Field>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} md={8}>
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Field label={t('pages.nodes.probe.price')}>
|
||||
<InputNumber
|
||||
value={meta.price}
|
||||
@@ -199,13 +289,13 @@ export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Field>
|
||||
</Col>
|
||||
<Col xs={24} md={8}>
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Field label={t('pages.nodes.probe.currency')}>
|
||||
<Input value={meta.currency} onChange={(e) => set('currency', e.target.value)} />
|
||||
</Field>
|
||||
</Col>
|
||||
<Col xs={24} md={8}>
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Field label={t('pages.nodes.probe.billingCycle')}>
|
||||
<Select
|
||||
value={meta.billingCycle}
|
||||
@@ -214,19 +304,20 @@ export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Field>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} md={12}>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Field label={t('pages.nodes.probe.expiresAt')}>
|
||||
<Input
|
||||
value={meta.expiresAt ?? ''}
|
||||
placeholder="YYYY-MM-DD"
|
||||
onChange={(e) => set('expiresAt', e.target.value || null)}
|
||||
<DatePicker
|
||||
value={meta.expiresAt ? dayjs(meta.expiresAt) : null}
|
||||
format="YYYY-MM-DD"
|
||||
onChange={(d) => set('expiresAt', d ? d.format('YYYY-MM-DD') : null)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Field>
|
||||
</Col>
|
||||
<Col xs={24} md={12}>
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Field label={t('pages.nodes.probe.trafficResetDay')}>
|
||||
<InputNumber
|
||||
value={meta.trafficResetDay}
|
||||
@@ -236,10 +327,8 @@ export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Field>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} md={12}>
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Field label={t('pages.nodes.probe.trafficLimit')}>
|
||||
<InputNumber
|
||||
value={trafficGb}
|
||||
@@ -248,8 +337,8 @@ export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Field>
|
||||
</Col>
|
||||
<Col xs={24} md={12}>
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Field label={t('pages.nodes.probe.trafficMode')}>
|
||||
<Select
|
||||
value={meta.trafficMode}
|
||||
@@ -263,8 +352,8 @@ export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Field>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} md={8}>
|
||||
<Field label={t('pages.nodes.probe.calUp')}>
|
||||
@@ -316,9 +405,11 @@ export default function ProbeMetaSection({ nodeId }: { nodeId: number }) {
|
||||
</Field>
|
||||
</Col>
|
||||
</Row>
|
||||
<Button type="primary" loading={saving} onClick={onSave}>
|
||||
{t('pages.nodes.probe.save')}
|
||||
</Button>
|
||||
{!hideSave && (
|
||||
<Button type="primary" loading={saving} onClick={onSave}>
|
||||
{t('pages.nodes.probe.save')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -230,35 +230,35 @@ export default function SettingsPage() {
|
||||
)}
|
||||
|
||||
<Row gutter={[isMobile ? 8 : 16, isMobile ? 0 : 12]}>
|
||||
<Col span={24}>
|
||||
<Card hoverable>
|
||||
<Row className="header-row">
|
||||
<Col xs={24} sm={10} className="header-actions">
|
||||
<Space>
|
||||
<Button type="primary" disabled={saveDisabled} onClick={onSave}>
|
||||
{t('pages.settings.save')}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
disabled={!saveDisabled}
|
||||
onClick={restartPanel}
|
||||
>
|
||||
{t('pages.settings.restartPanel')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col xs={24} sm={14} className="header-info">
|
||||
<FloatButton.BackTop target={scrollTarget} visibilityHeight={200} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
{activeSlug !== 'public-page' && activeSlug !== 'probe' && (
|
||||
<Col span={24}>
|
||||
<Card hoverable>
|
||||
<Row className="header-row">
|
||||
<Col xs={24} sm={10} className="header-actions">
|
||||
<Space>
|
||||
<Button type="primary" disabled={saveDisabled} onClick={onSave}>
|
||||
{t('pages.settings.save')}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
disabled={!saveDisabled}
|
||||
onClick={restartPanel}
|
||||
>
|
||||
{t('pages.settings.restartPanel')}
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
)}
|
||||
|
||||
<Col span={24}>
|
||||
<Card hoverable>{categoryBody}</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<FloatButton.BackTop target={scrollTarget} visibilityHeight={200} />
|
||||
</>
|
||||
)}
|
||||
</Spin>
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ esac
|
||||
# 改名对齐:旧 tag v3.7.0-agent.1 从未携带改名后的 l-ui-linux 资产,沿用它
|
||||
# 会指向不存在的发行;此处改指本批发版 V2.7.0。仓库名定为 rogue/l-ui,
|
||||
# Gitea 改名在发版前完成,占位警告随之销账。
|
||||
LUI_URL="${LUI_URL:-https://git.881218.xyz/rogue/l-ui/releases/download/V2.7.2/l-ui-linux-${BIN_ARCH}}"
|
||||
LUI_URL="${LUI_URL:-https://git.881218.xyz/rogue/l-ui/releases/download/V2.7.5/l-ui-linux-${BIN_ARCH}}"
|
||||
XRAY_VER="26.6.27"
|
||||
|
||||
info "创建目录..."
|
||||
|
||||
@@ -865,7 +865,6 @@
|
||||
"updateResult": "تم بدء التحديث على {ok} عقدة، فشل {failed}",
|
||||
"updateNoneEligible": "اختر عقدة واحدة على الأقل متصلة ومفعّلة"
|
||||
},
|
||||
"agentHint": "Agent nodes connect out to the master; no address, port or API token needed. An install command is generated on creation.",
|
||||
"probeOnly": "Probe only",
|
||||
"probeOnlyHint": "Probe-only mode: no proxy core runs on this node; only monitoring (heartbeat, metrics and latency probes) is active.",
|
||||
"agentTokenTitle": "Agent Node Setup",
|
||||
@@ -878,8 +877,6 @@
|
||||
"agentMasterUrlHint": "Master URL (used by agent to reach the master; change to public address if behind proxy):",
|
||||
"agentMasterUrlRequired": "Master URL is empty — enter the master address first; the install command is generated only then.",
|
||||
"probe": {
|
||||
"title": "Public status page",
|
||||
"hint": "Display and plan data for the public status page. Saving takes effect immediately.",
|
||||
"public": "Show on public page",
|
||||
"publicRemark": "Public remark",
|
||||
"group": "Group",
|
||||
@@ -894,8 +891,8 @@
|
||||
"cycleYearly": "Yearly",
|
||||
"cycleBiennial": "Biennial",
|
||||
"cycleTriennial": "Triennial",
|
||||
"expiresAt": "Expires on (YYYY-MM-DD)",
|
||||
"trafficLimit": "Traffic limit (GB, 0 = unlimited)",
|
||||
"expiresAt": "Expires on",
|
||||
"trafficLimit": "Traffic limit (GB)",
|
||||
"trafficMode": "Traffic counting",
|
||||
"modeSum": "Up + down",
|
||||
"modeUp": "Up only",
|
||||
@@ -1314,7 +1311,6 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "خلط المضيف",
|
||||
"tags": "وسوم",
|
||||
"nodeGuids": "النودز",
|
||||
"excludeFromSubTypes": "استبعاد من الصيغ",
|
||||
"verifyPeerCertByName": "التحقق من شهادة النظير بالاسم",
|
||||
"inheritAddress": "يرث العنوان"
|
||||
@@ -1322,8 +1318,6 @@
|
||||
"hints": {
|
||||
"address": "اتركه فارغاً ليرث عنوان الوارد نفسه.",
|
||||
"port": "0 يرث منفذ الوارد.",
|
||||
"tags": "غير مرئي للمستخدمين النهائيين؛ يُرسل مع اشتراك RAW فقط. أحرف كبيرة وأرقام و _ و : فقط.",
|
||||
"nodeGuids": "اختر النودز التي تم تحليلها من هذا المضيف. تعيين بصري فقط.",
|
||||
"serverDescription": "ملاحظة اختيارية تظهر تحت الملاحظة.",
|
||||
"allowInsecure": "تخطّي التحقق من شهادة TLS (allowInsecure / skip-cert-verify).",
|
||||
"vlessRoute": "قيمة مسار VLESS واحدة (0-65535) تُدمَج في UUID، مثل 443. اتركه فارغاً لعدم وجود أي منها.",
|
||||
|
||||
@@ -861,15 +861,12 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "Shuffle host",
|
||||
"tags": "Tags",
|
||||
"nodeGuids": "Nodes",
|
||||
"excludeFromSubTypes": "Exclude from formats",
|
||||
"inheritAddress": "Inherits"
|
||||
},
|
||||
"hints": {
|
||||
"address": "Leave blank to inherit the inbound's own address.",
|
||||
"port": "0 inherits the inbound's port.",
|
||||
"tags": "Not visible to end users; sent with RAW subscription only. Uppercase letters, digits, _ and : only.",
|
||||
"nodeGuids": "Pick nodes which resolved from this host. Only visual assignment.",
|
||||
"serverDescription": "Optional note shown under the remark.",
|
||||
"allowInsecure": "Skip TLS certificate verification (allowInsecure / skip-cert-verify).",
|
||||
"vlessRoute": "Single VLESS route value (0-65535) baked into the UUID, e.g. 443. Leave blank for none.",
|
||||
@@ -983,7 +980,6 @@
|
||||
"updateResult": "Update triggered on {ok} node(s), {failed} failed",
|
||||
"updateNoneEligible": "Select at least one online, enabled node"
|
||||
},
|
||||
"agentHint": "Agent nodes connect out to the master; no address, port or API token needed. An install command is generated on creation.",
|
||||
"probeOnly": "Probe only",
|
||||
"probeOnlyHint": "Probe-only mode: no proxy core runs on this node; only monitoring (heartbeat, metrics and latency probes) is active.",
|
||||
"agentTokenTitle": "Agent Node Setup",
|
||||
@@ -996,8 +992,6 @@
|
||||
"agentMasterUrlHint": "Master URL (used by agent to reach the master; change to public address if behind proxy):",
|
||||
"agentMasterUrlRequired": "Master URL is empty — enter the master address first; the install command is generated only then.",
|
||||
"probe": {
|
||||
"title": "Public status page",
|
||||
"hint": "Display and plan data for the public status page. Saving takes effect immediately.",
|
||||
"public": "Show on public page",
|
||||
"publicRemark": "Public remark",
|
||||
"group": "Group",
|
||||
@@ -1012,8 +1006,8 @@
|
||||
"cycleYearly": "Yearly",
|
||||
"cycleBiennial": "Biennial",
|
||||
"cycleTriennial": "Triennial",
|
||||
"expiresAt": "Expires on (YYYY-MM-DD)",
|
||||
"trafficLimit": "Traffic limit (GB, 0 = unlimited)",
|
||||
"expiresAt": "Expires on",
|
||||
"trafficLimit": "Traffic limit (GB)",
|
||||
"trafficMode": "Traffic counting",
|
||||
"modeSum": "Up + down",
|
||||
"modeUp": "Up only",
|
||||
|
||||
@@ -865,7 +865,6 @@
|
||||
"updateResult": "Actualización iniciada en {ok} nodo(s), {failed} fallaron",
|
||||
"updateNoneEligible": "Selecciona al menos un nodo en línea y habilitado"
|
||||
},
|
||||
"agentHint": "Agent nodes connect out to the master; no address, port or API token needed. An install command is generated on creation.",
|
||||
"probeOnly": "Probe only",
|
||||
"probeOnlyHint": "Probe-only mode: no proxy core runs on this node; only monitoring (heartbeat, metrics and latency probes) is active.",
|
||||
"agentTokenTitle": "Agent Node Setup",
|
||||
@@ -878,8 +877,6 @@
|
||||
"agentMasterUrlHint": "Master URL (used by agent to reach the master; change to public address if behind proxy):",
|
||||
"agentMasterUrlRequired": "Master URL is empty — enter the master address first; the install command is generated only then.",
|
||||
"probe": {
|
||||
"title": "Public status page",
|
||||
"hint": "Display and plan data for the public status page. Saving takes effect immediately.",
|
||||
"public": "Show on public page",
|
||||
"publicRemark": "Public remark",
|
||||
"group": "Group",
|
||||
@@ -894,8 +891,8 @@
|
||||
"cycleYearly": "Yearly",
|
||||
"cycleBiennial": "Biennial",
|
||||
"cycleTriennial": "Triennial",
|
||||
"expiresAt": "Expires on (YYYY-MM-DD)",
|
||||
"trafficLimit": "Traffic limit (GB, 0 = unlimited)",
|
||||
"expiresAt": "Expires on",
|
||||
"trafficLimit": "Traffic limit (GB)",
|
||||
"trafficMode": "Traffic counting",
|
||||
"modeSum": "Up + down",
|
||||
"modeUp": "Up only",
|
||||
@@ -1315,15 +1312,12 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "Barajar host",
|
||||
"tags": "Etiquetas",
|
||||
"nodeGuids": "Nodos",
|
||||
"excludeFromSubTypes": "Excluir de formatos",
|
||||
"inheritAddress": "Hereda dirección"
|
||||
},
|
||||
"hints": {
|
||||
"address": "Déjalo en blanco para heredar la dirección propia del inbound.",
|
||||
"port": "0 hereda el puerto del inbound.",
|
||||
"tags": "No visible para los usuarios finales; se envía solo con la suscripción RAW. Solo letras mayúsculas, dígitos, _ y :.",
|
||||
"nodeGuids": "Elige los nodos que se resolvieron desde este host. Solo asignación visual.",
|
||||
"serverDescription": "Nota opcional que se muestra bajo las notas.",
|
||||
"allowInsecure": "Omitir la verificación del certificado TLS (allowInsecure / skip-cert-verify).",
|
||||
"vlessRoute": "Un único valor de ruta VLESS (0-65535) incrustado en el UUID, p. ej. 443. Déjalo en blanco para ninguno.",
|
||||
|
||||
@@ -865,7 +865,6 @@
|
||||
"updateResult": "بهروزرسانی روی {ok} نود آغاز شد، {failed} ناموفق",
|
||||
"updateNoneEligible": "حداقل یک نود آنلاین و فعال انتخاب کنید"
|
||||
},
|
||||
"agentHint": "Agent nodes connect out to the master; no address, port or API token needed. An install command is generated on creation.",
|
||||
"probeOnly": "Probe only",
|
||||
"probeOnlyHint": "Probe-only mode: no proxy core runs on this node; only monitoring (heartbeat, metrics and latency probes) is active.",
|
||||
"agentTokenTitle": "Agent Node Setup",
|
||||
@@ -878,8 +877,6 @@
|
||||
"agentMasterUrlHint": "Master URL (used by agent to reach the master; change to public address if behind proxy):",
|
||||
"agentMasterUrlRequired": "Master URL is empty — enter the master address first; the install command is generated only then.",
|
||||
"probe": {
|
||||
"title": "Public status page",
|
||||
"hint": "Display and plan data for the public status page. Saving takes effect immediately.",
|
||||
"public": "Show on public page",
|
||||
"publicRemark": "Public remark",
|
||||
"group": "Group",
|
||||
@@ -894,8 +891,8 @@
|
||||
"cycleYearly": "Yearly",
|
||||
"cycleBiennial": "Biennial",
|
||||
"cycleTriennial": "Triennial",
|
||||
"expiresAt": "Expires on (YYYY-MM-DD)",
|
||||
"trafficLimit": "Traffic limit (GB, 0 = unlimited)",
|
||||
"expiresAt": "Expires on",
|
||||
"trafficLimit": "Traffic limit (GB)",
|
||||
"trafficMode": "Traffic counting",
|
||||
"modeSum": "Up + down",
|
||||
"modeUp": "Up only",
|
||||
@@ -1315,15 +1312,12 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "درهمسازی میزبان",
|
||||
"tags": "برچسبها",
|
||||
"nodeGuids": "نودها",
|
||||
"excludeFromSubTypes": "حذف از فرمتها",
|
||||
"inheritAddress": "ارثبری آدرس"
|
||||
},
|
||||
"hints": {
|
||||
"address": "برای ارثبری آدرس خودِ اینباند خالی بگذارید.",
|
||||
"port": "مقدار ۰ پورت اینباند را به ارث میبرد.",
|
||||
"tags": "برای کاربران نهایی قابل مشاهده نیست؛ فقط با اشتراک RAW ارسال میشود. تنها حروف بزرگ، ارقام، _ و : مجاز است.",
|
||||
"nodeGuids": "نودهایی را که از این میزبان resolve میشوند انتخاب کنید. صرفاً انتساب نمایشی است.",
|
||||
"serverDescription": "یادداشت اختیاری که زیر نام نمایش داده میشود.",
|
||||
"allowInsecure": "رد کردن بررسی گواهی TLS (allowInsecure / skip-cert-verify).",
|
||||
"vlessRoute": "یک مقدار مسیر VLESS (0 تا 65535) که در UUID جاسازی میشود، مثلاً 443. برای هیچکدام خالی بگذارید.",
|
||||
|
||||
@@ -865,7 +865,6 @@
|
||||
"updateResult": "Pembaruan dipicu pada {ok} node, {failed} gagal",
|
||||
"updateNoneEligible": "Pilih minimal satu node online dan aktif"
|
||||
},
|
||||
"agentHint": "Agent nodes connect out to the master; no address, port or API token needed. An install command is generated on creation.",
|
||||
"probeOnly": "Probe only",
|
||||
"probeOnlyHint": "Probe-only mode: no proxy core runs on this node; only monitoring (heartbeat, metrics and latency probes) is active.",
|
||||
"agentTokenTitle": "Agent Node Setup",
|
||||
@@ -878,8 +877,6 @@
|
||||
"agentMasterUrlHint": "Master URL (used by agent to reach the master; change to public address if behind proxy):",
|
||||
"agentMasterUrlRequired": "Master URL is empty — enter the master address first; the install command is generated only then.",
|
||||
"probe": {
|
||||
"title": "Public status page",
|
||||
"hint": "Display and plan data for the public status page. Saving takes effect immediately.",
|
||||
"public": "Show on public page",
|
||||
"publicRemark": "Public remark",
|
||||
"group": "Group",
|
||||
@@ -894,8 +891,8 @@
|
||||
"cycleYearly": "Yearly",
|
||||
"cycleBiennial": "Biennial",
|
||||
"cycleTriennial": "Triennial",
|
||||
"expiresAt": "Expires on (YYYY-MM-DD)",
|
||||
"trafficLimit": "Traffic limit (GB, 0 = unlimited)",
|
||||
"expiresAt": "Expires on",
|
||||
"trafficLimit": "Traffic limit (GB)",
|
||||
"trafficMode": "Traffic counting",
|
||||
"modeSum": "Up + down",
|
||||
"modeUp": "Up only",
|
||||
@@ -1314,7 +1311,6 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "Acak host",
|
||||
"tags": "Tag",
|
||||
"nodeGuids": "Node",
|
||||
"excludeFromSubTypes": "Kecualikan dari format",
|
||||
"verifyPeerCertByName": "Verifikasi sertifikat peer berdasarkan nama",
|
||||
"inheritAddress": "Warisi alamat"
|
||||
@@ -1322,8 +1318,6 @@
|
||||
"hints": {
|
||||
"address": "Biarkan kosong untuk mewarisi alamat inbound itu sendiri.",
|
||||
"port": "0 mewarisi port inbound.",
|
||||
"tags": "Tidak terlihat oleh pengguna akhir; hanya dikirim dengan langganan RAW. Hanya huruf kapital, angka, _ dan :.",
|
||||
"nodeGuids": "Pilih node yang teresolusi dari host ini. Hanya penetapan visual.",
|
||||
"serverDescription": "Catatan opsional yang ditampilkan di bawah catatan.",
|
||||
"allowInsecure": "Lewati verifikasi sertifikat TLS (allowInsecure / skip-cert-verify).",
|
||||
"vlessRoute": "Satu nilai rute VLESS (0-65535) yang disisipkan ke UUID, mis. 443. Biarkan kosong jika tidak ada.",
|
||||
|
||||
@@ -865,7 +865,6 @@
|
||||
"updateResult": "{ok} 個のノードで更新を開始、{failed} 個失敗",
|
||||
"updateNoneEligible": "オンラインで有効なノードを少なくとも1つ選択してください"
|
||||
},
|
||||
"agentHint": "Agent nodes connect out to the master; no address, port or API token needed. An install command is generated on creation.",
|
||||
"probeOnly": "Probe only",
|
||||
"probeOnlyHint": "Probe-only mode: no proxy core runs on this node; only monitoring (heartbeat, metrics and latency probes) is active.",
|
||||
"agentTokenTitle": "Agent Node Setup",
|
||||
@@ -878,8 +877,6 @@
|
||||
"agentMasterUrlHint": "Master URL (used by agent to reach the master; change to public address if behind proxy):",
|
||||
"agentMasterUrlRequired": "Master URL is empty — enter the master address first; the install command is generated only then.",
|
||||
"probe": {
|
||||
"title": "Public status page",
|
||||
"hint": "Display and plan data for the public status page. Saving takes effect immediately.",
|
||||
"public": "Show on public page",
|
||||
"publicRemark": "Public remark",
|
||||
"group": "Group",
|
||||
@@ -894,8 +891,8 @@
|
||||
"cycleYearly": "Yearly",
|
||||
"cycleBiennial": "Biennial",
|
||||
"cycleTriennial": "Triennial",
|
||||
"expiresAt": "Expires on (YYYY-MM-DD)",
|
||||
"trafficLimit": "Traffic limit (GB, 0 = unlimited)",
|
||||
"expiresAt": "Expires on",
|
||||
"trafficLimit": "Traffic limit (GB)",
|
||||
"trafficMode": "Traffic counting",
|
||||
"modeSum": "Up + down",
|
||||
"modeUp": "Up only",
|
||||
@@ -1315,15 +1312,12 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "ホストをシャッフル",
|
||||
"tags": "タグ",
|
||||
"nodeGuids": "ノード",
|
||||
"excludeFromSubTypes": "形式から除外",
|
||||
"inheritAddress": "アドレス継承"
|
||||
},
|
||||
"hints": {
|
||||
"address": "空欄にするとインバウンド自身のアドレスを継承します。",
|
||||
"port": "0 にするとインバウンドのポートを継承します。",
|
||||
"tags": "エンドユーザーには表示されません。RAW サブスクリプションでのみ送信されます。大文字、数字、_ と : のみ使用できます。",
|
||||
"nodeGuids": "このホストから解決されたノードを選択します。視覚的な割り当てのみです。",
|
||||
"serverDescription": "備考の下に表示される任意のメモ。",
|
||||
"allowInsecure": "TLS 証明書の検証をスキップします(allowInsecure / skip-cert-verify)。",
|
||||
"vlessRoute": "UUID に埋め込まれる単一の VLESS ルート値(0〜65535)。例: 443。なしの場合は空欄にします。",
|
||||
|
||||
@@ -865,7 +865,6 @@
|
||||
"updateResult": "Atualização iniciada em {ok} nó(s), {failed} falharam",
|
||||
"updateNoneEligible": "Selecione pelo menos um nó online e ativo"
|
||||
},
|
||||
"agentHint": "Agent nodes connect out to the master; no address, port or API token needed. An install command is generated on creation.",
|
||||
"probeOnly": "Probe only",
|
||||
"probeOnlyHint": "Probe-only mode: no proxy core runs on this node; only monitoring (heartbeat, metrics and latency probes) is active.",
|
||||
"agentTokenTitle": "Agent Node Setup",
|
||||
@@ -878,8 +877,6 @@
|
||||
"agentMasterUrlHint": "Master URL (used by agent to reach the master; change to public address if behind proxy):",
|
||||
"agentMasterUrlRequired": "Master URL is empty — enter the master address first; the install command is generated only then.",
|
||||
"probe": {
|
||||
"title": "Public status page",
|
||||
"hint": "Display and plan data for the public status page. Saving takes effect immediately.",
|
||||
"public": "Show on public page",
|
||||
"publicRemark": "Public remark",
|
||||
"group": "Group",
|
||||
@@ -894,8 +891,8 @@
|
||||
"cycleYearly": "Yearly",
|
||||
"cycleBiennial": "Biennial",
|
||||
"cycleTriennial": "Triennial",
|
||||
"expiresAt": "Expires on (YYYY-MM-DD)",
|
||||
"trafficLimit": "Traffic limit (GB, 0 = unlimited)",
|
||||
"expiresAt": "Expires on",
|
||||
"trafficLimit": "Traffic limit (GB)",
|
||||
"trafficMode": "Traffic counting",
|
||||
"modeSum": "Up + down",
|
||||
"modeUp": "Up only",
|
||||
@@ -1315,15 +1312,12 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "Embaralhar host",
|
||||
"tags": "Tags",
|
||||
"nodeGuids": "Nós",
|
||||
"excludeFromSubTypes": "Excluir dos formatos",
|
||||
"inheritAddress": "Herda endereço"
|
||||
},
|
||||
"hints": {
|
||||
"address": "Deixe em branco para herdar o próprio endereço da entrada.",
|
||||
"port": "0 herda a porta da entrada.",
|
||||
"tags": "Não visível aos usuários finais; enviado apenas na assinatura RAW. Apenas letras maiúsculas, dígitos, _ e :.",
|
||||
"nodeGuids": "Escolha os nós que foram resolvidos a partir deste host. Apenas atribuição visual.",
|
||||
"serverDescription": "Nota opcional exibida abaixo da observação.",
|
||||
"allowInsecure": "Ignorar a verificação do certificado TLS (allowInsecure / skip-cert-verify).",
|
||||
"vlessRoute": "Um único valor de rota VLESS (0-65535) embutido no UUID, ex.: 443. Deixe em branco para nenhum.",
|
||||
|
||||
@@ -865,7 +865,6 @@
|
||||
"updateResult": "Обновление запущено на {ok} узлах, {failed} не удалось",
|
||||
"updateNoneEligible": "Выберите хотя бы один включённый узел в сети"
|
||||
},
|
||||
"agentHint": "Agent nodes connect out to the master; no address, port or API token needed. An install command is generated on creation.",
|
||||
"probeOnly": "Probe only",
|
||||
"probeOnlyHint": "Probe-only mode: no proxy core runs on this node; only monitoring (heartbeat, metrics and latency probes) is active.",
|
||||
"agentTokenTitle": "Agent Node Setup",
|
||||
@@ -878,8 +877,6 @@
|
||||
"agentMasterUrlHint": "Master URL (used by agent to reach the master; change to public address if behind proxy):",
|
||||
"agentMasterUrlRequired": "Master URL is empty — enter the master address first; the install command is generated only then.",
|
||||
"probe": {
|
||||
"title": "Public status page",
|
||||
"hint": "Display and plan data for the public status page. Saving takes effect immediately.",
|
||||
"public": "Show on public page",
|
||||
"publicRemark": "Public remark",
|
||||
"group": "Group",
|
||||
@@ -894,8 +891,8 @@
|
||||
"cycleYearly": "Yearly",
|
||||
"cycleBiennial": "Biennial",
|
||||
"cycleTriennial": "Triennial",
|
||||
"expiresAt": "Expires on (YYYY-MM-DD)",
|
||||
"trafficLimit": "Traffic limit (GB, 0 = unlimited)",
|
||||
"expiresAt": "Expires on",
|
||||
"trafficLimit": "Traffic limit (GB)",
|
||||
"trafficMode": "Traffic counting",
|
||||
"modeSum": "Up + down",
|
||||
"modeUp": "Up only",
|
||||
@@ -1315,15 +1312,12 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "Перемешивать хост",
|
||||
"tags": "Теги",
|
||||
"nodeGuids": "Узлы",
|
||||
"excludeFromSubTypes": "Исключить из форматов",
|
||||
"inheritAddress": "Наследует адрес"
|
||||
},
|
||||
"hints": {
|
||||
"address": "Оставьте пустым, чтобы унаследовать собственный адрес входящего.",
|
||||
"port": "0 наследует порт входящего.",
|
||||
"tags": "Не видны конечным пользователям; отправляются только с RAW-подпиской. Только заглавные буквы, цифры, _ и :.",
|
||||
"nodeGuids": "Выберите узлы, которые разрешаются с этого хоста. Только визуальное назначение.",
|
||||
"serverDescription": "Необязательная заметка, отображаемая под примечанием.",
|
||||
"allowInsecure": "Пропустить проверку TLS-сертификата (allowInsecure / skip-cert-verify).",
|
||||
"vlessRoute": "Одно значение маршрута VLESS (0-65535), встраиваемое в UUID, напр. 443. Оставьте пустым, чтобы отключить.",
|
||||
|
||||
@@ -865,7 +865,6 @@
|
||||
"updateResult": "{ok} düğümde güncelleme başlatıldı, {failed} başarısız",
|
||||
"updateNoneEligible": "En az bir çevrimiçi ve etkin düğüm seçin"
|
||||
},
|
||||
"agentHint": "Agent nodes connect out to the master; no address, port or API token needed. An install command is generated on creation.",
|
||||
"probeOnly": "Probe only",
|
||||
"probeOnlyHint": "Probe-only mode: no proxy core runs on this node; only monitoring (heartbeat, metrics and latency probes) is active.",
|
||||
"agentTokenTitle": "Agent Node Setup",
|
||||
@@ -878,8 +877,6 @@
|
||||
"agentMasterUrlHint": "Master URL (used by agent to reach the master; change to public address if behind proxy):",
|
||||
"agentMasterUrlRequired": "Master URL is empty — enter the master address first; the install command is generated only then.",
|
||||
"probe": {
|
||||
"title": "Public status page",
|
||||
"hint": "Display and plan data for the public status page. Saving takes effect immediately.",
|
||||
"public": "Show on public page",
|
||||
"publicRemark": "Public remark",
|
||||
"group": "Group",
|
||||
@@ -894,8 +891,8 @@
|
||||
"cycleYearly": "Yearly",
|
||||
"cycleBiennial": "Biennial",
|
||||
"cycleTriennial": "Triennial",
|
||||
"expiresAt": "Expires on (YYYY-MM-DD)",
|
||||
"trafficLimit": "Traffic limit (GB, 0 = unlimited)",
|
||||
"expiresAt": "Expires on",
|
||||
"trafficLimit": "Traffic limit (GB)",
|
||||
"trafficMode": "Traffic counting",
|
||||
"modeSum": "Up + down",
|
||||
"modeUp": "Up only",
|
||||
@@ -1314,7 +1311,6 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "Host'u karıştır",
|
||||
"tags": "Etiketler",
|
||||
"nodeGuids": "Düğümler",
|
||||
"excludeFromSubTypes": "Formatlardan hariç tut",
|
||||
"verifyPeerCertByName": "Peer sertifikasını ada göre doğrula",
|
||||
"inheritAddress": "Adresi devralır"
|
||||
@@ -1322,8 +1318,6 @@
|
||||
"hints": {
|
||||
"address": "Gelen bağlantının kendi adresini devralmak için boş bırakın.",
|
||||
"port": "0 değeri gelen bağlantının portunu devralır.",
|
||||
"tags": "Son kullanıcılara görünmez; yalnızca RAW abonelikle gönderilir. Yalnızca büyük harf, rakam, _ ve : kullanılabilir.",
|
||||
"nodeGuids": "Bu host'tan çözümlenen düğümleri seçin. Yalnızca görsel atama.",
|
||||
"serverDescription": "Açıklamanın altında gösterilen isteğe bağlı not.",
|
||||
"allowInsecure": "TLS sertifika doğrulamasını atla (allowInsecure / skip-cert-verify).",
|
||||
"vlessRoute": "UUID'ye gömülen tek bir VLESS rota değeri (0-65535), örn. 443. Hiçbiri için boş bırakın.",
|
||||
|
||||
@@ -865,7 +865,6 @@
|
||||
"updateResult": "Оновлення запущено на {ok} вузлах, {failed} не вдалося",
|
||||
"updateNoneEligible": "Виберіть принаймні один увімкнений вузол у мережі"
|
||||
},
|
||||
"agentHint": "Agent nodes connect out to the master; no address, port or API token needed. An install command is generated on creation.",
|
||||
"probeOnly": "Probe only",
|
||||
"probeOnlyHint": "Probe-only mode: no proxy core runs on this node; only monitoring (heartbeat, metrics and latency probes) is active.",
|
||||
"agentTokenTitle": "Agent Node Setup",
|
||||
@@ -878,8 +877,6 @@
|
||||
"agentMasterUrlHint": "Master URL (used by agent to reach the master; change to public address if behind proxy):",
|
||||
"agentMasterUrlRequired": "Master URL is empty — enter the master address first; the install command is generated only then.",
|
||||
"probe": {
|
||||
"title": "Public status page",
|
||||
"hint": "Display and plan data for the public status page. Saving takes effect immediately.",
|
||||
"public": "Show on public page",
|
||||
"publicRemark": "Public remark",
|
||||
"group": "Group",
|
||||
@@ -894,8 +891,8 @@
|
||||
"cycleYearly": "Yearly",
|
||||
"cycleBiennial": "Biennial",
|
||||
"cycleTriennial": "Triennial",
|
||||
"expiresAt": "Expires on (YYYY-MM-DD)",
|
||||
"trafficLimit": "Traffic limit (GB, 0 = unlimited)",
|
||||
"expiresAt": "Expires on",
|
||||
"trafficLimit": "Traffic limit (GB)",
|
||||
"trafficMode": "Traffic counting",
|
||||
"modeSum": "Up + down",
|
||||
"modeUp": "Up only",
|
||||
@@ -1315,15 +1312,12 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "Перемішувати host",
|
||||
"tags": "Теги",
|
||||
"nodeGuids": "Вузли",
|
||||
"excludeFromSubTypes": "Виключити з форматів",
|
||||
"inheritAddress": "Успадковує адресу"
|
||||
},
|
||||
"hints": {
|
||||
"address": "Залиште порожнім, щоб успадкувати власну адресу вхідного.",
|
||||
"port": "0 успадковує порт вхідного.",
|
||||
"tags": "Не видно кінцевим користувачам; надсилається лише з RAW-підпискою. Лише великі літери, цифри, _ та :.",
|
||||
"nodeGuids": "Виберіть вузли, які розв'язуються з цього хоста. Лише візуальне призначення.",
|
||||
"serverDescription": "Необов'язкова примітка, що показується під приміткою.",
|
||||
"allowInsecure": "Пропускати перевірку TLS-сертифіката (allowInsecure / skip-cert-verify).",
|
||||
"vlessRoute": "Одне значення маршруту VLESS (0-65535), що вбудовується в UUID, напр. 443. Залиште порожнім, щоб не використовувати.",
|
||||
|
||||
@@ -865,7 +865,6 @@
|
||||
"updateResult": "Đã kích hoạt cập nhật trên {ok} node, {failed} thất bại",
|
||||
"updateNoneEligible": "Chọn ít nhất một node trực tuyến và đang bật"
|
||||
},
|
||||
"agentHint": "Agent nodes connect out to the master; no address, port or API token needed. An install command is generated on creation.",
|
||||
"probeOnly": "Probe only",
|
||||
"probeOnlyHint": "Probe-only mode: no proxy core runs on this node; only monitoring (heartbeat, metrics and latency probes) is active.",
|
||||
"agentTokenTitle": "Agent Node Setup",
|
||||
@@ -878,8 +877,6 @@
|
||||
"agentMasterUrlHint": "Master URL (used by agent to reach the master; change to public address if behind proxy):",
|
||||
"agentMasterUrlRequired": "Master URL is empty — enter the master address first; the install command is generated only then.",
|
||||
"probe": {
|
||||
"title": "Public status page",
|
||||
"hint": "Display and plan data for the public status page. Saving takes effect immediately.",
|
||||
"public": "Show on public page",
|
||||
"publicRemark": "Public remark",
|
||||
"group": "Group",
|
||||
@@ -894,8 +891,8 @@
|
||||
"cycleYearly": "Yearly",
|
||||
"cycleBiennial": "Biennial",
|
||||
"cycleTriennial": "Triennial",
|
||||
"expiresAt": "Expires on (YYYY-MM-DD)",
|
||||
"trafficLimit": "Traffic limit (GB, 0 = unlimited)",
|
||||
"expiresAt": "Expires on",
|
||||
"trafficLimit": "Traffic limit (GB)",
|
||||
"trafficMode": "Traffic counting",
|
||||
"modeSum": "Up + down",
|
||||
"modeUp": "Up only",
|
||||
@@ -1315,15 +1312,12 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "Xáo trộn host",
|
||||
"tags": "Tag",
|
||||
"nodeGuids": "Nút",
|
||||
"excludeFromSubTypes": "Loại trừ khỏi định dạng",
|
||||
"inheritAddress": "Kế thừa địa chỉ"
|
||||
},
|
||||
"hints": {
|
||||
"address": "Để trống để kế thừa địa chỉ của chính inbound.",
|
||||
"port": "0 sẽ kế thừa cổng của inbound.",
|
||||
"tags": "Không hiển thị cho người dùng cuối; chỉ gửi kèm đăng ký RAW. Chỉ chữ in hoa, chữ số, _ và :.",
|
||||
"nodeGuids": "Chọn các nút được phân giải từ host này. Chỉ là gán trực quan.",
|
||||
"serverDescription": "Ghi chú tùy chọn hiển thị bên dưới ghi chú.",
|
||||
"allowInsecure": "Bỏ qua xác minh chứng chỉ TLS (allowInsecure / skip-cert-verify).",
|
||||
"vlessRoute": "Một giá trị định tuyến VLESS (0-65535) được nhúng vào UUID, ví dụ 443. Để trống nếu không dùng.",
|
||||
|
||||
@@ -865,7 +865,6 @@
|
||||
"updateResult": "已在 {ok} 个节点上触发更新,{failed} 个失败",
|
||||
"updateNoneEligible": "请至少选择一个在线且已启用的节点"
|
||||
},
|
||||
"agentHint": "Agent 节点由节点机主动连接主控,无需填写地址、端口和 API 令牌。创建后会生成安装命令。",
|
||||
"probeOnly": "纯探针",
|
||||
"probeOnlyHint": "纯探针模式:该节点不运行任何代理核心,只保留探针功能(心跳、指标与延迟探测)。",
|
||||
"agentTokenTitle": "Agent 节点安装",
|
||||
@@ -878,8 +877,6 @@
|
||||
"agentMasterUrlHint": "主控地址(agent 用来连接主控,如有域名或反代请改成对外地址):",
|
||||
"agentMasterUrlRequired": "主控地址为空——请先填入主控地址,之后才会生成安装命令。",
|
||||
"probe": {
|
||||
"title": "公开状态页",
|
||||
"hint": "公开状态页的展示与套餐信息,保存后立即生效。",
|
||||
"public": "在公开页显示",
|
||||
"publicRemark": "公开备注",
|
||||
"group": "分组",
|
||||
@@ -894,8 +891,8 @@
|
||||
"cycleYearly": "每年",
|
||||
"cycleBiennial": "每两年",
|
||||
"cycleTriennial": "每三年",
|
||||
"expiresAt": "到期日期(YYYY-MM-DD)",
|
||||
"trafficLimit": "流量上限(GB,0 = 不限)",
|
||||
"expiresAt": "到期日期",
|
||||
"trafficLimit": "流量上限(GB)",
|
||||
"trafficMode": "流量统计口径",
|
||||
"modeSum": "上行 + 下行",
|
||||
"modeUp": "只算上行",
|
||||
@@ -1315,15 +1312,12 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "随机打乱 Host",
|
||||
"tags": "标签",
|
||||
"nodeGuids": "节点",
|
||||
"excludeFromSubTypes": "从格式中排除",
|
||||
"inheritAddress": "继承地址"
|
||||
},
|
||||
"hints": {
|
||||
"address": "留空则继承入站自身的地址。",
|
||||
"port": "填 0 则继承入站的端口。",
|
||||
"tags": "对终端用户不可见;仅随 RAW 订阅发送。只能包含大写字母、数字、_ 和 :。",
|
||||
"nodeGuids": "选择从此主机解析的节点。仅用于可视化关联。",
|
||||
"serverDescription": "可选备注,显示在备注下方。",
|
||||
"allowInsecure": "跳过 TLS 证书验证(allowInsecure / skip-cert-verify)。",
|
||||
"vlessRoute": "嵌入 UUID 的单个 VLESS 路由值(0-65535),例如 443。留空表示不路由。",
|
||||
|
||||
@@ -865,7 +865,6 @@
|
||||
"updateResult": "已在 {ok} 個節點上觸發更新,{failed} 個失敗",
|
||||
"updateNoneEligible": "請至少選擇一個在線且已啟用的節點"
|
||||
},
|
||||
"agentHint": "Agent nodes connect out to the master; no address, port or API token needed. An install command is generated on creation.",
|
||||
"probeOnly": "Probe only",
|
||||
"probeOnlyHint": "Probe-only mode: no proxy core runs on this node; only monitoring (heartbeat, metrics and latency probes) is active.",
|
||||
"agentTokenTitle": "Agent Node Setup",
|
||||
@@ -878,8 +877,6 @@
|
||||
"agentMasterUrlHint": "Master URL (used by agent to reach the master; change to public address if behind proxy):",
|
||||
"agentMasterUrlRequired": "Master URL is empty — enter the master address first; the install command is generated only then.",
|
||||
"probe": {
|
||||
"title": "Public status page",
|
||||
"hint": "Display and plan data for the public status page. Saving takes effect immediately.",
|
||||
"public": "Show on public page",
|
||||
"publicRemark": "Public remark",
|
||||
"group": "Group",
|
||||
@@ -894,8 +891,8 @@
|
||||
"cycleYearly": "Yearly",
|
||||
"cycleBiennial": "Biennial",
|
||||
"cycleTriennial": "Triennial",
|
||||
"expiresAt": "Expires on (YYYY-MM-DD)",
|
||||
"trafficLimit": "Traffic limit (GB, 0 = unlimited)",
|
||||
"expiresAt": "Expires on",
|
||||
"trafficLimit": "Traffic limit (GB)",
|
||||
"trafficMode": "Traffic counting",
|
||||
"modeSum": "Up + down",
|
||||
"modeUp": "Up only",
|
||||
@@ -1315,15 +1312,12 @@
|
||||
"mihomoX25519": "Mihomo X25519",
|
||||
"shuffleHost": "隨機排序 Host",
|
||||
"tags": "標籤",
|
||||
"nodeGuids": "節點",
|
||||
"excludeFromSubTypes": "從格式中排除",
|
||||
"inheritAddress": "繼承地址"
|
||||
},
|
||||
"hints": {
|
||||
"address": "留空以繼承入站本身的地址。",
|
||||
"port": "0 表示繼承入站的連接埠。",
|
||||
"tags": "對終端使用者不可見;僅隨 RAW 訂閱傳送。只能使用大寫字母、數字、_ 和 :。",
|
||||
"nodeGuids": "選擇由此 Host 解析而來的節點。僅為視覺上的指派。",
|
||||
"serverDescription": "顯示於備註下方的選填註記。",
|
||||
"allowInsecure": "略過 TLS 憑證驗證(allowInsecure / skip-cert-verify)。",
|
||||
"vlessRoute": "嵌入 UUID 的單一 VLESS 路由值(0-65535),例如 443。留空表示無。",
|
||||
|
||||
@@ -343,7 +343,7 @@ reset_user() {
|
||||
|
||||
echo -e "Panel login username has been reset to: ${green} ${config_account} ${plain}"
|
||||
echo -e "Panel login password has been reset to: ${green} ${config_password} ${plain}"
|
||||
echo -e "${green} Please use the new login username and password to access the X-UI panel. Also remember them! ${plain}"
|
||||
echo -e "${green} Please use the new login username and password to access the L-UI panel. Also remember them! ${plain}"
|
||||
confirm_restart
|
||||
}
|
||||
|
||||
@@ -3412,7 +3412,7 @@ show_usage() {
|
||||
show_menu() {
|
||||
echo -e "
|
||||
╔────────────────────────────────────────────────╗
|
||||
│ ${green}3X-UI Panel Management Script${plain} │
|
||||
│ ${green}L-UI Panel Management Script${plain} │
|
||||
│ ${green}0.${plain} Exit Script │
|
||||
│────────────────────────────────────────────────│
|
||||
│ ${green}1.${plain} Install │
|
||||
|
||||
Reference in New Issue
Block a user