main 582d408f6021 cached
58 files
740.0 KB
250.5k tokens
18 symbols
1 requests
Download .txt
Showing preview only (955K chars total). Download the full file or copy to clipboard to get everything.
Repository: hello-yunshu/Xray_bash_onekey
Branch: main
Commit: 582d408f6021
Files: 58
Total size: 740.0 KB

Directory structure:
gitextract_9rt8erfj/

├── .dockerignore
├── .editorconfig
├── .github/
│   └── workflows/
│       ├── cleanup.yml
│       ├── i18n.yml
│       └── translate_readme.yml
├── DOCKER.md
├── Dockerfile
├── LICENSE
├── README.md
├── VLESS_reality/
│   └── config.json
├── VLESS_tls/
│   └── config.json
├── VLESS_xtls/
│   └── config.json
├── auto_update.sh
├── docker-compose.yml
├── docker-entrypoint.sh
├── fail2ban_manager.sh
├── fake-systemctl
├── file_manager.sh
├── install.sh
├── languages/
│   ├── en/
│   │   ├── DOCKER.md
│   │   ├── LC_MESSAGES/
│   │   │   ├── version
│   │   │   └── xray_install.mo
│   │   └── README.md
│   ├── fa/
│   │   ├── DOCKER.md
│   │   ├── LC_MESSAGES/
│   │   │   ├── version
│   │   │   └── xray_install.mo
│   │   └── README.md
│   ├── fr/
│   │   ├── DOCKER.md
│   │   ├── LC_MESSAGES/
│   │   │   ├── version
│   │   │   └── xray_install.mo
│   │   └── README.md
│   ├── ko/
│   │   ├── DOCKER.md
│   │   ├── LC_MESSAGES/
│   │   │   ├── version
│   │   │   └── xray_install.mo
│   │   └── README.md
│   ├── ru/
│   │   ├── DOCKER.md
│   │   ├── LC_MESSAGES/
│   │   │   ├── version
│   │   │   └── xray_install.mo
│   │   └── README.md
│   └── zh_CN/
│       └── LC_MESSAGES/
│           ├── version
│           └── xray_install.mo
├── po/
│   ├── POTFILES.in
│   ├── cache_English.json
│   ├── cache_French.json
│   ├── cache_Korean.json
│   ├── cache_Persian.json
│   ├── cache_Russian.json
│   ├── en.po
│   ├── fa.po
│   ├── fr.po
│   ├── ko.po
│   ├── ru.po
│   ├── xray_install.pot
│   └── zh_CN.po
├── ssl_update.sh
├── status_config.json
├── translate.py
└── translate_readme.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
.github/
.git/
.gitignore
.editorconfig
LICENSE
po/
translate.py
translate_readme.py
Dockerfile
docker-compose.yml
docker-entrypoint.sh
fake-systemctl
.dockerignore


================================================
FILE: .editorconfig
================================================
root = true
[*]
end_of_line = crlf
insert_final_newline = true
charset = utf-8


================================================
FILE: .github/workflows/cleanup.yml
================================================
name: Cleanup Workflow Runs

on:
  workflow_dispatch:
  schedule:
    - cron: '0 0 * * *'

jobs:
  del_runs:
    runs-on: ubuntu-latest
    permissions:
      actions: write
      contents: read
    steps:
      - name: Delete workflow runs
        uses: Mattraks/delete-workflow-runs@main
        with:
          token: ${{ github.token }}
          repository: ${{ github.repository }}
          retain_days: 14
          keep_minimum_runs: 10
          

================================================
FILE: .github/workflows/i18n.yml
================================================
name: Update Translations

on:
  push:
    paths:
      - 'install.sh'
      - 'fail2ban_manager.sh'
      - 'file_manager.sh'
      - 'translate.py'
    branches:
      - main
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  update-translations:
    runs-on: ubuntu-latest
    
    steps:
      - name: Check running workflows
        uses: styfle/cancel-workflow-action@main
        with:
          access_token: ${{ github.token }}
      
      - uses: actions/checkout@main
        with:
          persist-credentials: true
      
      - name: Restore translation cache
        id: cache-translations
        uses: actions/cache@main
        with:
          path: po/cache_*.json
          key: ${{ runner.os }}-translations-${{ hashFiles('po/*.po') }}
          restore-keys: |
            ${{ runner.os }}-translations-
      
      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y gettext python3 python3-pip i18nspector
          pip3 install googletrans-py langdetect openai

      - name: Cache Python dependencies
        uses: actions/cache@main
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-googletrans-py-langdetect-openai
          restore-keys: |
            ${{ runner.os }}-pip-

      - name: Setup directories
        run: |
          mkdir -p po
          mkdir -p languages/{en,fa,ru,zh_CN,ko,fr}/LC_MESSAGES
      
      - name: Extract strings from specified scripts
        run: |
          echo "install.sh" > po/POTFILES.in
          echo "fail2ban_manager.sh" >> po/POTFILES.in
          echo "file_manager.sh" >> po/POTFILES.in
          
          xgettext \
            --files-from=po/POTFILES.in \
            --from-code=UTF-8 \
            --language=Shell \
            --keyword=gettext \
            --package-name=xray_install \
            --package-version=1.0 \
            --msgid-bugs-address=https://github.com/hello-yunshu/Xray_bash_onekey/issues \
            --copyright-holder="yunshu" \
            --output=po/xray_install.pot
      
      - name: Update/Create PO files
        run: |
          rm -f po/*.po
          for lang in zh_CN en fa ru ko fr; do
            msginit --no-translator --locale=$lang --input=po/xray_install.pot --output=po/$lang.po
          done
      
      - name: Auto translate
        run: python3 translate.py
        env:
          AI_API_KEY: ${{ secrets.AI_API_KEY }}
      
      - name: Compile MO files if necessary
        run: |
          needs_compile=false
          for lang in en fa ru ko fr; do
            if [ ! -f "po/${lang}.po.no-update" ]; then
              needs_compile=true
              if ! msgfmt --check -v --statistics -o languages/${lang}/LC_MESSAGES/xray_install.mo po/${lang}.po; then
                echo "Error in ${lang}.po file:"
                cat po/${lang}.po
              fi
            fi
          done
          if [ "$needs_compile" = false ]; then
            echo "No translations were updated. Skipping MO file compilation."
          fi
      
      - name: Run i18nspector checks on PO files
        run: |
          i18nspector check po/
      
      - name: Run i18nspector checks on MO files if compiled
        run: |
          for lang in en fa ru ko fr; do
            if [ ! -f "po/${lang}.po.no-update" ] && [ -f "languages/${lang}/LC_MESSAGES/xray_install.mo" ]; then
              i18nspector check-binary languages/${lang}/LC_MESSAGES/xray_install.mo
            fi
          done
      
      - name: Save translation cache
        if: always()
        uses: actions/cache@main
        with:
          path: po/cache_*.json
          key: ${{ runner.os }}-translations-${{ hashFiles('po/*.po') }}
      
      - name: Pull changes
        run: |
          git pull origin main
      
      - name: Commit changes if necessary
        run: |
          needs_commit=false
          for lang in en fa ru ko fr; do
            if [ ! -f "po/${lang}.po.no-update" ]; then
              needs_commit=true
              break
            else
                rm po/${lang}.po.no-update
            fi
          done
          if [ "$needs_commit" = true ]; then
            git config --local user.email "action@github.com"
            git config --local user.name "GitHub Action"
            git add po/ languages/
            git commit -m "Update translations for all languages [$(date +%Y-%m-%d)]" || echo "No changes to commit"
            git push 
          else
            echo "No translations were updated. Skipping commit and push."
          fi

================================================
FILE: .github/workflows/translate_readme.yml
================================================
name: Translate README

on:
  push:
    paths:
      - 'README.md'
    branches:
      - main
  workflow_dispatch:

jobs:
  translate-readme:
    runs-on: ubuntu-latest
    
    steps:
      - name: Check running workflows
        uses: styfle/cancel-workflow-action@main
        with:
          access_token: ${{ github.token }}
      
      - uses: actions/checkout@main
        with:
          persist-credentials: true
      
      - name: Set up Python
        uses: actions/setup-python@main
        with:
          python-version: '3.x'
      
      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y gettext
          pip install --upgrade pip
          pip install googletrans==4.0.0-rc1 langdetect markdown beautifulsoup4
        # 确保使用具体版本,避免兼容性问题
      
      - name: Cache Python dependencies
        uses: actions/cache@main
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('**/translate_readme.py') }}
          restore-keys: |
            ${{ runner.os }}-pip-
      
      - name: Translate README
        run: python3 translate_readme.py README.md languages/
      
      - name: Pull changes
        run: |
          git pull origin main

      - name: Commit changes
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add languages/
          git commit -m "Update translations for README [$(date +%Y-%m-%d)]" || echo "No changes to commit"
          git push https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git HEAD:main 

================================================
FILE: DOCKER.md
================================================
# Docker 部署指南

简体中文 | [English](/languages/en/DOCKER.md) | [Français](/languages/fr/DOCKER.md) | [Русский](/languages/ru/DOCKER.md) | [فارسی](/languages/fa/DOCKER.md) | [한국어](/languages/ko/DOCKER.md)

本文档介绍如何使用 Docker 运行 Xray 一键脚本。镜像预装了 Xray 和 Nginx,容器内可直接使用原脚本的所有功能。

## 快速启动

### 1. 克隆仓库并构建

```bash
git clone https://github.com/hello-yunshu/Xray_bash_onekey.git
cd Xray_bash_onekey
docker compose up -d
```

### 2. 进入交互式安装菜单

```bash
docker attach xray-onekey
```

首次运行会自动启动安装脚本,按照提示完成配置即可。退出菜单后容器自动进入守护模式。

### 3. 后续管理

```bash
docker exec -it xray-onekey idleleo
```

## 运行模式

| 模式 | 说明 | 命令 |
|------|------|------|
| `idleleo`(默认) | 启动服务并进入管理菜单 | `docker compose up -d` + `docker attach xray-onekey` |
| `start` | 仅启动服务(守护模式) | 修改 `docker-compose.yml` 中 `command: start` |
| `shell` | 启动服务并进入 Shell | `docker exec -it xray-onekey bash` |

## 管理操作

所有原脚本命令均可使用:

```bash
docker exec -it xray-onekey idleleo          # 管理菜单
docker exec -it xray-onekey idleleo -s        # 查看安装信息
docker exec -it xray-onekey idleleo -x        # 更新 Xray
docker exec -it xray-onekey idleleo -n        # 更新 Nginx
docker exec -it xray-onekey idleleo -h        # 查看帮助
```

## 使用 docker run

```bash
docker build -t xray-onekey .

docker run -d --name xray-onekey   --network host   --cap-add NET_ADMIN   -e TZ=Asia/Shanghai   -v xray-conf:/etc/idleleo/conf   -v xray-cert:/etc/idleleo/cert   -v xray-info:/etc/idleleo/info   -v xray-logs:/var/log/xray   -v acme-data:/root/.acme.sh   -it xray-onekey
```

## 数据持久化

| Volume | 容器路径 | 说明 |
|--------|---------|------|
| `xray-conf` | `/etc/idleleo/conf` | Xray 和 Nginx 配置文件 |
| `xray-cert` | `/etc/idleleo/cert` | SSL 证书文件 |
| `xray-info` | `/etc/idleleo/info` | 连接信息和状态文件 |
| `xray-logs` | `/var/log/xray` | Xray 日志文件 |
| `acme-data` | `/root/.acme.sh` | acme.sh 证书签发数据 |

## 网络配置

容器使用 `network_mode: host`,直接使用宿主机网络:

* Reality 模式需要看到真实客户端 IP
* TLS 模式需要直接绑定 443/80 端口
* 避免额外的 NAT 转发性能损耗

## 注意事项

* 容器内使用 `fake-systemctl` 替代 systemd,`systemctl` 命令可正常使用
* 内置看门狗每 30 秒检查服务状态,异常时自动重启
* 退出管理菜单后容器自动进入守护模式,服务不会中断
* 防火墙建议在宿主机层面管理
* 证书自动续签在容器内可正常工作(需确保 80 端口可访问)

## 故障排查

```bash
docker logs xray-onekey                    # 查看容器日志
docker exec -it xray-onekey bash           # 进入容器
docker exec -it xray-onekey idleleo -s     # 查看安装信息
```

### 完全重置

```bash
docker compose down
docker volume rm xray-conf xray-cert xray-info xray-logs acme-data
docker compose up -d
```


================================================
FILE: Dockerfile
================================================
FROM debian:bookworm-slim

ARG XRAY_VERSION=26.3.27
ARG NGINX_BUILD_VERSION=2026.04.14

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y --no-install-recommends \
    bc \
    ca-certificates \
    cron \
    curl \
    dbus \
    fail2ban \
    gettext \
    git \
    gnupg \
    gzip \
    iftop \
    iptables \
    iptables-persistent \
    jq \
    lsof \
    netcat-openbsd \
    nmap \
    openssl \
    procps \
    psmisc \
    python3 \
    qrencode \
    socat \
    sysvinit-utils \
    unzip \
    vim \
    && rm -rf /var/lib/apt/lists/*

RUN groupadd -f nogroup && \
    id nobody >/dev/null 2>&1 || useradd -g nogroup -s /usr/sbin/nologin nobody

RUN temp_dir=$(mktemp -d) && cd "$temp_dir" && \
    nginx_filename="xray-nginx-custom-$(dpkg --print-architecture).tar.gz" && \
    curl -L -o "$nginx_filename" "https://github.com/hello-yunshu/Xray_bash_onekey_Nginx/releases/download/v${NGINX_BUILD_VERSION}/$nginx_filename" && \
    tar -xzf "$nginx_filename" && \
    mv ./nginx /usr/local/nginx && \
    cd / && rm -rf "$temp_dir"

RUN curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh | bash -s -- install -f --version v${XRAY_VERSION} && \
    rm -f /usr/local/etc/xray/config.json

COPY fake-systemctl /usr/local/bin/systemctl
RUN chmod +x /usr/local/bin/systemctl

WORKDIR /etc/idleleo

COPY . /etc/idleleo/

RUN ln -sf /etc/idleleo/install.sh /usr/bin/idleleo && \
    ln -sf /etc/idleleo/conf/xray/config.json /usr/local/etc/xray/config.json && \
    chmod +x /etc/idleleo/install.sh /etc/idleleo/auto_update.sh \
    /etc/idleleo/ssl_update.sh /etc/idleleo/fail2ban_manager.sh \
    /etc/idleleo/file_manager.sh && \
    mkdir -p /etc/idleleo/conf/xray /etc/idleleo/conf/nginx \
    /etc/idleleo/cert /etc/idleleo/info /etc/idleleo/logs \
    /etc/idleleo/tmp /var/log/xray /root/.acme.sh

RUN mkdir -p /etc/systemd/system && \
    printf '[Unit]\nDescription=Xray Service\n[Service]\nType=simple\nExecStart=/usr/local/bin/xray run -config /etc/idleleo/conf/xray/config.json\n[Install]\nWantedBy=multi-user.target\n' > /etc/systemd/system/xray.service && \
    printf '[Unit]\nDescription=NGINX HTTP and reverse proxy server\n[Service]\nType=forking\nPIDFile=/usr/local/nginx/logs/nginx.pid\nExecStart=/usr/local/nginx/sbin/nginx\nExecReload=/usr/local/nginx/sbin/nginx -s reload\nExecStop=/bin/kill -s QUIT \\$MAINPID\n[Install]\nWantedBy=multi-user.target\n' > /etc/systemd/system/nginx.service && \
    echo '* soft nofile 65536' >> /etc/security/limits.conf && \
    echo '* hard nofile 65536' >> /etc/security/limits.conf

COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh

EXPOSE 443 80

VOLUME ["/etc/idleleo/conf", "/etc/idleleo/cert", "/etc/idleleo/info", "/var/log/xray", "/root/.acme.sh"]

ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["idleleo"]


================================================
FILE: LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.

  The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.


================================================
FILE: README.md
================================================
# Xray 一键安装脚本 — Reality / VLESS WebSocket/gRPC+TLS + Nginx

简体中文 | [English](/languages/en/README.md) | [Français](/languages/fr/README.md) | [Русский](/languages/ru/README.md) | [فارسی](/languages/fa/README.md) | [한국어](/languages/ko/README.md)

[![GitHub stars](https://img.shields.io/github/stars/hello-yunshu/Xray_bash_onekey?color=%230885ce)](https://github.com/hello-yunshu/Xray_bash_onekey/stargazers) [![GitHub forks](https://img.shields.io/github/forks/hello-yunshu/Xray_bash_onekey?color=%230885ce)](https://github.com/hello-yunshu/Xray_bash_onekey/network) [![GitHub issues](https://img.shields.io/github/issues/hello-yunshu/Xray_bash_onekey)](https://github.com/hello-yunshu/Xray_bash_onekey/issues)

> Thanks for non-commercial open source development authorization by JetBrains

## 功能特性

* 输入 `idleleo` 即可管理脚本([查看 `idleleo` 背景故事](https://github.com/hello-yunshu/Xray_bash_onekey/wiki/%E8%BF%B7%E9%9B%BE%E5%90%8E%E7%9A%84%E7%9C%9F%E5%AE%B9))
* 采用 Qwen-MT-Plus AI 实现多语言精准翻译
* 支持 Reality 协议,建议搭配 Nginx 前置(脚本内可安装)
* 内置 fail2ban 防护(脚本内可安装)
* 采用 [@DuckSoft](https://github.com/DuckSoft) 的分享链接[提案](https://github.com/XTLS/Xray-core/issues/91)(beta),兼容 Qv2ray、V2rayN、V2rayNG
* 采用 [XTLS](https://github.com/XTLS/Xray-core/issues/158) 提案,遵循 [UUIDv5](https://tools.ietf.org/html/rfc4122#section-4.3) 标准,支持自定义字符串映射至 VLESS UUID
* 支持 gRPC 协议:[使用 gRPC 协议](https://hey.run/archives/xrayjin-jie-wan-fa---shi-yong-grpcxie-yi)
* 支持 Reality / ws/gRPC 负载均衡:
  - [部署 Reality 负载均衡](https://hey.run/archives/bushu-reality-balance)
  - [搭建后端负载均衡](https://hey.run/archives/xrayjin-jie-wan-fa---da-jian-hou-duan-fu-wu-qi-fu-zai-jun-heng)

## 延伸阅读

* Reality 安装指南:[搭建 Xray Reality 服务器](https://hey.run/archives/da-jian-xray-reality-xie-yi-fu-wu-qi)
* Reality 协议风险:[Xray Reality 协议的风险](https://hey.run/archives/reality-xie-yi-de-feng-xian)
* Reality 加速服务器:[利用 Reality 协议"漏洞"加速服务器](https://hey.run/archives/use-reality)

## Telegram 群组

* 交流群:[点击加入](https://t.me/+48VSqv7xIIFmZDZl)

## 准备工作

* 一台境外服务器,具备公网 IP
* 安装 Reality 协议:需准备符合 Xray 要求的目标域名
* 安装 TLS 版本:需准备域名并添加 A 记录
* 阅读 [Xray 官方文档](https://xtls.github.io),了解 Reality、TLS、WebSocket、gRPC 及 Xray 相关概念
* **确保已安装 curl**:CentOS 用户执行 `yum install -y curl`;Debian/Ubuntu 用户执行 `apt install -y curl`

## 快速安装

```bash
bash <(curl -Ss https://raw.githubusercontent.com/hello-yunshu/Xray_bash_onekey/main/install.sh)
```

## Docker 部署

支持使用 Docker 部署,镜像预装 Xray 和 Nginx,容器内可直接使用原脚本所有功能。详见 [Docker 部署指南](/DOCKER.md)。

```bash
git clone https://github.com/hello-yunshu/Xray_bash_onekey.git
cd Xray_bash_onekey
docker compose up -d
docker attach xray-onekey
```

## 注意事项

* 不了解各项设置含义时,除必填项外请使用默认值(全程回车即可)
* Cloudflare 用户请在安装完成后再开启 CDN
* 本脚本需要 Linux 基础知识及计算机网络常识
* 支持 Debian 12+ / Ubuntu 24.04+ / CentOS Stream 8+,部分 CentOS 模板可能存在编译问题,建议遇到问题时更换系统
* 建议单服务器仅部署单个代理,使用默认 443 端口
* 自定义字符串映射至 UUIDv5 需要客户端支持
* 推荐在纯净环境下使用;新手请勿使用 CentOS
* 本程序依赖 Nginx,已通过 [LNMP](https://lnmp.org) 等脚本安装过 Nginx 的用户请注意潜在冲突
* 请勿在未验证可用性前将本脚本用于生产环境
* 作者仅提供有限支持(因为太笨了)

## 鸣谢

* 基于 [wulabing/V2Ray_ws-tls_bash_onekey](https://github.com/wulabing/V2Ray_ws-tls_bash_onekey) 开发
* TCP 加速脚本引用自 [ylx2016/Linux-NetSpeed](https://github.com/ylx2016/Linux-NetSpeed)

## 证书配置

**自定义证书**:将 crt 和 key 文件分别命名为 `xray.crt` 和 `xray.key`,放入 `/etc/idleleo/cert` 目录(目录不存在则先创建)。请注意证书权限及有效期,自定义证书过期后需自行续签。

**自动证书**:脚本支持自动生成 Let's Encrypt 证书(有效期 3 个月),理论上支持自动续签。

## 查看客户端配置

```bash
cat /etc/idleleo/info/xray_info.inf
```

## Xray 简介

* Xray 是一款优秀的开源网络代理工具,支持 Windows、macOS、Android、iOS、Linux 等全平台
* 本脚本为一键完整配置脚本,所有流程正常完成后,按输出结果设置客户端即可使用
* **强烈建议**全面了解程序的工作流程及原理

## 服务管理

| 操作 | 命令 |
|------|------|
| 启动 Xray | `systemctl start xray` |
| 停止 Xray | `systemctl stop xray` |
| 启动 Nginx | `systemctl start nginx` |
| 停止 Nginx | `systemctl stop nginx` |

## 相关目录

| 内容 | 路径 |
|------|------|
| Xray 服务端配置 | `/etc/idleleo/conf/xray/config.json` |
| Nginx 目录 | `/usr/local/nginx` |
| 证书文件 | `/etc/idleleo/cert/xray.key`、`/etc/idleleo/cert/xray.crt` |
| 配置信息等 | `/etc/idleleo` |


================================================
FILE: VLESS_reality/config.json
================================================
{
    "log": {
        "access": "/var/log/xray/access.log",
        "error": "/var/log/xray/error.log",
        "loglevel": "warning"
    },
    "inbounds": [
        {
            "port": 9443,
            "listen": "0.0.0.0",
            "protocol": "VLESS",
            "tag": "VLESS-Reality-in",
            "settings": {
                "clients": [
                    {
                        "id": "UUID",
                        "flow": "xtls-rprx-vision",
                        "level": 0,
                        "email": "me@hey.run"
                    }
                ],
                "decryption": "none"
            },
            "streamSettings": {
                "network": "raw",
                "security": "reality",
                "realitySettings": {
                    "target": "example.com:443", 
                    "serverNames": ["example.com"],
                    "privateKey": "privateKey",
                    "shortIds": ["shortIds"]
                }
            }
        },
        {
            "port": 10086,
            "listen": "127.0.0.1",
            "tag": "VLESS-ws-in",
            "protocol": "VLESS",
            "settings": {
                "clients": [
                    {
                        "id": "UUID",
                        "level": 0,
                        "email": "ws@hey.run"
                    }
                ],
                "decryption": "none"
            },
            "streamSettings": {
                "network": "ws",
                "security": "none",
                "wsSettings": {
                    "path": "/websocket"
                }
            }
        },
        {
            "port": 10087,
            "listen": "127.0.0.1",
            "tag": "VLESS-gRPC-in",
            "protocol": "VLESS",
            "settings": {
                "clients": [
                    {
                        "id": "UUID",
                        "level": 0,
                        "email": "me@hey.run"
                    }
                ],
                "decryption": "none"
            },
            "streamSettings": {
                "network": "grpc",
                "security": "none",
                "grpcSettings": {
                    "serviceName": "grpc",
                    "multiMode": true,
                    "idle_timeout": 20
                }
            }
        }
    ],
    "outbounds": [
        {
            "protocol": "freedom",
            "settings": {
                "domainStrategy": "UseIPv4v6"
            },
            "tag": "direct"
        },
        {
            "protocol": "blackhole",
            "tag": "blocked"
        }
    ],
    "dns": {
        "servers": [
            "https+local://dns.google/dns-query",
            "https+local://cloudflare-dns.com/dns-query",
            "https+local://doh.opendns.com/dns-query",
            "208.67.222.222",
            "8.8.8.8",
            "1.1.1.1",
            "localhost"
        ],
        "disableCache": false
    },
    "routing": {
        "domainStrategy": "AsIs",
        "rules": [
            {
                "type": "field",
                "inboundTag": [
                    "VLESS-Reality-in",
                    "VLESS-ws-in",
                    "VLESS-gRPC-in"
                ],
                "outboundTag": "direct"
            }
        ]
    }
}


================================================
FILE: VLESS_tls/config.json
================================================
{
    "log": {
        "access": "/var/log/xray/access.log",
        "error": "/var/log/xray/error.log",
        "loglevel": "warning"
    },
    "inbounds": [
        {
            "port": 10086,
            "listen": "127.0.0.1",
            "tag": "VLESS-ws-in",
            "protocol": "VLESS",
            "settings": {
                "clients": [
                    {
                        "id": "UUID",
                        "level": 0,
                        "email": "me@hey.run"
                    }
                ],
                "decryption": "none"
            },
            "streamSettings": {
                "network": "ws",
                "security": "none",
                "wsSettings": {
                    "path": "/ray/"
                }
            }
        },
        {
            "port": 10087,
            "listen": "127.0.0.1",
            "tag": "VLESS-gRPC-in",
            "protocol": "VLESS",
            "settings": {
                "clients": [
                    {
                        "id": "UUID",
                        "level": 0,
                        "email": "me@hey.run"
                    }
                ],
                "decryption": "none"
            },
            "streamSettings": {
                "network": "grpc",
                "security": "none",
                "grpcSettings": {
                    "serviceName": "grpc",
                    "multiMode": true,
                    "idle_timeout": 20
                }
            }
        }
    ],
    "outbounds": [
        {
            "protocol": "freedom",
            "settings": {
                "domainStrategy": "UseIPv4v6"
            },
            "tag": "direct"
        },
        {
            "protocol": "blackhole",
            "tag": "blocked"
        }
    ],
    "dns": {
        "servers": [
            "https+local://dns.google/dns-query",
            "https+local://cloudflare-dns.com/dns-query",
            "https+local://doh.opendns.com/dns-query",
            "208.67.222.222",
            "8.8.8.8",
            "1.1.1.1",
            "localhost"
        ],
        "disableCache": false
    },
    "routing": {
        "domainStrategy": "AsIs",
        "rules": [
            {
                "type": "field",
                "inboundTag": [
                    "VLESS-ws-in",
                    "VLESS-gRPC-in"
                ],
                "outboundTag": "direct"
            }
        ]
    }
}


================================================
FILE: VLESS_xtls/config.json
================================================
{
    "log": {
        "access": "/var/log/xray/access.log",
        "error": "/var/log/xray/error.log",
        "loglevel": "warning"
    },
    "inbounds": [
        {
            "port": 443,
            "listen": "0.0.0.0",
            "protocol": "VLESS",
            "tag": "VLESS-XTLS-in",
            "settings": {
                "clients": [
                    {
                        "id": "UUID",
                        "flow": "xtls-rprx-vision",
                        "level": 0,
                        "email": "me@hey.run"
                    }
                ],
                "decryption": "none",
                "fallbacks": [
                    {
                        "outboundTag": "blocked",
                        "xver": 1
                    }
                ]
            },
            "streamSettings": {
                "network": "raw",
                "security": "none"
            }
        }
    ],
    "outbounds": [
        {
            "protocol": "freedom",
            "settings": {
                "domainStrategy": "UseIPv4v6"
            },
            "tag": "direct"
        },
        {
            "protocol": "blackhole",
            "tag": "blocked"
        }
    ],
    "dns": {
        "servers": [
            "8.8.8.8",
            "1.1.1.1",
            "localhost"
        ],
        "disableCache": false
    },
    "routing": {
        "domainStrategy": "AsIs",
        "rules": [
            {
                "type": "field",
                "inboundTag": [
                    "VLESS-XTLS-in"
                ],
                "outboundTag": "direct"
            }
        ]
    }
}

================================================
FILE: auto_update.sh
================================================
#!/usr/bin/env bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

VERSION="1.0.4"

idleleo_dir="/etc/idleleo"
local_bin="/usr/local"
nginx_dir="${local_bin}/nginx"
xray_conf_dir="${idleleo_dir}/conf/xray"
xray_conf="${xray_conf_dir}/config.json"
log_dir="${idleleo_dir}/logs"
log_file="${log_dir}/auto_update.log"
running_file="${log_dir}/auto_update.running"
xray_qr_config_file="${idleleo_dir}/info/vless_qr.json"
failed_update_marker="${log_dir}/update_failed.mark"

check_update() {
    temp_file="/tmp/temp_script.sh"
    if ! curl -s -o "$temp_file" "https://github.com/hello-yunshu/Xray_bash_onekey/raw/refs/heads/main/auto_update.sh"; then
        echo "Failed to download remote script" >>${log_file}
        return 1
    fi

    remote_version=$(grep "^VERSION=" "$temp_file" | cut -d'"' -f2)
    if [ -z "$remote_version" ]; then
        echo "Unable to get remote version number" >>${log_file}
        return 1
    fi

    if [ "$VERSION" != "$remote_version" ]; then
        echo "New version found: $remote_version" >>${log_file}
        cp "$temp_file" "$0"
        chmod +x "$0"
        rm -f "$temp_file"
        exec "$0" "$@"
    fi

    rm -f "$temp_file"
    return 0
}

if [[ -f "${failed_update_marker}" ]]; then
    echo "Previous update failed, skipping auto update. Remove ${failed_update_marker} or perform manual update to re-enable." >>${log_file}
    exit 0
fi

[[ ! -d "${log_dir}" ]] && mkdir -p ${log_dir}
if [[ -f "${running_file}" ]]; then
    echo "Previous auto update process is still running! Checked at: $(date '+%Y-%m-%d %H:%M') Manual troubleshooting recommended!" >>${log_file}
    exit 1
else
    touch ${running_file}
fi
[[ -f "${log_file}" ]] && rm -rf ${log_file}

echo "Update time: $(date '+%Y-%m-%d %H:%M')" >${log_file}

check_update

get_versions_all=$(curl -s https://cdn.jsdelivr.net/gh/hello-yunshu/Xray_bash_onekey_api@main/xray_shell_versions.json)

if [[ ! -f ${xray_qr_config_file} ]]; then
    echo "Config file not found, skipping update checks." >>${log_file}
    rm -rf ${running_file}
    exit 0
fi

info_extraction_all=$(jq -rc . ${xray_qr_config_file})

check_online_version() {
    echo ${get_versions_all} | jq -rc ".$1"
    [[ 0 -ne $? ]] && echo "Online version check failed, please try again later!" >>${log_file} && exit 1
}

info_extraction() {
    echo ${info_extraction_all} | jq -r ".$1"
}

shell_online_version="$(check_online_version shell_online_version)"
xray_online_version="$(check_online_version xray_online_version)"
nginx_online_version="$(check_online_version nginx_build_version)"

if [[ -f ${xray_qr_config_file} ]]; then
    if [[ $(info_extraction shell_version) == null ]] || [[ $(info_extraction shell_version) != ${shell_online_version} ]]; then
        bash idleleo -u auto_update
        [[ 0 -ne $? ]] && echo "Script update failed!" >>${log_file} && exit 1
        echo "Script updated successfully!" >>${log_file}
        add_shell_version=$(jq -r ". += {\"shell_version\": \"${shell_online_version}\"}" ${xray_qr_config_file})
        echo "${add_shell_version}" | jq . >${xray_qr_config_file}
    else
        echo "Script is up to date!" >>${log_file}
    fi
    if [[ $(info_extraction nginx_build_version) != null ]] && [[ -f "${nginx_dir}/sbin/nginx" ]]; then
        if [[ ${nginx_online_version} != $(info_extraction nginx_build_version) ]]; then
            echo "Updating Nginx..." >>${log_file}
            auto_update=YES bash idleleo -n auto_update
            if [[ $? -ne 0 ]]; then
                echo "Nginx update failed!" >>${log_file}
                touch ${failed_update_marker}
            else
                echo "Nginx updated successfully!" >>${log_file}
            fi
        else
            echo "Nginx is up to date!" >>${log_file}
        fi
    else
        echo "Nginx not installed!" >>${log_file}
    fi
    if [[ -f ${xray_qr_config_file} ]] && [[ -f ${xray_conf} ]] && [[ -f /usr/local/bin/xray ]]; then
        if [[ $(info_extraction xray_version) != null ]]; then
            if [[ ${xray_online_version} != $(info_extraction xray_version) ]]; then
                echo "Updating Xray..." >>${log_file}
                auto_update=YES bash idleleo -x auto_update
                if [[ $? -ne 0 ]]; then
                    echo "Xray update failed!" >>${log_file}
                    touch ${failed_update_marker}
                else
                    echo "Xray updated successfully!" >>${log_file}
                fi
            else
                echo "Xray is up to date!" >>${log_file}
            fi
        else
            echo "Xray version unknown, cannot auto update" >>${log_file}
        fi
    else
        echo "Xray not installed!" >>${log_file}
    fi
fi
rm -rf ${running_file}


================================================
FILE: docker-compose.yml
================================================
services:
  xray-onekey:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: xray-onekey
    restart: unless-stopped
    network_mode: host
    cap_add:
      - NET_ADMIN
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - xray-conf:/etc/idleleo/conf
      - xray-cert:/etc/idleleo/cert
      - xray-info:/etc/idleleo/info
      - xray-logs:/var/log/xray
      - acme-data:/root/.acme.sh
    stdin_open: true
    tty: true

volumes:
  xray-conf:
  xray-cert:
  xray-info:
  xray-logs:
  acme-data:


================================================
FILE: docker-entrypoint.sh
================================================
#!/bin/bash
set -e

export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

XRAY_BIN="/usr/local/bin/xray"
NGINX_BIN="/usr/local/nginx/sbin/nginx"
XRAY_CONF="/etc/idleleo/conf/xray/config.json"
NGINX_CONF="/usr/local/nginx/conf/nginx.conf"

_start_cron() {
    pgrep -f "cron" >/dev/null 2>&1 || cron
}

_start_services() {
    if [[ -f "$XRAY_CONF" ]] && [[ -x "$XRAY_BIN" ]]; then
        echo "[entrypoint] Starting Xray..."
        systemctl start xray
    fi
    if [[ -f "$NGINX_CONF" ]] && [[ -x "$NGINX_BIN" ]]; then
        echo "[entrypoint] Starting Nginx..."
        systemctl start nginx
    fi
}

STOPPING=0

_stop_services() {
    if [[ "$STOPPING" -eq 1 ]]; then return; fi
    STOPPING=1
    echo "[entrypoint] Stopping services..."
    if [[ -f /usr/local/nginx/logs/nginx.pid ]]; then
        "$NGINX_BIN" -s stop 2>/dev/null || true
    fi
    pkill -f "$NGINX_BIN" 2>/dev/null || true
    if [[ -f /var/run/xray.pid ]]; then
        kill "$(cat /var/run/xray.pid 2>/dev/null)" 2>/dev/null || true
    fi
    pkill -f "$XRAY_BIN" 2>/dev/null || true
    echo "[entrypoint] Services stopped."
}

_watchdog() {
    while [[ "$STOPPING" -eq 0 ]]; do
        if [[ -f "$XRAY_CONF" ]] && [[ -x "$XRAY_BIN" ]]; then
            if ! systemctl -q is-active xray 2>/dev/null; then
                echo "[watchdog] Xray not running, restarting..."
                systemctl start xray
            fi
        fi
        if [[ -f "$NGINX_CONF" ]] && [[ -x "$NGINX_BIN" ]]; then
            if ! systemctl -q is-active nginx 2>/dev/null; then
                echo "[watchdog] Nginx not running, restarting..."
                systemctl start nginx
            fi
        fi
        sleep 30
    done
}

trap '_stop_services; exit 0' SIGTERM SIGINT SIGQUIT

_start_cron

case "${1:-idleleo}" in
    idleleo)
        if [[ -f "$XRAY_CONF" ]] && [[ -x "$XRAY_BIN" ]]; then
            _start_services
            echo "[entrypoint] Services started. Launching management script..."
            echo "[entrypoint] Type 'exit' to return to daemon mode."
            bash /etc/idleleo/install.sh
            echo "[entrypoint] Entering daemon mode..."
            _watchdog
        else
            echo "[entrypoint] No config found. Launching install script..."
            bash /etc/idleleo/install.sh
            if [[ -f "$XRAY_CONF" ]]; then
                _start_services
                echo "[entrypoint] Installation complete. Entering daemon mode..."
            else
                echo "[entrypoint] No config. Entering daemon mode..."
            fi
            _watchdog
        fi
        ;;
    start)
        _start_services
        echo "[entrypoint] Services started. Daemon mode."
        _watchdog
        ;;
    shell|bash)
        _start_services
        echo "[entrypoint] Services started. Opening shell..."
        /bin/bash
        echo "[entrypoint] Entering daemon mode..."
        _watchdog
        ;;
    *)
        _start_services
        exec "$@"
        ;;
esac


================================================
FILE: fail2ban_manager.sh
================================================
#!/bin/bash

# 定义当前版本号
mf_SCRIPT_VERSION="1.2.1"

mf_main_menu() {
    check_system
    while true; do
        echo
        log_echo "${GreenBG} $(gettext "设置") Fail2ban $(gettext "用于防止暴力破解") ${Font}"
        log_echo "${Green} $(gettext "主菜单") ${Font}"
        log_echo "1. ${Green}$(gettext "安装") Fail2ban${Font}"
        log_echo "2. ${Green}$(gettext "管理") Fail2ban${Font}"
        log_echo "3. ${Green}$(gettext "卸载") Fail2ban${Font}"
        log_echo "4. ${Green}$(gettext "查看") Fail2ban $(gettext "状态")${Font}"
        log_echo "5. ${Green}$(gettext "退出")${Font}"
        local fail2ban_fq
        read_optimize "$(gettext "请选择一个选项"):" fail2ban_fq "" 1
        case $fail2ban_fq in
            1) mf_install_fail2ban ;;
            2) mf_manage_fail2ban ;;
            3) mf_uninstall_fail2ban ;;
            4) mf_display_fail2ban_status ;;
            5) source "${idleleo}" ;;
            *)
                echo
                log_echo "${Error} ${RedBG} $(gettext "无效的选择, 请重试") ${Font}"
                ;;
        esac
    done    
}

mf_install_fail2ban() {
    if command -v fail2ban-client &> /dev/null; then
        log_echo "${OK} ${Green} Fail2ban $(gettext "已经安装, 跳过安装步骤") ${Font}"
    else
        pkg_install "fail2ban"
        mf_configure_fail2ban
        judge "Fail2ban $(gettext "安装")"
        source "${idleleo}"
    fi
}

mf_ensure_sshd_config() {
    cat > /etc/fail2ban/jail.d/sshd.local << 'EOF'
[sshd]
enabled = true
filter = sshd
logpath = %(sshd_log)s
backend = systemd
maxretry = 5
bantime = 604800
EOF
}

mf_configure_fail2ban() {

    # 确保 jail.d 目录存在
    mkdir -p /etc/fail2ban/jail.d

    # 如果 jail.local 不存在,创建它
    if [[ ! -f "/etc/fail2ban/jail.local" ]]; then
        cp -fp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
    fi

    # systemd SSH 日志检查
    if ! journalctl -u ssh --since "1 hour ago" --no-pager -q | head -n 1 >/dev/null 2>&1; then
        log_echo "${Warning} ${YellowBG} $(gettext "systemd 无法读取 SSH 日志") ${Font}"
        log_echo "${Warning} ${YellowBG} $(gettext "跳过启用") SSH $(gettext "规则") ${Font}"
    else
        mf_ensure_sshd_config
    fi

    # 检查 Nginx 是否安装
    if [[ ${tls_mode} == "TLS" || ${reality_add_nginx} == "on" ]]; then
        if [[ ! -f "${nginx_dir}/sbin/nginx" ]]; then
            log_echo "${Warning} ${YellowBG} Nginx $(gettext "未安装, 请先安装") Nginx ${Font}"
            return
        fi
    fi

    # 配置 Nginx 相关规则
    if [[ ${tls_mode} == "TLS" || ${reality_add_nginx} == "on" ]]; then
        cat > /etc/fail2ban/jail.d/nginx-badbots.local << EOF
[nginx-badbots]
enabled  = true
port     = http,https,8080
filter   = apache-badbots
logpath  = ${nginx_dir}/logs/access.log
bantime  = 604800
maxretry = 5
EOF
        log_echo "${OK} ${GreenBG} $(gettext "已启用") nginx-badbots $(gettext "规则") ${Font}"

        cat > /etc/fail2ban/jail.d/nginx-botsearch.local << EOF
[nginx-botsearch]
enabled  = true
filter   = nginx-botsearch
logpath  = ${nginx_dir}/logs/access.log
           ${nginx_dir}/logs/error.log
bantime  = 604800
EOF
        log_echo "${OK} ${GreenBG} $(gettext "已启用") nginx-botsearch $(gettext "规则") ${Font}"
    fi

    # 启用 nginx-no-host 规则
    if [[ ${reality_add_nginx} == "on" ]]; then
        if [[ ! -f "/etc/fail2ban/jail.d/nginx-no-host.local" ]]; then
            mf_create_nginx_no_host_filter
            cat > /etc/fail2ban/jail.d/nginx-no-host.local << EOF
[nginx-no-host]
enabled  = true
filter   = nginx-no-host
logpath  = ${nginx_dir}/logs/sni_error.log
bantime  = 604800
maxretry = 5
findtime = 120
EOF
            
            log_echo "${GreenBG} $(gettext "是否启用") nginx-no-host $(gettext "规则")? [${Red}Y${Font}${GreenBG}/N] ${Font}"
            read -r enable_nginx_no_host
            case $enable_nginx_no_host in
                [nN][oO] | [nN])
                    sed -i "s/enabled\s*=\s*true/enabled = false/" /etc/fail2ban/jail.d/nginx-no-host.local
                    log_echo "${OK} ${GreenBG} $(gettext "已禁用") nginx-no-host $(gettext "规则") ${Font}"
                    ;;
                *)
                    log_echo "${OK} ${GreenBG} $(gettext "已启用") nginx-no-host $(gettext "规则") ${Font}"
                    ;;
            esac
        fi

        # 启用 nginx-tls-error 规则
        if [[ ! -f "/etc/fail2ban/jail.d/nginx-tls-error.local" ]]; then
            mf_create_nginx_tls_error_filter
            cat > /etc/fail2ban/jail.d/nginx-tls-error.local << EOF
[nginx-tls-error]
enabled  = true
filter   = nginx-tls-error
logpath  = ${nginx_dir}/logs/tls_error.log
bantime  = 43200
maxretry = 8
findtime = 300
EOF
            
            log_echo "${GreenBG} $(gettext "是否启用") nginx-tls-error $(gettext "规则")? [${Red}Y${Font}${GreenBG}/N] ${Font}"
            read -r enable_nginx_tls_error
            case $enable_nginx_tls_error in
                [nN][oO] | [nN])
                    sed -i "s/enabled\s*=\s*true/enabled = false/" /etc/fail2ban/jail.d/nginx-tls-error.local
                    log_echo "${OK} ${GreenBG} $(gettext "已禁用") nginx-tls-error $(gettext "规则") ${Font}"
                    ;;
                *)
                    log_echo "${OK} ${GreenBG} $(gettext "已启用") nginx-tls-error $(gettext "规则") ${Font}"
                    ;;
            esac
        fi
    fi
    systemctl daemon-reload
    systemctl restart fail2ban
    judge "Fail2ban $(gettext "配置")"
}

mf_create_nginx_no_host_filter() {
    local filter_file="/etc/fail2ban/filter.d/nginx-no-host.conf"
    if [[ ! -f "$filter_file" ]]; then
        cat >"$filter_file" <<'EOF'
[Definition]
datepattern = ^%%d/%%b/%%Y:%%H:%%M:%%S %%z$
failregex = ^<HOST> \[.*\] ".*".*\d+$
ignoreregex =
EOF
    fi
}

mf_create_nginx_tls_error_filter() {
    local filter_file="/etc/fail2ban/filter.d/nginx-tls-error.conf"
    if [[ ! -f "$filter_file" ]]; then
        cat >"$filter_file" <<'EOF'
[Definition]
datepattern = ^%%d/%%b/%%Y:%%H:%%M:%%S %%z$
failregex = ^<HOST> \[.*\] ".*".*\d+$
ignoreregex =
EOF
    fi
}

# 检查模块是否启用
mf_is_module_enabled() {
    local module_file="$1"
    local default_status="${2:-true}"
    
    if [[ ! -f "$module_file" ]]; then
        return 1
    fi
    
    local enabled_status=$(grep -oP 'enabled\s*=\s*\K\w+' "$module_file" 2>/dev/null || echo "$default_status")
    [[ "$enabled_status" == "true" ]]
}

mf_manage_fail2ban() {
    if ! command -v fail2ban-client &> /dev/null; then
        log_echo "${Error} ${RedBG} Fail2ban $(gettext "未安装, 请先安装") Fail2ban ${Font}"
        return
    fi

    while true; do
        echo
        log_echo "${Green} $(gettext "请选择") Fail2ban $(gettext "操作"): ${Font}"
        echo "1. $(gettext "管理模块")"
        echo "2. $(gettext "添加自定义规则")"
        echo "3. $(gettext "服务管理")"
        echo "4. $(gettext "返回")"
        local mf_action
        read_optimize "$(gettext "请输入"):" mf_action 1
        case $mf_action in
        1)
            mf_manage_modules
            ;;
        2)
            mf_add_custom_rule
            mf_main_menu
            ;;
        3)
            # 服务管理子菜单
            while true; do
                echo
                log_echo "${Green} $(gettext "服务管理"): ${Font}"
                echo "1. $(gettext "启动") Fail2ban"
                echo "2. $(gettext "停止") Fail2ban"
                echo "3. $(gettext "重启") Fail2ban"
                echo "4. $(gettext "返回")"
                local service_action
                read_optimize "$(gettext "请输入"):" service_action 1
                case $service_action in
                1)
                    mf_start_enable_fail2ban
                    ;;
                2)
                    mf_stop_disable_fail2ban
                    ;;
                3)
                    mf_restart_fail2ban
                    ;;
                4)
                    break
                    ;;
                *)
                    echo
                    log_echo "${Error} ${RedBG} $(gettext "无效的选择, 请重试") ${Font}"
                    ;;
                esac
            done
            ;;
        4) mf_main_menu ;;
        *)
            echo
            log_echo "${Error} ${RedBG} $(gettext "无效的选择, 请重试") ${Font}"
            ;;
        esac
    done
}

mf_add_custom_rule() {
    local jail_name
    local filter_name
    local log_path
    local max_retry
    local ban_time

    read_optimize "$(gettext "请输入新的") Jail $(gettext "名称"):" "jail_name" NULL
    read_optimize "$(gettext "请输入") Filter $(gettext "名称"):" "filter_name" NULL
    read_optimize "$(gettext "请输入日志路径"):" "log_path" NULL
    read_optimize "$(gettext "请输入最大重试次数") ($(gettext "默认") 5):" "max_retry" 5 1 99 "$(gettext "最大重试次数必须在 1 到 99 之间")"
    read_optimize "$(gettext "请输入封禁时间") ($(gettext "秒"), $(gettext "默认") 604800):" "ban_time" 604800 1 8640000 "$(gettext "封禁时间必须在 1 到 8640000 秒之间")"

    cat > "/etc/fail2ban/jail.d/${jail_name}.local" << EOF
[$jail_name]
enabled  = true
filter   = $filter_name
logpath  = $log_path
maxretry = $max_retry
bantime  = $ban_time
EOF
    log_echo "${OK} ${GreenBG} $(gettext "自定义规则添加成功") ${Font}"

    systemctl daemon-reload
    systemctl restart fail2ban
    judge "Fail2ban $(gettext "重启以应用新规则")"
}

mf_manage_modules() {
    echo
    log_echo "${Green} $(gettext "管理 Fail2ban 模块") ${Font}"
    
    # 列出所有模块化配置文件
    local module_files=()
    local module_names=()
    local index=1
    
    # 查找所有 .local 文件
    for file in /etc/fail2ban/jail.d/*.local; do
        if [[ -f "$file" ]]; then
            module_files[$index]="$file"
            module_names[$index]=$(basename "$file" .local)
            index=$((index + 1))
        fi
    done
    
    if [[ ${#module_files[@]} -eq 0 ]]; then
        log_echo "${Warning} ${YellowBG} $(gettext "未找到任何模块化配置文件") ${Font}"
        return
    fi
    
    # 计算列宽
    local max_name_length=15

    local compare_strings=()
    compare_strings+=("$(gettext "模块名称")")
    
    for ((i=1; i<${#module_files[@]}+1; i++)); do
        compare_strings+=("${module_names[$i]}")
    done
    
    compare_strings+=("$(gettext "返回")")
    
    for str in "${compare_strings[@]}"; do
        local length=${#str}
        if (( length > max_name_length )); then
            max_name_length=$length
        fi
    done
    
    # 计算总宽度
    local total_width=$((max_name_length + 20))
    
    # 打印表头
    printf "%s\n" "$(printf '%*s' "$total_width" | tr ' ' '-')"
    printf "| %-4s | %-${max_name_length}s | %-10s |\n" "$(gettext "序号")" "$(gettext "模块名称")" "$(gettext "状态")"
    printf "%s\n" "$(printf '%*s' "$total_width" | tr ' ' '-')"
    
    for ((i=1; i<${#module_files[@]}+1; i++)); do
        local module_file=${module_files[$i]}
        local module_name=${module_names[$i]}
        
        if mf_is_module_enabled "$module_file"; then
            local status_text="$(gettext "已启用")"
        else
            local status_text="$(gettext "已禁用")"
        fi
        
        printf "| %4d | %-${max_name_length}s | %-10s |\n" $i "$module_name" "$status_text"
    done
    
    # 打印表尾
    printf "%s\n" "$(printf '%*s' "$total_width" | tr ' ' '-')"
    printf "| %4d | %-${max_name_length}s | %-10s |\n" 0 "$(gettext "返回")" ""
    printf "%s\n" "$(printf '%*s' "$total_width" | tr ' ' '-')"

    
    # 让用户选择要管理的模块
    local module_choice
    read_optimize "$(gettext "请选择要管理的模块"): " "module_choice" 0 0 ${#module_files[@]} "$(gettext "无效的选择, 请重试")"
    
    if [[ $module_choice -eq 0 ]]; then
        return
    fi
    
    local selected_file=${module_files[$module_choice]}
    local selected_name=${module_names[$module_choice]}
    
    # 获取当前状态
    local current_status=$(grep -oP 'enabled\s*=\s*\K\w+' "$selected_file" 2>/dev/null || echo "true")
    local new_status=$([[ "$current_status" == "true" ]] && echo "false" || echo "true")
    local status_text=$([[ "$new_status" == "true" ]] && echo "$(gettext "启用")" || echo "$(gettext "禁用")")
    
    # 确认操作
    log_echo "${GreenBG} $(gettext "是否") $status_text $selected_name $(gettext "模块") [${Red}Y${Font}${GreenBG}/N]? ${Font}"
    read -r confirm
    
    if [[ ! $confirm =~ ^[nN]([oO])?$ ]]; then
        sed -i "s/enabled\s*=\s*\w*/enabled = $new_status/" "$selected_file"

        mf_restart_fail2ban
        
        log_echo "${OK} ${GreenBG} $selected_name $(gettext "模块") $status_text ${Font}"
    else
        log_echo "${Green} $(gettext "操作已取消") ${Font}"
    fi
    
    mf_manage_modules
}

mf_start_enable_fail2ban() {
    systemctl daemon-reload
    systemctl start fail2ban
    systemctl enable fail2ban
    judge "Fail2ban $(gettext "启动")"
    # timeout "$(gettext "清空屏幕")!"
    # clear
}

mf_uninstall_fail2ban() {
    systemctl stop fail2ban
    systemctl disable fail2ban
    ${INS} -y remove fail2ban
    [[ -f "/etc/fail2ban/jail.local" ]] && rm -rf /etc/fail2ban/jail.local
    rm -rf /etc/fail2ban/jail.d/*.local
    if [[ -f "/etc/fail2ban/filter.d/nginx-no-host.conf" ]]; then
        rm -rf /etc/fail2ban/filter.d/nginx-no-host.conf
    fi
    if [[ -f "/etc/fail2ban/filter.d/nginx-tls-error.conf" ]]; then
        rm -rf /etc/fail2ban/filter.d/nginx-tls-error.conf
    fi
    judge "Fail2ban $(gettext "卸载")"
    timeout "$(gettext "清空屏幕")!"
    clear
    source "${idleleo}"
}

mf_stop_disable_fail2ban() {
    systemctl stop fail2ban
    systemctl disable fail2ban
    log_echo "${OK} ${GreenBG} Fail2ban $(gettext "停止成功") ${Font}"
    # timeout "$(gettext "清空屏幕")!"
    # clear
}

mf_restart_fail2ban() {
    systemctl daemon-reload
    systemctl restart fail2ban
    judge "Fail2ban $(gettext "重启")"
    # timeout "$(gettext "清空屏幕")!"
    # clear
}

mf_display_fail2ban_status() {
    echo
    if ! command -v fail2ban-client &> /dev/null; then
        log_echo "${Error} ${RedBG} Fail2ban $(gettext "未安装, 请先安装") Fail2ban ${Font}"
        return
    fi

    log_echo "${GreenBG} Fail2ban $(gettext "总体状态"): ${Font}"
    fail2ban-client status

    echo
    log_echo "${Green} $(gettext "默认启用的 Jail 状态"): ${Font}"
    echo "----------------------------------------"
    
    if mf_is_module_enabled "/etc/fail2ban/jail.d/sshd.local"; then
        log_echo "${Green} SSH $(gettext "封锁情况"): ${Font}"
        fail2ban-client status sshd 2>/dev/null || log_echo "${Warning} ${YellowBG} SSH Jail $(gettext "未启用或配置异常") ${Font}"
    fi

    if [[ ${tls_mode} == "TLS" || ${reality_add_nginx} == "on" ]]; then
        if mf_is_module_enabled "/etc/fail2ban/jail.d/nginx-badbots.local"; then
            log_echo "${Green} Fail2ban Nginx $(gettext "封锁情况"): ${Font}"
            fail2ban-client status nginx-badbots 2>/dev/null || log_echo "${Warning} ${YellowBG} nginx-badbots $(gettext "未启用或配置异常") ${Font}"
        fi
        if mf_is_module_enabled "/etc/fail2ban/jail.d/nginx-botsearch.local"; then
            fail2ban-client status nginx-botsearch 2>/dev/null || log_echo "${Warning} ${YellowBG} nginx-botsearch $(gettext "未启用或配置异常") ${Font}"
        fi
        if [[ ${reality_add_nginx} == "on" ]]; then
            if mf_is_module_enabled "/etc/fail2ban/jail.d/nginx-no-host.local"; then
                fail2ban-client status nginx-no-host 2>/dev/null || log_echo "${Warning} ${YellowBG} nginx-no-host $(gettext "未启用或配置异常") ${Font}"
            fi
            if mf_is_module_enabled "/etc/fail2ban/jail.d/nginx-tls-error.local"; then
                fail2ban-client status nginx-tls-error 2>/dev/null || log_echo "${Warning} ${YellowBG} nginx-tls-error $(gettext "未启用或配置异常") ${Font}"
            fi
        fi
    fi
    echo
    mf_main_menu
}

mf_check_for_updates() {
    local latest_version
    local update_choice

    # 直接使用 curl 下载远程版本信息
    latest_version=$(curl -s "$mf_remote_url" | grep 'mf_SCRIPT_VERSION=' | head -n 1 | sed 's/mf_SCRIPT_VERSION="//; s/"//')
    if [ -n "$latest_version" ] && [ "$latest_version" != "$mf_SCRIPT_VERSION" ]; then
        log_echo "${Warning} ${YellowBG} $(gettext "新版本可用"): $latest_version $(gettext "当前版本"): $mf_SCRIPT_VERSION ${Font}"
        log_echo "${Warning} ${YellowBG} $(gettext "请访问") https://github.com/hello-yunshu/Xray_bash_onekey $(gettext "查看更新说明") ${Font}"

        log_echo "${GreenBG} $(gettext "是否下载并安装新版本") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        read -r update_choice
        case $update_choice in
            [yY][eE][sS] | [yY])
                log_echo "${Info} ${Green} $(gettext "正在下载新版本")... ${Font}"
                curl -sL "$mf_remote_url" -o "${idleleo_dir}/fail2ban_manager.sh"

                if [ $? -eq 0 ]; then
                    chmod +x "${idleleo_dir}/fail2ban_manager.sh"
                    log_echo "${OK} ${GreenBG} $(gettext "下载完成, 请重新运行脚本") ${Font}"
                    bash "${idleleo}"
                else
                    echo
                    log_echo "${Error} ${RedBG} $(gettext "下载失败, 请手动下载并安装新版本") ${Font}"
                fi
                ;;
            *)
                log_echo "${Green} $(gettext "跳过更新") ${Font}"
                ;;
        esac
    else
        log_echo "${OK} ${Green} $(gettext "当前已经是最新版本"): $mf_SCRIPT_VERSION ${Font}"
    fi
}

# 检查更新
mf_check_for_updates

mf_main_menu


================================================
FILE: fake-systemctl
================================================
#!/bin/bash

XRAY_PID_FILE="/var/run/xray.pid"
NGINX_PID_FILE="/usr/local/nginx/logs/nginx.pid"
XRAY_BIN="/usr/local/bin/xray"
NGINX_BIN="/usr/local/nginx/sbin/nginx"
XRAY_CONF="/etc/idleleo/conf/xray/config.json"
NGINX_CONF="/usr/local/nginx/conf/nginx.conf"

_xray_is_running() {
    if [[ -f "$XRAY_PID_FILE" ]]; then
        local pid=$(cat "$XRAY_PID_FILE" 2>/dev/null)
        [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null && return 0
    fi
    pgrep -f "$XRAY_BIN" >/dev/null 2>&1
}

_nginx_is_running() {
    if [[ -f "$NGINX_PID_FILE" ]]; then
        local pid=$(cat "$NGINX_PID_FILE" 2>/dev/null)
        [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null && return 0
    fi
    pgrep -f "$NGINX_BIN" >/dev/null 2>&1
}

_start_xray() {
    _xray_is_running && return 0
    if [[ -x "$XRAY_BIN" ]] && [[ -f "$XRAY_CONF" ]]; then
        nohup "$XRAY_BIN" run -config "$XRAY_CONF" \
            >/var/log/xray/access.log 2>/var/log/xray/error.log &
        echo $! > "$XRAY_PID_FILE"
        sleep 1
        _xray_is_running && return 0
        rm -f "$XRAY_PID_FILE"
    fi
    return 1
}

_stop_xray() {
    if [[ -f "$XRAY_PID_FILE" ]]; then
        kill "$(cat "$XRAY_PID_FILE" 2>/dev/null)" 2>/dev/null
        sleep 1
        kill -9 "$(cat "$XRAY_PID_FILE" 2>/dev/null)" 2>/dev/null
        rm -f "$XRAY_PID_FILE"
    fi
    pkill -f "$XRAY_BIN" 2>/dev/null
}

_start_nginx() {
    _nginx_is_running && return 0
    if [[ -x "$NGINX_BIN" ]] && [[ -f "$NGINX_CONF" ]]; then
        "$NGINX_BIN" 2>/dev/null
        sleep 1
        _nginx_is_running && return 0
    fi
    return 1
}

_stop_nginx() {
    [[ -x "$NGINX_BIN" ]] && "$NGINX_BIN" -s stop 2>/dev/null
    sleep 1
    pkill -f "$NGINX_BIN" 2>/dev/null
    rm -f "$NGINX_PID_FILE"
}

_reload_nginx() {
    _nginx_is_running && "$NGINX_BIN" -s reload 2>/dev/null
}

_iptables_restore() {
    if [[ -f /etc/iptables/rules.v4 ]]; then
        iptables-restore < /etc/iptables/rules.v4 2>/dev/null
    fi
    if [[ -f /etc/iptables/rules.v6 ]]; then
        ip6tables-restore < /etc/iptables/rules.v6 2>/dev/null
    fi
}

case "$1" in
    start)
        case "$2" in
            xray)      _start_xray ;;
            nginx)     _start_nginx ;;
            crond|cron) cron ;;
            iptables)  _iptables_restore ;;
            fail2ban)  fail2ban-client start 2>/dev/null ;;
            *)         ;;
        esac
        ;;
    stop)
        case "$2" in
            xray)      _stop_xray ;;
            nginx)     _stop_nginx ;;
            iptables)  iptables -F 2>/dev/null; ip6tables -F 2>/dev/null ;;
            fail2ban)  fail2ban-client stop 2>/dev/null ;;
            *)         ;;
        esac
        ;;
    restart)
        case "$2" in
            xray)      _stop_xray; sleep 1; _start_xray ;;
            nginx)     _stop_nginx; sleep 1; _start_nginx ;;
            iptables)  iptables -F 2>/dev/null; ip6tables -F 2>/dev/null; _iptables_restore ;;
            fail2ban)  fail2ban-client restart 2>/dev/null ;;
            *)         ;;
        esac
        ;;
    reload)
        case "$2" in
            nginx)  _reload_nginx ;;
            *)      ;;
        esac
        ;;
    enable|disable|daemon-reload)
        exit 0
        ;;
    is-active)
        shift
        if [[ "$1" == "--quiet" ]] || [[ "$1" == "-q" ]]; then shift; fi
        case "$1" in
            xray)      _xray_is_running && echo "active" && exit 0 || echo "inactive" && exit 3 ;;
            nginx)     _nginx_is_running && echo "active" && exit 0 || echo "inactive" && exit 3 ;;
            fail2ban)  pgrep -f "fail2ban-server" >/dev/null && echo "active" && exit 0 || echo "inactive" && exit 3 ;;
            *)         echo "unknown"; exit 3 ;;
        esac
        ;;
    -q)
        [[ "$2" == "is-active" ]] || exit 0
        case "$3" in
            xray)      _xray_is_running && exit 0 || exit 3 ;;
            nginx)     _nginx_is_running && exit 0 || exit 3 ;;
            fail2ban)  pgrep -f "fail2ban-server" >/dev/null && exit 0 || exit 3 ;;
            *)         exit 3 ;;
        esac
        ;;
    is-enabled)
        exit 0
        ;;
    status)
        case "$2" in
            xray)  _xray_is_running  && echo "active (running)" || echo "inactive (dead)" ;;
            nginx) _nginx_is_running && echo "active (running)" || echo "inactive (dead)" ;;
            *)     echo "unknown" ;;
        esac
        ;;
    *)
        echo "Fake systemctl for Docker" >&2
        exit 0
        ;;
esac


================================================
FILE: file_manager.sh
================================================
#!/bin/bash

# 定义当前版本号
fm_SCRIPT_VERSION="1.2.4"

if [ -z "$1" ]; then
    echo "$(gettext "用法"):" $0 <$(gettext "文件扩展名")> [<$(gettext "目录路径")>]
    exit 1
fi

fm_EXTENSION="$1"
fm_WORKDIR="${2:-$(pwd)}"

if [ ! -d "$fm_WORKDIR" ]; then
    echo
    log_echo "${Error} ${RedBG} $(gettext "目录") $fm_WORKDIR $(gettext "不存在, 请检查路径") ${Font}"
    exit 1
fi

fm_original_dir=$(pwd)

cd "$fm_WORKDIR"

fm_list_files() {
    local max_length
    log_echo "${GreenBG} $(gettext "列出所有") .$fm_EXTENSION $(gettext "文件") ${Font}"

    # 设置 dotglob 选项,使通配符 * 包括以点开头的文件
    shopt -s dotglob nullglob

    # 使用数组存储匹配到的文件
    files=(*.$fm_EXTENSION)

    if [ ${#files[@]} -eq 0 ]; then
        log_echo "${Warning} ${YellowBG} $(gettext "未找到") .$fm_EXTENSION $(gettext "文件") ${Font}"
        return 1
    else
        local max_length=0
        for file in "${files[@]}"; do
            local length=${#file}
            if (( length > max_length )); then
                max_length=$length
            fi
        done

        if (( max_length < 10 )); then
            max_length=10
        fi

        local total_width=$((max_length + 10))
        printf "%-${total_width}s\n" "$(printf '%*s' "$total_width" | tr ' ' '-')"

        local header_text="$(gettext "文件名")"
        local header_length=${#header_text}
        local padding=$(( (total_width - header_length - 4) / 2 ))
        local left_padding=$(( padding - 4 ))  # 加上序号列的宽度
        local right_padding=$(( padding - 4 ))

        printf "| %-4s | %-${left_padding}s%-${header_length}s%-${right_padding}s |\n" "$(gettext "序号")" "" "$header_text" ""

        printf "%-${total_width}s\n" "$(printf '%*s' "$total_width" | tr ' ' '-')"

        local index=1
        for file in "${files[@]}"; do
            printf "| %4d | %-*s |\n" $index $((max_length)) "$file"
            ((index++))
        done

        printf "%-${total_width}s\n" "$(printf '%*s' "$total_width" | tr ' ' '-')"

        return 0
    fi
}

fm_create_servername_file() {
    local url
    fm_list_files
    echo
    log_echo "${Green} $(gettext "请输入网址 (例如 hey.run)")"
    log_echo "${Green} $(gettext "不要包含 http:// 或 https:// 开头") ${Font}"
    read_optimize "$(gettext "请输入"):" url
    if [[ $url =~ ^(http|https):// ]]; then
        echo
        log_echo "${Error} ${RedBG} $(gettext "网址不能包含 http:// 或 https:// 前缀") ${Font}"
        return
    fi
    echo "${url} reality;" > "${url}.serverNames"
    log_echo "${OK} ${GreenBG} $(gettext "文件") ${url}.serverNames $(gettext "已创建") ${Font}"
    fm_restart_nginx_and_check_status
    fm_list_files
}

fm_create_server_file() {
    local default_port="$1"
    local host port weight content firewall_set_fq

    fm_list_files

    read_optimize "$(gettext "请输入主机") (host):" host

    if [[ -n "$default_port" ]]; then
        read_optimize "$(gettext "请输入端口") (port $(gettext "默认值"): ${default_port}):" port "${default_port}" 1 65535
    else
        read_optimize "$(gettext "请输入端口") (port):" port "" 1 65535
    fi

    read_optimize "$(gettext "请输入权重") (0~100 $(gettext "默认值") 50):" weight "50" 0 100

    content="server ${host}:${port} weight=${weight} max_fails=2 fail_timeout=10;"
    echo "$content" > "${host}.${fm_EXTENSION}"
    log_echo "${OK} ${GreenBG} $(gettext "文件") ${host}.${fm_EXTENSION} $(gettext "已创建") ${Font}"

    echo
    log_echo "${GreenBG} $(gettext "是否需要设置防火墙") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
    read -r firewall_set_fq
    case $firewall_set_fq in
    [yY][eE][sS] | [yY])

        if [[ "${ID}" == "centos" ]]; then
            pkg_install "iptables-services"
        else
            pkg_install "iptables-persistent"
        fi
        iptables -I INPUT -p tcp --dport ${port} -j ACCEPT
        iptables -I INPUT -p udp --dport ${port} -j ACCEPT
        iptables -I OUTPUT -p tcp --sport ${port} -j ACCEPT
        iptables -I OUTPUT -p udp --sport ${port} -j ACCEPT
        log_echo "${OK} ${GreenBG} $(gettext "防火墙") $(gettext "追加完成") ${Font}"
        if [[ "${ID}" == "centos" && ${VERSION_ID} -ge 7 ]]; then
            service iptables save
            service iptables restart
            log_echo "${OK} ${GreenBG} $(gettext "防火墙") $(gettext "重启完成") ${Font}"
        else
            netfilter-persistent save
            systemctl restart iptables
            log_echo "${OK} ${GreenBG} $(gettext "防火墙") $(gettext "重启完成") ${Font}"
        fi
        ;;
    *)
        log_echo "${OK} ${GreenBG} $(gettext "跳过防火墙设置") ${Font}"
        ;;
    esac
    fm_restart_nginx_and_check_status
    fm_list_files
}

fm_edit_file() {
    fm_list_files
    local num_files=${#files[@]}
    local choice
    read_optimize "$(gettext "请输入要编辑的文件编号") (1-$num_files): " choice "" 1 "$num_files"

    local filename="${files[$((choice - 1))]}"

    if ! command -v vim &> /dev/null; then
        log_echo "${Warning} ${YellowBG} vim $(gettext "未安装, 正在尝试安装") ${Font}"
        pkg_install vim
    fi
    vim "$filename"
    log_echo "${OK} ${GreenBG} $(gettext "文件") $filename $(gettext "已编辑") ${Font}"
    fm_restart_nginx_and_check_status
}

fm_delete_file() {
    if ! fm_list_files; then
        return
    fi

    local num_files=${#files[@]}
    local choice
    read_optimize "$(gettext "请输入要删除的文件编号") (1-$num_files): " choice "" 1 "$num_files"

    local filename="${files[$((choice - 1))]}"

    rm "$filename"
    log_echo "${OK} ${GreenBG} $(gettext "文件") $filename $(gettext "已删除") ${Font}"
    fm_restart_nginx_and_check_status
    fm_list_files
}

fm_create_file() {
    case $fm_EXTENSION in
        serverNames)
            fm_create_servername_file
            ;;
        wsServers|grpcServers)
             fm_create_server_file ""
             ;;
        realityServers)
             fm_create_server_file ""
             ;;
        *)
            echo
            log_echo "${Error} ${RedBG} $(gettext "不支持的文件扩展名") $fm_EXTENSION ${Font}"
            ;;
    esac
}

fm_main_menu() {
    fm_list_files
    while true; do
        echo
        log_echo "${GreenBG} $(gettext "主菜单") ${Font}"
        log_echo "1 ${Green}$(gettext "列出所有") $fm_EXTENSION $(gettext "文件")${Font}"
        log_echo "2 ${Green}$(gettext "创建一个新的") $fm_EXTENSION $(gettext "文件")${Font}"
        log_echo "3 ${Green}$(gettext "编辑一个已存在的") $fm_EXTENSION $(gettext "文件")${Font}"
        log_echo "4 ${Green}$(gettext "删除一个已存在的") $fm_EXTENSION $(gettext "文件")${Font}"
        log_echo "5 ${Green}$(gettext "退出")${Font}"
        local choice
        read_optimize "$(gettext "请选择一个选项"):" choice "" 1 5

        case $choice in
            1) fm_list_files ;;
            2) fm_create_file ;;
            3) fm_edit_file ;;
            4) fm_delete_file ;;
            5) source "$idleleo" ;;
            *)
                echo
                log_echo "${Error} ${RedBG} $(gettext "无效选项, 请重试") ${Font}"
                ;;
        esac
    done
}

fm_check_for_updates() {
    local latest_version
    local update_choice

    latest_version=$(curl -s "$fm_remote_url" | grep 'fm_SCRIPT_VERSION=' | head -n 1 | sed 's/fm_SCRIPT_VERSION="//; s/"//')
    if [ -n "$latest_version" ] && [ "$latest_version" != "$fm_SCRIPT_VERSION" ]; then
        log_echo "${Warning} ${YellowBG} $(gettext "新版本可用"): $latest_version $(gettext "当前版本"): $fm_SCRIPT_VERSION ${Font}"
        log_echo "${Warning} ${YellowBG} $(gettext "请访问") https://github.com/hello-yunshu/Xray_bash_onekey $(gettext "查看更新说明") ${Font}"

        log_echo "${GreenBG} $(gettext "是否下载并安装新版本") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        read -r update_choice
        case $update_choice in
            [yY][eE][sS] | [yY])
                log_echo "${Info} ${Green} $(gettext "正在下载新版本")... ${Font}"
                curl -sL "$fm_remote_url" -o "${idleleo_dir}/file_manager.sh"

                if [ $? -eq 0 ]; then
                    chmod +x "${idleleo_dir}/file_manager.sh"
                    log_echo "${OK} ${GreenBG} $(gettext "下载完成, 请重新运行脚本") ${Font}"
                    bash "${idleleo}"
                else
                    echo
                    log_echo "${Error} ${RedBG} $(gettext "下载失败, 请手动下载并安装新版本") ${Font}"
                fi
                ;;
            *)
                log_echo "${OK} ${Green} $(gettext "跳过更新") ${Font}"
                ;;
        esac
    else
        log_echo "${OK} ${Green} $(gettext "当前已经是最新版本"): $fm_SCRIPT_VERSION ${Font}"
    fi
}

fm_restart_nginx_and_check_status() {
    if [[ -f ${nginx_systemd_file} ]]; then
        systemctl restart nginx
        if systemctl is-active --quiet nginx; then
            echo
            log_echo "${OK} ${GreenBG} Nginx $(gettext "重启成功") ${Font}"
        else
            echo
            log_echo "${Error} ${RedBG} Nginx $(gettext "重启失败"), $(gettext "请检查配置文件是否有误") ${Font}"
            fm_edit_file
        fi
    fi
}

fm_check_for_updates
fm_main_menu

cd "$fm_original_dir" || exit 1


================================================
FILE: install.sh
================================================
#!/bin/bash

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#stty erase ^?

cd "$(
    cd "$(dirname "$0")" || exit
    pwd
)" || exit

#=================================================================
#	System Request: Debian 12+ / Ubuntu 24.04+ / Centos Stream 8+
#	Author:	yunyunshu
#	Dscription: Xray Onekey Management
#	Version: 2.8
#	Official document: hey.run
#=================================================================

#fonts color
Green="\033[32m"
Red="\033[31m"
GreenW="\033[1;32m"
RedW="\033[1;31m"
#Yellow="\033[33m"
GreenBG="\033[42;30m"
RedBG="\033[41;30m"
YellowBG="\033[43;30m"
Font="\033[0m"

#notification information
Info="${Green}[$(gettext "信息")]${Font}"
OK="${Green}[OK]${Font}"
Error="${RedW}[$(gettext "错误")]${Font}"
Warning="${RedW}[$(gettext "警告")]${Font}"

shell_version="2.8.12"
shell_mode="$(gettext "未安装")"
tls_mode="None"
ws_grpc_mode="None"
local_bin="/usr/local"
idleleo_dir="/etc/idleleo"
idleleo="${idleleo_dir}/install.sh"
idleleo_conf_dir="${idleleo_dir}/conf"
log_dir="${idleleo_dir}/logs"
xray_bin_dir="${local_bin}/bin"
xray_conf_dir="${idleleo_conf_dir}/xray"
nginx_conf_dir="${idleleo_conf_dir}/nginx"
xray_conf="${xray_conf_dir}/config.json"
xray_status_conf="${xray_conf_dir}/status_config.json"
xray_default_conf="${local_bin}/etc/xray/config.json"
nginx_conf="${nginx_conf_dir}/00-xray.conf"
nginx_ssl_conf="${nginx_conf_dir}/01-xray-80.conf"
nginx_upstream_conf="${nginx_conf_dir}/02-xray-server.conf"
idleleo_commend_file="/usr/bin/idleleo"
ssl_chainpath="${idleleo_dir}/cert"
nginx_dir="${local_bin}/nginx"
xray_info_file="${idleleo_dir}/info/xray_info.inf"
xray_qr_config_file="${idleleo_dir}/info/vless_qr.json"
nginx_systemd_file="/etc/systemd/system/nginx.service"
xray_systemd_file="/etc/systemd/system/xray.service"
xray_access_log="/var/log/xray/access.log"
xray_error_log="/var/log/xray/error.log"
amce_sh_file="/root/.acme.sh/acme.sh"
auto_update_file="${idleleo_dir}/auto_update.sh"
ssl_update_file="${idleleo_dir}/ssl_update.sh"
myemali="my@example.com"
shell_version_tmp="${idleleo_dir}/tmp/shell_version.tmp"
get_versions_all=$(curl -s https://cdn.jsdelivr.net/gh/hello-yunshu/Xray_bash_onekey_api@main/xray_shell_versions.json)
read_config_status=1
reality_add_more="off"
reality_add_nginx="off"
reality_add_balance="off"
old_config_status="off"
old_tls_mode="NULL"
random_num=$((RANDOM % 12 + 4))
[[ -f "${xray_qr_config_file}" ]] && info_extraction_all=$(jq -rc . ${xray_qr_config_file})

[[ ! -d ${log_dir} ]] && mkdir -p ${log_dir}
[[ ! -f "${log_dir}/install.log" ]] && touch ${log_dir}/install.log
LOG_FILE="${log_dir}/install.log"
LOG_MAX_SIZE=$((3 * 1024 * 1024))  # 3 MB
MAX_ARCHIVES=5

log() {
    if [ $(stat -c%s "$LOG_FILE" 2>/dev/null) -gt $LOG_MAX_SIZE ]; then
        log_rotate
    fi

    local message=$(echo -e "$1" | sed 's/\x1B\[\([0-9]\(;[0-9]\)*\)*m//g' | tr -d '\n')
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" | tee -a $LOG_FILE >/dev/null
}

log_rotate() {
    local timestamp=$(date +%Y%m%d%H%M%S)
    local archived_log="${LOG_FILE}.${timestamp}.gz"

    if ! gzip -c "$LOG_FILE" > "$archived_log"; then
        log_echo "${Error} ${RedBG} $(gettext "日志文件归档失败") ${Font}"
        return 1
    fi

    if ! :> "$LOG_FILE"; then
        log_echo "${Error} ${RedBG} $(gettext "日志文件清空失败") ${Font}"
        return 1
    fi

    log "$(gettext "日志文件已轮转并归档为") $archived_log"

    rotate_archives
}

rotate_archives() {
    local archives=($(ls ${LOG_FILE}.*.gz 2>/dev/null))
    while [ ${#archives[@]} -gt $MAX_ARCHIVES ]; do
        oldest_archive=${archives[0]}
        rm "$oldest_archive"
        archives=($(ls ${LOG_FILE}.*.gz 2>/dev/null))
    done
}

log_echo() {
    local message=$(printf "%b" "$@")
    echo -e "$message"
    log "$message"
}

source '/etc/os-release'

VERSION=$(echo "${VERSION}" | awk -F "[()]" '{print $2}')

check_system() {
    if [[ "${ID}" == "centos" && ${VERSION_ID} -ge 7 ]]; then
        log_echo "${OK} ${GreenBG} $(gettext "当前系统为") Centos ${VERSION_ID} ${VERSION} ${Font}"
        INS="yum"
        [[ ! -f "${xray_qr_config_file}" ]] && $INS update || true
    elif [[ "${ID}" == "debian" && ${VERSION_ID} -ge 8 ]]; then
        log_echo "${OK} ${GreenBG} $(gettext "当前系统为") Debian ${VERSION_ID} ${VERSION} ${Font}"
        INS="apt"
        [[ ! -f "${xray_qr_config_file}" ]] && $INS update || true
    elif [[ "${ID}" == "ubuntu" && $(echo "${VERSION_ID}" | cut -d '.' -f1) -ge 16 ]]; then
        log_echo "${OK} ${GreenBG} $(gettext "当前系统为") Ubuntu ${VERSION_ID} ${UBUNTU_CODENAME} ${Font}"
        INS="apt"
        if [[ ! -f "${xray_qr_config_file}" ]]; then
            rm /var/lib/dpkg/lock || true
            dpkg --configure -a || true
            rm /var/lib/apt/lists/lock || true
            rm /var/cache/apt/archives/lock || true
            $INS update || true
        fi
    else
        log_echo "${Error} ${RedBG} $(gettext "当前系统为") ${ID} ${VERSION_ID} $(gettext "不在支持的系统列表内, 安装中断")! ${Font}"
        exit 1
    fi
}

is_root() {
    if [[ 0 == $UID ]]; then
        log_echo "${OK} ${GreenBG} $(gettext "当前用户是 root 用户, 开始安装") ${Font}"
    else
        log_echo "${Error} ${RedBG} $(gettext "当前用户不是 root 用户, 请切换到 root 用户后重新运行脚本")! ${Font}"
        exit 1
    fi
}

check_and_create_user_group() {
    if ! getent group nogroup > /dev/null; then
        groupadd nogroup
    fi

    if ! id nobody > /dev/null 2>&1; then
        useradd -r -g nogroup -s /sbin/nologin -c "Unprivileged User" nobody
    fi
}

check_language_update() {
    local lang_code="$1"
    local local_file="${idleleo_dir}/languages/${lang_code}/LC_MESSAGES/xray_install.mo"
    local version_file_url="https://raw.githubusercontent.com/hello-yunshu/Xray_bash_onekey/main/languages/${lang_code}/LC_MESSAGES/version"

    [[ ! -f "${local_file}" ]] && return 0

    local remote_version
    remote_version=$(curl -s "${version_file_url}" || echo "")

    if [ -z "$remote_version" ]; then
        log_echo "${Warning} ${YellowBG} $(gettext "无法获取远程语言文件信息") ${Font}"
        return 1
    fi

    local local_version
    local_version=$(cat "${idleleo_dir}/languages/${lang_code}/LC_MESSAGES/version" 2>/dev/null || echo "")

    [ "$remote_version" != "$local_version" ]
}

update_language_file() {
    local lang_code="$1"
    local mo_file="${idleleo_dir}/languages/${lang_code}/LC_MESSAGES/xray_install.mo"
    local version_file="${idleleo_dir}/languages/${lang_code}/LC_MESSAGES/version"
    local github_url="https://raw.githubusercontent.com/hello-yunshu/Xray_bash_onekey/main/languages"

    mkdir -p "${idleleo_dir}/languages/${lang_code}/LC_MESSAGES"

    log_echo "${Info} ${Green} $(gettext "正在更新语言文件")... ${Font}"

    if ! curl -s -o "${mo_file}" "${github_url}/${lang_code}/LC_MESSAGES/xray_install.mo"; then
        log_echo "${Error} ${RedBG} $(gettext "语言文件更新失败") ${Font}"
        return 1
    fi

    if [ ! -s "${mo_file}" ]; then
        log_echo "${Error} ${RedBG} $(gettext "语言文件无效") ${Font}"
        rm -f "${mo_file}"
        return 1
    fi

    if ! curl -s -o "${version_file}" "${github_url}/${lang_code}/LC_MESSAGES/version"; then
        log_echo "${Error} ${RedBG} $(gettext "版本文件更新失败") ${Font}"
        return 1
    fi

    find "${idleleo_dir}/languages" -type d -exec chmod 755 {} \;
    find "${idleleo_dir}/languages" -type f -exec chmod 644 {} \;

    log_echo "${OK} ${Green} $(gettext "语言文件更新完成") ${Font}"
}

init_language() {
    if ! command -v gettext >/dev/null 2>&1; then
        log_echo "${Warning} ${YellowBG} $(gettext "正在安装") gettext... ${Font}"
        pkg_install "gettext"
        if [ $? -ne 0 ]; then
            log_echo "${Error} ${RedBG} gettext $(gettext "安装失败"), $(gettext "将使用默认语言") ${Font}"
            unset LANG
            unset LC_MESSAGES
            return 1
        fi
    fi

    local gettext_paths=(
        "/usr/share/gettext/gettext.sh"
        "/usr/local/share/gettext/gettext.sh"
        "/usr/bin/gettext.sh"
        "/usr/local/bin/gettext.sh"
        "/usr/share/gettext-"*/gettext.sh
    )

    local gettext_sh=""
    for path in "${gettext_paths[@]}"; do
        if [ -f "$path" ]; then
            gettext_sh="$path"
            break
        fi
    done

    if [ -z "$gettext_sh" ]; then
        log_echo "${Error} ${RedBG} $(gettext "未找到") gettext.sh, $(gettext "将使用默认语言") ${Font}"
        unset LANG
        unset LC_MESSAGES
        return 1
    fi

    [ -d "${idleleo_dir}/languages" ] || mkdir "${idleleo_dir}/languages"
    export TEXTDOMAIN="xray_install"
    export TEXTDOMAINDIR="${idleleo_dir}/languages"
    . "$gettext_sh"

    if [ -f "${idleleo_dir}/language.conf" ]; then
        source "${idleleo_dir}/language.conf"

        if [[ "${LANG%.*}" != "zh_CN" ]]; then
            local lang_code
            case "${LANG%.*}" in
                "en_US") lang_code="en" ;;
                "fa_IR") lang_code="fa" ;;
                "ru_RU") lang_code="ru" ;;
                "ko_KR") lang_code="ko" ;;
                "fr_FR") lang_code="fr" ;;
                *)
                    log_echo "${Warning} ${YellowBG} $(gettext "不支持的语言"):${LANG%.*}, $(gettext "将使用默认语言") ${Font}"
                    unset LANG
                    unset LC_MESSAGES
                    return 0
                    ;;
            esac

            local lang_file="${TEXTDOMAINDIR}/${lang_code}/LC_MESSAGES/${TEXTDOMAIN}.mo"
            if [ ! -f "$lang_file" ]; then
                if ! update_language_file "$lang_code"; then
                    log_echo "${Warning} ${YellowBG} $(gettext "语言文件更新失败"), $(gettext "将使用默认语言") ${Font}"
                    unset LANG
                    unset LC_MESSAGES
                    return 0
                fi
            elif check_language_update "$lang_code"; then
                log_echo "${Info} ${Green} $(gettext "发现语言文件更新") ${Font}"
                if update_language_file "$lang_code"; then
                    . "$gettext_sh"
                fi
            fi
        fi
    # else
        # log_echo "${Info} ${Green} $(gettext "未找到") language.conf, $(gettext "将使用默认语言") ${Font}"
        # unset LANG
        # unset LC_MESSAGES
    fi
}

judge() {
    if [[ 0 -eq $? ]]; then
        log_echo "${OK} ${GreenBG} $1 $(gettext "完成") ${Font}"
        sleep 0.5
    else
        log_echo "${Error} ${RedBG} $1 $(gettext "失败") ${Font}"
        exit 1
    fi
}

check_version() {
    echo ${get_versions_all} | jq -rc ".$1"
    [[ 0 -ne $? ]] && log_echo "${Error} ${RedBG} $(gettext "在线版本检测失败, 请稍后再试")! ${Font}" && exit 1
}

pkg_install_judge() {
    if [[ "${ID}" == "centos" ]]; then
        yum list installed | grep -iw "^$1"
    else
        dpkg --get-selections | grep -iw "^$1" | grep -ivw "deinstall"
    fi
}

pkg_install() {
    install_array=(${1//,/ })
    install_status=1
    if [[ ${#install_array[@]} -gt 1 ]]; then
        for install_var in ${install_array[@]}; do
            if [[ -z $(pkg_install_judge "${install_var}") ]]; then
                ${INS} -y install ${install_var}
                install_status=0
            fi
        done
        if [[ ${install_status} == 0 ]]; then
            judge "$(gettext "安装") ${1//,/ }"
        else
            log_echo "${OK} ${GreenBG} $(gettext "已安装") ${1//,/ } ${Font}"
            sleep 0.5
        fi
    else
        if [[ -z $(pkg_install_judge "$1") ]]; then
            ${INS} -y install $1
            judge "$(gettext "安装") $1"
        else
            log_echo "${OK} ${GreenBG} $(gettext "已安装") $1 ${Font}"
            sleep 0.5
        fi
    fi
}

dependency_install() {
    pkg_install "bc,curl,dbus,git,jq,lsof,python3,qrencode"
    if [[ "${ID}" == "centos" ]]; then
        pkg_install "crontabs"
    else
        pkg_install "cron"
    fi
    if [[ ! -f "/var/spool/cron/root" ]] && [[ ! -f "/var/spool/cron/crontabs/root" ]]; then
        if [[ "${ID}" == "centos" ]]; then
            touch /var/spool/cron/root && chmod 600 /var/spool/cron/root
            systemctl start crond && systemctl enable crond >/dev/null 2>&1
            judge "crontab $(gettext "自启动配置")"
        else
            touch /var/spool/cron/crontabs/root && chmod 600 /var/spool/cron/crontabs/root
            systemctl start cron && systemctl enable cron >/dev/null 2>&1
            judge "crontab $(gettext "自启动配置")"
        fi
    fi
    if [[ ${tls_mode} != "None" ]]; then
        if [[ "${ID}" == "centos" ]]; then
            pkg_install "epel-release,iputils,pcre,pcre-devel,zlib-devel,perl-IPC-Cmd"
        else
            pkg_install "iputils-ping,libpcre3,libpcre3-dev,zlib1g-dev"
        fi
        judge "Nginx $(gettext "链接库安装")"
    fi
}

read_optimize() {
    local prompt="$1" var_name="$2" default_value="${3:-NULL}" min_value="${4:-}" max_value="${5:-}" error_msg="${6:-$(gettext "值为空或超出范围, 请重新输入")!}"
    local user_input

    read -rp "$prompt" user_input

    if [[ -z $user_input ]]; then
        if [[ $default_value != "NULL" ]]; then
            user_input=$default_value
        else
            log_echo "${Error} ${RedBG} $(gettext "值为空, 请重新输入")! ${Font}"
            read_optimize "$prompt" "$var_name" "$default_value" "$min_value" "$max_value" "$error_msg"
            return
        fi
    fi

    printf -v "$var_name" "%s" "$user_input"

    if [[ -n $min_value ]] && [[ -n $max_value ]]; then
        if (( user_input < min_value )) || (( user_input > max_value )); then
            log_echo "${Error} ${RedBG} $error_msg ${Font}"
            read_optimize "$prompt" "$var_name" "$default_value" "$min_value" "$max_value" "$error_msg"
            return
        fi
    fi
}

basic_optimization() {
    sed -i '/^\*\ *soft\ *nofile\ *[[:digit:]]*/d' /etc/security/limits.conf
    sed -i '/^\*\ *hard\ *nofile\ *[[:digit:]]*/d' /etc/security/limits.conf
    echo '* soft nofile 65536' >>/etc/security/limits.conf
    echo '* hard nofile 65536' >>/etc/security/limits.conf

    if [[ "${ID}" == "centos" ]]; then
        sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
        setenforce 0
    fi

}

create_directory() {
    if [[ ${tls_mode} != "None" ]]; then
        [[ ! -d "${nginx_conf_dir}" ]] && mkdir -p ${nginx_conf_dir}
    fi
    [[ ! -d "${ssl_chainpath}" ]] && mkdir -p ${ssl_chainpath}
    [[ ! -d "${xray_conf_dir}" ]] && mkdir -p ${xray_conf_dir}
    [[ ! -d "${idleleo_dir}/info" ]] && mkdir -p ${idleleo_dir}/info
}

port_set() {
    if [[ "on" != ${old_config_status} ]]; then
        echo
        log_echo "${GreenBG} $(gettext "确定端口") ${Font}"
        read_optimize "$(gettext "请输入端口") ($(gettext "默认值"):443):" "port" 443 0 65535 "$(gettext "请输入 0-65535 之间的值")!"
        if [[ ${port} -eq 9443 || ${port} -eq 9403 ]] && [[ ${tls_mode} == "Reality" ]]; then
            echo -e "${Error} ${RedBG} $(gettext "端口不允许使用, 请重新输入")! ${Font}"
            read_optimize "$(gettext "请输入端口") ($(gettext "默认值"):443):" "port" 443 0 65535 "$(gettext "请输入 0-65535 之间的值")!"
        fi
    fi
}

ws_grpc_choose() {
    if [[ "on" != ${old_config_status} ]]; then
        echo
        log_echo "${GreenBG} $(gettext "请选择安装协议") ws/gRPC ${Font}"
        echo -e "${Red}1${Font}: ws ($(gettext "默认"))"
        echo "2: gRPC"
        echo "3: ws+gRPC"
        local choose_network
        read_optimize "$(gettext "请输入"): " "choose_network" 1 1 3 "$(gettext "请输入有效的数字")!"
        if [[ $choose_network == 2 ]]; then
            [[ ${shell_mode} == "Nginx+ws+TLS" ]] && shell_mode="Nginx+gRPC+TLS"
            [[ ${shell_mode} == "Reality" ]] && shell_mode="Reality+gRPC"
            [[ ${shell_mode} == "ws ONLY" ]] && shell_mode="gRPC ONLY"
            ws_grpc_mode="onlygRPC"
        elif [[ $choose_network == 3 ]]; then
            [[ ${shell_mode} == "Nginx+ws+TLS" ]] && shell_mode="Nginx+ws+gRPC+TLS"
            [[ ${shell_mode} == "Reality" ]] && shell_mode="Reality+ws+gRPC"
            [[ ${shell_mode} == "ws ONLY" ]] && shell_mode="ws+gRPC ONLY"
            ws_grpc_mode="all"
        else
            [[ ${shell_mode} == "Reality" ]] && shell_mode="Reality+ws"
            ws_grpc_mode="onlyws"
        fi
    fi
}

xray_reality_add_more_choose() {
    if [[ "on" != ${old_config_status} ]]; then
        echo
        log_echo "${GreenBG} $(gettext "是否添加简单 ws/gRPC 协议 用于负载均衡") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        echo -e "${Warning} ${YellowBG} $(gettext "如不清楚具体用途, 请勿选择")! ${Font}"
        read -r reality_add_more_fq
        case $reality_add_more_fq in
        [yY][eE][sS] | [yY])
            reality_add_more="on"
            ws_grpc_choose
            ws_inbound_port_set
            grpc_inbound_port_set
            ws_path_set
            grpc_path_set
            port_exist_check "${xport}"
            port_exist_check "${gport}"
            ;;
        *)
            reality_add_more="off"
            ws_grpc_mode="None"
            ws_inbound_port_set
            grpc_inbound_port_set
            ws_path_set
            grpc_path_set
            log_echo "${OK} ${GreenBG} $(gettext "已跳过添加简单 ws/gRPC 协议") ${Font}"
            ;;
        esac
    fi
}

ws_grpc_qr() {
    artpath="None"
    artxport="None"
    artserviceName="None"
    artgport="None"
    if [[ ${ws_grpc_mode} == "onlyws" ]]; then
        artxport=${xport}
        artpath=${path}
    elif [[ ${ws_grpc_mode} == "onlygRPC" ]]; then
        artgport=${gport}
        artserviceName=${serviceName}
    elif [[ ${ws_grpc_mode} == "all" ]]; then
        artxport=${xport}
        artpath=${path}
        artgport=${gport}
        artserviceName=${serviceName}
    fi
}

ws_inbound_port_set() {
    if [[ "on" != ${old_config_status} ]]; then
        if [[ ${ws_grpc_mode} == "onlyws" || ${ws_grpc_mode} == "all" ]] || [[ ${reality_add_more} == "on" ]]; then
            echo
            log_echo "${GreenBG} $(gettext "是否需要自定义") ws inbound_port [Y/${Red}N${Font}${GreenBG}]? ${Font}"
            read -r inbound_port_modify_fq
            case $inbound_port_modify_fq in
            [yY][eE][sS] | [yY])
                read_optimize "$(gettext "请输入") ws inbound_port ($(gettext "请勿与其他端口相同")!): " "xport" "NULL" 0 65535 "$(gettext "请输入 0-65535 之间的值")!"
                log_echo "${Green} ws inbound_port: ${xport} ${Font}"
                ;;
            *)
                xport=$((RANDOM % 1000 + 10000))
                log_echo "${Green} ws inbound_port: ${xport} ${Font}"
                ;;
            esac
        else
            xport=$((RANDOM % 1000 + 20000))
        fi
    fi
}

grpc_inbound_port_set() {
    if [[ "on" != ${old_config_status} ]]; then
        if [[ ${ws_grpc_mode} == "onlygRPC" || ${ws_grpc_mode} == "all" ]] || [[ ${reality_add_more} == "on" ]]; then
            echo
            log_echo "${GreenBG} $(gettext "是否需要自定义") gRPC inbound_port [Y/${Red}N${Font}${GreenBG}]? ${Font}"
            read -r inbound_port_modify_fq
            case $inbound_port_modify_fq in
            [yY][eE][sS] | [yY])
                read_optimize "$(gettext "请输入") gRPC inbound_port ($(gettext "请勿与其他端口相同")!): " "gport" "NULL" 0 65535 "$(gettext "请输入 0-65535 之间的值")!"
                log_echo "${Green} gRPC inbound_port: ${gport} ${Font}"
                ;;
            *)
                gport=$((RANDOM % 1000 + 10000))
                while [[ ${gport} == ${xport} ]]; do gport=$((RANDOM % 1000 + 10000)); done
                log_echo "${Green} gRPC inbound_port: ${gport} ${Font}"
                ;;
            esac
        else
            gport=$((RANDOM % 1000 + 30000))
        fi
    fi
}

firewall_set() {
    echo
    log_echo "${GreenBG} $(gettext "是否需要设置防火墙") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
    read -r firewall_set_fq
    case $firewall_set_fq in
    [yY][eE][sS] | [yY])
        if [[ "${ID}" == "centos" ]]; then
            pkg_install "iptables-services"
        else
            pkg_install "iptables-persistent"
        fi
        iptables -A INPUT -i lo -j ACCEPT
        iptables -A OUTPUT -o lo -j ACCEPT
        if [[ ${tls_mode} == "TLS" || ${tls_mode} == "XTLS" ]]; then
            iptables -I INPUT -p tcp -m multiport --dport 53,80,${port} -j ACCEPT
            iptables -I INPUT -p udp -m multiport --dport 53,80,${port} -j ACCEPT
            iptables -I OUTPUT -p tcp -m multiport --sport 53,80,${port} -j ACCEPT
            iptables -I OUTPUT -p udp -m multiport --sport 53,80,${port} -j ACCEPT
            iptables -I INPUT -p udp --dport 1024:65535 -j ACCEPT
        fi
        if [[ ${ws_grpc_mode} == "onlyws" ]]; then
            iptables -I INPUT -p tcp -m multiport --dport 53,${xport} -j ACCEPT
            iptables -I INPUT -p udp -m multiport --dport 53,${xport} -j ACCEPT
            iptables -I OUTPUT -p tcp -m multiport --sport 53,${xport} -j ACCEPT
            iptables -I OUTPUT -p udp -m multiport --sport 53,${xport} -j ACCEPT
            iptables -I INPUT -p udp --dport 1024:65535 -j ACCEPT
        elif [[ ${ws_grpc_mode} == "onlygRPC" ]]; then
            iptables -I INPUT -p tcp -m multiport --dport 53,${gport} -j ACCEPT
            iptables -I INPUT -p udp -m multiport --dport 53,${gport} -j ACCEPT
            iptables -I OUTPUT -p tcp -m multiport --sport 53,${gport} -j ACCEPT
            iptables -I OUTPUT -p udp -m multiport --sport 53,${gport} -j ACCEPT
            iptables -I INPUT -p udp --dport 1024:65535 -j ACCEPT
        elif [[ ${ws_grpc_mode} == "all" ]]; then
            iptables -I INPUT -p tcp -m multiport --dport 53,${xport},${gport} -j ACCEPT
            iptables -I INPUT -p udp -m multiport --dport 53,${xport},${gport} -j ACCEPT
            iptables -I OUTPUT -p tcp -m multiport --sport 53,${xport},${gport} -j ACCEPT
            iptables -I OUTPUT -p udp -m multiport --sport 53,${xport},${gport} -j ACCEPT
            iptables -I INPUT -p udp --dport 1024:65535 -j ACCEPT
        fi
        if [[ "${ID}" == "centos" && ${VERSION_ID} -ge 7 ]]; then
            service iptables save
            service iptables restart
            log_echo "${OK} ${GreenBG} $(gettext "防火墙") $(gettext "重启") ${Font}"
        else
            netfilter-persistent save
            systemctl restart iptables
            log_echo "${OK} ${GreenBG} $(gettext "防火墙") $(gettext "重启") ${Font}"
        fi
        log_echo "${OK} ${GreenBG} $(gettext "开放防火墙相关端口") ${Font}"
        log_echo "${GreenBG} $(gettext "若修改配置, 请注意关闭防火墙相关端口") ${Font}"
        log_echo "${OK} ${GreenBG} $(gettext "配置") Xray FullCone ${Font}"
        ;;
    *)
        log_echo "${OK} ${GreenBG} $(gettext "跳过防火墙设置") ${Font}"
        ;;
    esac
}

ws_path_set() {
    if [[ "on" != ${old_config_status} ]] || [[ ${change_ws_path} == "yes" ]]; then
        if [[ ${ws_grpc_mode} == "onlyws" || ${ws_grpc_mode} == "all" ]] || [[ ${reality_add_more} == "on" ]]; then
            echo
            log_echo "${GreenBG} $(gettext "是否需要自定义") ws $(gettext "伪装路径") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
            read -r path_modify_fq
            case $path_modify_fq in
            [yY][eE][sS] | [yY])
                read_optimize "$(gettext "请输入") ws $(gettext "伪装路径") ($(gettext "不需要")"/":)" "path" "NULL"
                log_echo "${Green} ws $(gettext "伪装路径"): ${path} ${Font}"
                ;;
            *)
                path="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
                log_echo "${Green} ws $(gettext "伪装路径"): ${path} ${Font}"
                ;;
            esac
        else
            path="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
        fi
    elif [[ ${ws_grpc_mode} == "onlyws" ]] || [[ ${ws_grpc_mode} == "all" ]]; then
        echo
        log_echo "${GreenBG} $(gettext "是否需要自定义") ws $(gettext "伪装路径") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        read -r change_ws_path_fq
        case $change_ws_path_fq in
        [yY][eE][sS] | [yY])
            change_ws_path="yes"
            ws_path_set
            ;;
        *) ;;
        esac
    fi
}

grpc_path_set() {
    if [[ "on" != ${old_config_status} ]] || [[ ${change_grpc_path} == "yes" ]]; then
        if [[ ${ws_grpc_mode} == "onlygRPC" || ${ws_grpc_mode} == "all" ]] || [[ ${reality_add_more} == "on" ]]; then
            echo
            log_echo "${GreenBG} $(gettext "是否需要自定义") gRPC $(gettext "伪装路径") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
            read -r path_modify_fq
            case $path_modify_fq in
            [yY][eE][sS] | [yY])
                read_optimize "$(gettext "请输入") gRPC $(gettext "伪装路径") ($(gettext "不需要")"/":)" "serviceName" "NULL"
                log_echo "${Green} gRPC $(gettext "伪装路径"): ${serviceName} ${Font}"
                ;;
            *)
                serviceName="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
                log_echo "${Green} gRPC $(gettext "伪装路径"): ${serviceName} ${Font}"
                ;;
            esac
        else
            serviceName="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
        fi
    elif [[ ${ws_grpc_mode} == "onlygRPC" ]] || [[ ${ws_grpc_mode} == "all" ]]; then
        echo
        log_echo "${GreenBG} $(gettext "是否需要自定义") gRPC $(gettext "伪装路径") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        read -r change_grpc_path_fq
        case $change_grpc_path_fq in
        [yY][eE][sS] | [yY])
            change_grpc_path="yes"
            grpc_path_set
            ;;
        *) ;;
        esac
    fi
}

email_set() {
    if [[ "on" != ${old_config_status} ]]; then
        echo
        log_echo "${GreenBG} $(gettext "是否需要自定义") Xray $(gettext "用户名") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        read -r custom_email_fq
        case $custom_email_fq in
        [yY][eE][sS] | [yY])
            read_optimize "$(gettext "请输入正确的 email") (e.g. me@hey.run): " "custom_email" "NULL"
            ;;
        *)
            custom_email="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})@hey.run"
            ;;
        esac
        log_echo "${Green} Xray $(gettext "用户名") (email): ${custom_email} ${Font}"
    fi
}

UUID_set() {
    if [[ "on" != ${old_config_status} ]]; then
        echo
        log_echo "${GreenBG} $(gettext "是否需要自定义字符串映射") (UUIDv5) [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        read -r need_UUID5
        case $need_UUID5 in
        [yY][eE][sS] | [yY])
            read_optimize "$(gettext "请输入自定义字符串") ($(gettext "最多30字符")):" "UUID5_char" "NULL"
            UUID="$(UUIDv5_tranc ${UUID5_char})"
            log_echo "${Green} $(gettext "自定义字符串"): ${UUID5_char} ${Font}"
            log_echo "${Green} UUIDv5: ${UUID} ${Font}"
            echo
            ;;
        *)
            UUID5_char="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
            UUID="$(UUIDv5_tranc ${UUID5_char})"
            log_echo "${Green} UUID $(gettext "映射字符串"): ${UUID5_char} ${Font}"
            log_echo "${Green} UUID: ${UUID} ${Font}"
            echo
            ;;
        esac
    fi
}

target_set() {
    if [[ "on" == ${old_config_status} ]] && [[ -n $(info_extraction target) ]]; then
        echo
        log_echo "${GreenBG} $(gettext "检测到 target 域名已配置, 是否保留") [${Red}Y${Font}${GreenBG}/N]? ${Font}"
            read -r old_host_fq
            case $old_host_fq in
            [nN][oO] | [nN])
                target_reset=1
                nginx_reality_serverNames_del
                ;;
            *)
                target_reset=0
                ;;
            esac
    fi
    if [[ ${target_reset} == 1 ]] || [[ "on" != ${old_config_status} ]]; then
        local domain
        local output
        local curl_output
        pkg_install "nmap"

        while true; do
            echo
            log_echo "${GreenBG} $(gettext "请输入一个域名") (e.g. bing.com)${Font}"
            log_echo "${Green}$(gettext "域名要求支持 TLSv1.3、X25519 与 H2 以及域名非跳转用")${Font}"
            read_optimize "$(gettext "确认域名符合要求后请输入"): " "domain" "NULL"
            log_echo "${Green}$(gettext "正在检测域名请等待")...${Font}"

            output=$(nmap --script ssl-enum-ciphers -p 443 "${domain}")
            curl_output=$(curl -I -k -m 5 "https://${domain}" 2>&1)

            # 检测TLSv1.3支持
            if ! echo "$output" | grep -q "TLSv1.3"; then
                log_echo "${Warning} ${YellowBG} $(gettext "该域名不支持") TLSv1.3 ${YellowBG}${Font}"
            fi

            # 检测X25519支持
            if ! echo "$output" | grep -q "x25519"; then
                log_echo "${Warning} ${YellowBG} $(gettext "该域名不支持") X25519 ${YellowBG}${Font}"
            fi

            # 检测HTTP/2支持
            if ! echo "$curl_output" | grep -q "HTTP/2"; then
                log_echo "${Warning} ${YellowBG} $(gettext "该域名不支持") HTTP/2 ${YellowBG}${Font}"
            fi

            # 检测是否跳转
            if echo "$curl_output" | grep -i -q 'location:'; then
                log_echo "${Warning} ${YellowBG} $(gettext "该域名发生了跳转") ${YellowBG}${Font}"
            fi

            if ! echo "$output" | grep -q "TLSv1.3" || \
               ! echo "$output" | grep -q "x25519" || \
               ! echo "$curl_output" | grep -q "HTTP/2" || \
               echo "$curl_output" | grep -i -q 'location:'; then
                log_echo "${Warning} ${YellowBG} $(gettext "该域名可能不满足所有要求") ${YellowBG}${Font}"
                log_echo "${GreenBG} $(gettext "是否仍要设置此域名") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
                read -r force_set_fq
                case $force_set_fq in
                    [yY][eE][sS] | [yY])
                        target=$domain
                        break
                        ;;
                    *)
                        continue
                        ;;
                esac
            else
                log_echo "${OK} ${GreenBG} $(gettext "域名") ${domain} $(gettext "满足所有要求") ${Font}"
                target=$domain
                break
            fi
        done
        log_echo "${Green} target $(gettext "域名"): ${target} ${Font}"
    fi
}

serverNames_set() {
    if [[ ${target_reset} == 1 ]] || [[ "on" != ${old_config_status} ]]; then
        local custom_serverNames_fq
        echo
        log_echo "${GreenBG} $(gettext "是否需要自定义") ${target} $(gettext "域名的") serverNames [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        echo -e "${Green} $(gettext "默认为") ${target} $(gettext "域名本身")${Font}"
        echo -e "${Warning} ${YellowBG} $(gettext "如不清楚具体用途, 请勿继续")! ${Font}"
        read -r custom_serverNames_fq
        case $custom_serverNames_fq in
        [yY][eE][sS] | [yY])
            read_optimize "$(gettext "请输入"): " "serverNames" "NULL"
            ;;
        *)
            serverNames=$target
            ;;
        esac
        log_echo "${Green} serverNames: ${serverNames} ${Font}"
        echo
    fi
}

keys_set() {
    if [[ "on" != ${old_config_status} ]]; then
        local keys custom_keys_fq
        echo
        log_echo "${GreenBG} $(gettext "是否需要自定义") privateKey [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        read -r custom_keys_fq
        case $custom_keys_fq in
        [yY][eE][sS] | [yY])
            read_optimize "$(gettext "请输入") privateKey:" "privateKey" "NULL"
            keys=$(${xray_bin_dir}/xray x25519 -i "${privateKey}" | tr '\n' ' ')
            ## 兼容之前的xray版本,一个大版本后删除 from: 2.8.9
            if echo "${keys}" | grep -q "Password (PublicKey): "; then
                password=$(echo "${keys}" | sed 's/.*Password (PublicKey): //' | awk '{print $1}')
            elif echo "${keys}" | grep -q "Password: "; then
                password=$(echo "${keys}" | awk -F"Password: " '{print $2}' | awk '{print $1}')
            elif echo "${keys}" | grep -q "PublicKey: "; then
                password=$(echo "${keys}" | awk -F"PublicKey: " '{print $2}' | awk '{print $1}')
            fi
            ## 兼容结束
            ;;
        *)
            keys=$(${xray_bin_dir}/xray x25519 | tr '\n' ' ')
            privateKey=$(echo "${keys}" | awk -F"PrivateKey: " '{print $2}' | awk '{print $1}')
            ## 兼容之前的xray版本,一个大版本后删除  from: 2.8.9
            if echo "${keys}" | grep -q "Password (PublicKey): "; then
                password=$(echo "${keys}" | sed 's/.*Password (PublicKey): //' | awk '{print $1}')
            elif echo "${keys}" | grep -q "Password: "; then
                password=$(echo "${keys}" | awk -F"Password: " '{print $2}' | awk '{print $1}')
            elif echo "${keys}" | grep -q "PublicKey: "; then
                password=$(echo "${keys}" | awk -F"PublicKey: " '{print $2}' | awk '{print $1}')
            fi
            ## 兼容结束
            ;;
        esac
        log_echo "${Green} privateKey: ${privateKey} ${Font}"
        log_echo "${Green} Password: ${password} ${Font}"
        echo
    fi
}


shortIds_set() {
    if [[ "on" != ${old_config_status} ]]; then
        local custom_shortids_fq
        echo
        log_echo "${GreenBG} $(gettext "是否需要自定义") shortIds [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        read -r custom_shortids_fq
        case $custom_shortids_fq in
        [yY][eE][sS] | [yY])
            read_optimize "$(gettext "请输入") shortIds:" "shortIds" "NULL"
            ;;
        *)
            pkg_install "openssl"
            shortIds=$(openssl rand -hex 8)
            ;;
        esac
        log_echo "${Green} shortIds: ${shortIds} ${Font}"
        echo
    fi
}

ensure_file_manager() {
    local fm_remote_url="https://raw.githubusercontent.com/hello-yunshu/Xray_bash_onekey/main/file_manager.sh"
    local fm_local_path="${idleleo_dir}/file_manager.sh"

    if [[ ! -f "${fm_local_path}" ]]; then
        log_echo "${Info} ${Green} $(gettext "本地文件 file_manager.sh 不存在, 正在下载")... ${Font}"
        if ! curl -sL "$fm_remote_url" -o "$fm_local_path"; then
            log_echo "${Error} ${RedBG} $(gettext "下载失败, 请手动下载并安装新版本") ${Font}"
            return 1
        fi
        chmod +x "$fm_local_path"
    fi
    return 0
}


nginx_upstream_server_set() {
    echo
    log_echo "${GreenBG} $(gettext "是否变更") Nginx $(gettext "负载均衡") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
    echo -e "${Warning} ${YellowBG} $(gettext "如不清楚具体用途, 请勿继续")! ${Font}"
    read -r nginx_upstream_server_fq
    case $nginx_upstream_server_fq in
    [yY][eE][sS] | [yY])
        if [[ ${tls_mode} == "TLS" ]]; then
            echo -e "\n${GreenBG} $(gettext "请选择协议为 ws 或 gRPC") ${Font}"
            echo "1: ws"
            echo "2: gRPC"
            echo "3: $(gettext "返回")"
            local upstream_choose
            read_optimize "$(gettext "请输入"): " "upstream_choose" "NULL" 1 3 "$(gettext "请输入有效的数字")!"

            if ensure_file_manager; then
                case $upstream_choose in
                1) source "${idleleo_dir}/file_manager.sh" wsServers ${nginx_conf_dir} ;;
                2) source "${idleleo_dir}/file_manager.sh" grpcServers ${nginx_conf_dir} ;;
                3) ;;
                *)
                    log_echo "${Error} ${RedBG} $(gettext "无效选项, 请重试")! ${Font}"
                    nginx_upstream_server_set
                    ;;
                esac
            fi
        elif [[ ${tls_mode} == "Reality" ]] && [[ ${reality_add_balance} == "on" ]] && [[ ${reality_add_nginx} == "on" ]]; then
            if ensure_file_manager; then
                source "${idleleo_dir}/file_manager.sh" realityServers ${nginx_conf_dir}
            fi
        else
            log_echo "${Error} ${RedBG} $(gettext "当前模式不支持此操作")! ${Font}"
            return 1
        fi
        ;;
    *) ;;
    esac
}

nginx_servernames_server_set() {
    if [[ ${tls_mode} == "Reality" ]] && [[ ${reality_add_nginx} == "on" ]]; then
        echo
        log_echo "${GreenBG} $(gettext "是否变更") Nginx serverNames [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        echo -e "${Warning} ${YellowBG} $(gettext "如不清楚具体用途, 请勿继续")! ${Font}"
        echo -e "${Info} ${GreenBG} $(gettext "配置用途可以参考文章"): https://hey.run/archives/use-reality ${Font}"
        read -r nginx_servernames_server_fq
        case $nginx_servernames_server_fq in
        [yY][eE][sS] | [yY])
            if ensure_file_manager; then
                source "${idleleo_dir}/file_manager.sh" serverNames ${nginx_conf_dir}
            fi
        ;;
        *) ;;
        esac
    else
        log_echo "${Error} ${RedBG} $(gettext "当前模式不支持此操作")! ${Font}"
    fi
}

UUIDv5_tranc() {
    [[ $# = 0 ]] && return
    python3 -c "import uuid,sys;UUID_NAMESPACE=uuid.UUID('00000000-0000-0000-0000-000000000000');print(uuid.uuid5(UUID_NAMESPACE,sys.argv[1]))" "$1"
}

modify_listen_address() {
    local modifynum modifynum2
    if [[ ${tls_mode} == "Reality" ]]; then
        modifynum=1
        modifynum2=2
    elif [[ ${tls_mode} == "XTLS" ]]; then
        jq '.inbounds[0].listen = "0.0.0.0"' "${xray_conf}" > "${xray_conf}.tmp"
        judge "Xray listen address $(gettext "修改")"
        mv "${xray_conf}.tmp" "${xray_conf}"
        return
    else
        modifynum=0
        modifynum2=1
    fi

    if [[ ${ws_grpc_mode} == "onlyws" ]]; then
        jq --argjson modifynum "$modifynum" \
           '.inbounds[$modifynum].listen = "0.0.0.0"' "${xray_conf}" > "${xray_conf}.tmp"
        judge "Xray listen address $(gettext "修改")"
    elif [[ ${ws_grpc_mode} == "onlygRPC" ]]; then
        jq --argjson modifynum2 "$modifynum2" \
           '.inbounds[$modifynum2].listen = "0.0.0.0"' "${xray_conf}" > "${xray_conf}.tmp"
        judge "Xray listen address $(gettext "修改")"
    elif [[ ${ws_grpc_mode} == "all" ]]; then
        jq --argjson modifynum "$modifynum" --argjson modifynum2 "$modifynum2" \
           '.inbounds[$modifynum].listen = "0.0.0.0" | .inbounds[$modifynum2].listen = "0.0.0.0"' "${xray_conf}" > "${xray_conf}.tmp"
        judge "Xray listen address $(gettext "修改")"
    fi
    mv "${xray_conf}.tmp" "${xray_conf}"
}

modify_inbound_port() {
    if [[ ${tls_mode} == "Reality" ]]; then
        if [[ ${reality_add_nginx} == "off" ]]; then
            jq --argjson port "${port}" --argjson xport "${xport}" --argjson gport "${gport}" \
               '.inbounds[0].port = $port |
                .inbounds[1].port = $xport |
                .inbounds[2].port = $gport' "${xray_conf}" > "${xray_conf}.tmp"
            judge "Xray inbound port $(gettext "修改")"
        else
            jq --argjson xport "${xport}" --argjson gport "${gport}" \
               '.inbounds[1].port = $xport |
                .inbounds[2].port = $gport' "${xray_conf}" > "${xray_conf}.tmp"
            judge "Xray inbound port $(gettext "修改")"
        fi
    elif [[ ${tls_mode} == "XTLS" ]]; then
        jq --argjson port "${port}" '.inbounds[0].port = $port' "${xray_conf}" > "${xray_conf}.tmp"
        judge "Xray inbound port $(gettext "修改")"
    else
        jq --argjson xport "${xport}" --argjson gport "${gport}" \
           '.inbounds[0].port = $xport |
            .inbounds[1].port = $gport' "${xray_conf}" > "${xray_conf}.tmp"
        judge "Xray inbound port $(gettext "修改")"
    fi
    mv "${xray_conf}.tmp" "${xray_conf}"
}

modify_nginx_origin_conf() {
    sed -i "s/worker_processes  1;/worker_processes  auto;/" ${nginx_dir}/conf/nginx.conf
    sed -i "s/^\( *\)worker_connections  1024;.*/\1worker_connections  4096;/" ${nginx_dir}/conf/nginx.conf
    if [[ ${tls_mode} == "TLS" ]]; then
        sed -i "\$i include ${nginx_conf_dir}/*.conf;" ${nginx_dir}/conf/nginx.conf
    elif [[ ${tls_mode} == "Reality" ]] && [[ ${reality_add_nginx} == "on" ]]; then
        sed -i "\$a include ${nginx_conf_dir}/*.conf;" ${nginx_dir}/conf/nginx.conf
    fi
    sed -i "/http\( *\){/a \\\tserver_tokens off;" ${nginx_dir}/conf/nginx.conf
    sed -i "/error_page.*504/i \\\t\\tif (\$host = '${local_ip}') {\\n\\t\\t\\treturn 403;\\n\\t\\t}" ${nginx_dir}/conf/nginx.conf
}

modify_nginx_port() {
    if [[ ${tls_mode} == "Reality" ]] && [[ ${reality_add_nginx} == "on" ]]; then
        sed -i "s/^\( *\)listen.*so_keepalive=on.*/\1listen ${port} reuseport so_keepalive=on backlog=65535;/" ${nginx_conf}
        judge "Nginx port $(gettext "修改")"
    elif [[ ${tls_mode} == "TLS" ]]; then
        sed -i "2s/^\( *\).*ssl reuseport;$/\1listen ${port} ssl reuseport;/" ${nginx_conf}
        sed -i "3s/^\( *\).*ssl reuseport;$/\1listen [::]:${port} ssl reuseport;/" ${nginx_conf}
        sed -i "4s/^\( *\).*quic reuseport;$/\1listen ${port} quic reuseport;/" ${nginx_conf}
        sed -i "5s/^\( *\).*quic reuseport;$/\1listen [::]:${port} quic reuseport;/" ${nginx_conf}
        judge "Xray port $(gettext "修改")"
    fi
    [[ "on" != ${old_config_status} ]] && log_echo "${Green} $(gettext "端口"): ${port} ${Font}"
}

modify_nginx_ssl_other() {
    if [[ -f "${nginx_dir}/conf/nginx.conf" ]] && [[ $(grep -c "server_tokens off;" ${nginx_dir}/conf/nginx.conf) -eq '0' ]] && [[ ${save_originconf} != "Yes" ]]; then
        modify_nginx_origin_conf
    fi
    sed -i "s/^\( *\)server_name\( *\).*/\1server_name\2${domain};/g" ${nginx_ssl_conf}
    sed -i "s/^\( *\)return 301.*/\1return 301 https:\/\/${domain}\$request_uri;/" ${nginx_ssl_conf}
}

modify_nginx_other() {
    if [[ -f "${nginx_dir}/conf/nginx.conf" ]] && [[ $(grep -c "server_tokens off;" ${nginx_dir}/conf/nginx.conf) -eq '0' ]] && [[ ${save_originconf} != "Yes" ]]; then
        modify_nginx_origin_conf
    fi
    if [[ ${tls_mode} == "TLS" ]]; then
        sed -i "s/^\( *\)server_name\( *\).*/\1server_name\2${domain};/g" ${nginx_conf}
        sed -i "s/^\( *\)location ws$/\1location \/${path}/" ${nginx_conf}
        sed -i "s/^\( *\)location grpc$/\1location \/${serviceName}/" ${nginx_conf}
        sed -i "s/^\( *\)return 301.*/\1return 301 https:\/\/${domain}\$request_uri;/" ${nginx_conf}
        if [[ ${ws_grpc_mode} == "onlyws" ]]; then
            sed -i "s/^\( *\)#proxy_pass\(.*\)/\1proxy_pass\2/" ${nginx_conf}
            sed -i "s/^\( *\)#proxy_redirect default;/\1proxy_redirect default;/" ${nginx_conf}
        elif [[ ${ws_grpc_mode} == "onlygRPC" ]]; then
            sed -i "s/^\( *\)#grpc_pass\(.*\)/\1grpc_pass\2/" ${nginx_conf}
        elif [[ ${ws_grpc_mode} == "all" ]]; then
            sed -i "s/^\( *\)#proxy_pass\(.*\)/\1proxy_pass\2/" ${nginx_conf}
            sed -i "s/^\( *\)#proxy_redirect default;/\1proxy_redirect default;/" ${nginx_conf}
            sed -i "s/^\( *\)#grpc_pass\(.*\)/\1grpc_pass\2/" ${nginx_conf}
        fi
    fi
}

nginx_servers_add() {
    touch ${nginx_conf_dir}/127.0.0.1.wsServers
    cat >${nginx_conf_dir}/127.0.0.1.wsServers <<EOF
server 127.0.0.1:${xport} weight=50 max_fails=2 fail_timeout=10;
EOF
    touch ${nginx_conf_dir}/127.0.0.1.grpcServers
    cat >${nginx_conf_dir}/127.0.0.1.grpcServers<<EOF
server 127.0.0.1:${gport} weight=50 max_fails=2 fail_timeout=10;
EOF
}

modify_path() {
    sed -i "s/^\( *\)\"path\".*/\1\"path\": \"\/${path}\"/" ${xray_conf}
    sed -i "s/^\( *\)\"serviceName\".*/\1\"serviceName\": \"${serviceName}\",/" ${xray_conf}
    if [[ ${tls_mode} != "Reality" ]] || [[ "$reality_add_more" == "off" ]]; then
        judge "Xray $(gettext "伪装路径") $(gettext "修改")"
    else
        log_echo "${Warning} ${YellowBG} Reality $(gettext "不支持") path ${Font}"
    fi
}

modify_email_address() {
    if [[ $(jq -r '.inbounds[0].settings.clients|length' ${xray_conf}) == 1 ]] && [[ $(jq -r '.inbounds[1].settings.clients|length' ${xray_conf}) == 1 ]]; then
        sed -i "s/^\( *\)\"email\".*/\1\"email\": \"${custom_email}\"/g" ${xray_conf}
        judge "Xray $(gettext "用户名修改")"
    else
        echo
        log_echo "${Warning} ${YellowBG} $(gettext "请先删除多余的用户") ${Font}"
    fi
}

modify_UUID() {
    if [[ $(jq -r '.inbounds[0].settings.clients|length' ${xray_conf}) == 1 ]] && [[ $(jq -r '.inbounds[1].settings.clients|length' ${xray_conf}) == 1 ]]; then
        sed -i "s/^\( *\)\"id\".*/\1\"id\": \"${UUID}\",/g" ${xray_conf}
        judge "Xray UUID $(gettext "修改")"
    else
        echo
        log_echo "${Warning} ${YellowBG} $(gettext "请先删除多余的用户") ${Font}"
    fi
}

modify_target_serverNames() {
  jq --arg target "${target}:443" --arg serverNames "${serverNames}" '
     .inbounds[0].streamSettings.realitySettings.target = $target |
     .inbounds[0].streamSettings.realitySettings.serverNames = [$serverNames]' "${xray_conf}" > "${xray_conf}.tmp"
  judge "target serverNames $(gettext "配置修改")"
  mv "${xray_conf}.tmp" "${xray_conf}"
}

modify_privateKey_shortIds() {
  jq --arg privateKey "${privateKey}" --arg shortIds "${shortIds}" '
     .inbounds[0].streamSettings.realitySettings.privateKey = $privateKey |
     .inbounds[0].streamSettings.realitySettings.shortIds = [$shortIds]' "${xray_conf}" > "${xray_conf}.tmp"
  judge "privateKey shortIds $(gettext "配置修改")"
  mv "${xray_conf}.tmp" "${xray_conf}"
}

modify_reality_listen_address () {
    jq '.inbounds[0].listen = "127.0.0.1"' "${xray_conf}" > "${xray_conf}.tmp"
    mv "${xray_conf}.tmp" "${xray_conf}"
    judge "Xray reality listen address $(gettext "配置修改")"
}

xray_privilege_escalation() {
    if [[ -n "$(grep "User=nobody" ${xray_systemd_file})" ]]; then
        log_echo "${OK} ${GreenBG} $(gettext "检测到 Xray 的权限控制, 启动修改程序") ${Font}"
        chmod -fR a+rw /var/log/xray/
        chown -fR nobody:nogroup /var/log/xray/
        [[ -f "${ssl_chainpath}/xray.key" ]] && chown -fR nobody:nogroup ${ssl_chainpath}/*
    fi
    log_echo "${OK} ${GreenBG} Xray $(gettext "修改完成") ${Font}"
}

xray_install() {
    if [[ $(xray version) == "" ]] || [[ ! -f "${xray_conf}" ]]; then
        bash -c "$(curl -L https://raw.githubusercontent.com/XTLS/Xray-install/main/install-release.sh)" @ install -f --version v${xray_online_version}
        judge "$(gettext "安装") Xray"
        systemctl daemon-reload
        xray_privilege_escalation
        [[ -f "${xray_default_conf}" ]] && rm -rf ${xray_default_conf}
        ln -s ${xray_conf} ${xray_default_conf}
        xray_version=${xray_online_version}
    else
        log_echo "${OK} ${GreenBG} $(gettext "已安装") Xray ${Font}"
        xray_version=$(info_extraction xray_version)
    fi
}

xray_update() {
    [[ -f "/etc/idleleo/logs/update_failed.mark" ]] && rm -rf "/etc/idleleo/logs/update_failed.mark"
    [[ ! -d "${local_bin}/etc/xray" ]] && log_echo "${GreenBG} $(gettext "若更新无效, 建议直接卸载再安装")! ${Font}"
    log_echo "${Warning} ${GreenBG} $(gettext "部分新功能需要重新安装才可生效") ${Font}"
    ## xray_online_version=$(check_version xray_online_pre_version)
    ## if [[ $(info_extraction xray_version) != ${xray_online_version} ]] && [[ ${xray_version} != ${xray_online_version} ]]; then
    if [[ $(info_extraction xray_version) != ${xray_online_version} ]]; then
        if [[ ${auto_update} != "YES" ]]; then
            log_echo "${Warning} ${GreenBG} $(gettext "检测到存在最新版") ${Font}"
            log_echo "${Warning} ${GreenBG} $(gettext "脚本可能未兼容此版本") ${Font}"
            log_echo "${Warning} ${GreenBG} $(gettext "是否更新") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
            read -r xray_test_fq
            case $xray_test_fq in
            [yY][eE][sS] | [yY])
                log_echo "${OK} ${GreenBG} $(gettext "更新") Xray ! ${Font}"
                systemctl stop xray
                bash -c "$(curl -L https://raw.githubusercontent.com/XTLS/Xray-install/main/install-release.sh)" @ install -f --version v${xray_online_version}
                if ! ${xray_bin_dir}/xray -version &> /dev/null; then
                    log_echo "${Error} ${RedBG} Xray $(gettext "启动失败")! ${Font}"
                    log_echo "${Warning} ${GreenBG} $(gettext "是否回滚到之前的版本") [${Red}Y${Font}${GreenBG}/N]? ${Font}"
                    read -r rollback_fq
                    case $rollback_fq in
                    [nN][oO] | [nN])
                        log_echo "${Info} ${YellowBG} $(gettext "未执行回滚操作")! ${Font}"
                        return 0
                        ;;
                    *)
                        log_echo "${OK} ${GreenBG} $(gettext "正在回滚")... ${Font}"
                        xray_version=$(info_extraction xray_version)
                        bash -c "$(curl -L https://raw.githubusercontent.com/XTLS/Xray-install/main/install-release.sh)" @ install -f --version v${xray_version}
                        if ${xray_bin_dir}/xray -version &> /dev/null; then
                            log_echo "${OK} ${GreenBG} $(gettext "已成功回滚到之前的") Xray $(gettext "版本")! ${Font}"
                        else
                            log_echo "${Error} ${RedBG} Xray $(gettext "回滚失败")! ${Font}"
                            return 1
                        fi
                        ;;
                    esac
                else
                    judge "Xray $(gettext "更新")"
                    xray_version=${xray_online_version}
                fi
                ;;
            *)
                return 0
                ;;
            esac
        else
            systemctl stop xray
            bash -c "$(curl -L https://raw.githubusercontent.com/XTLS/Xray-install/main/install-release.sh)" @ install -f --version v${xray_online_version}
            if ! ${xray_bin_dir}/xray -version &> /dev/null; then
                xray_version=$(info_extraction xray_version)
                bash -c "$(curl -L https://raw.githubusercontent.com/XTLS/Xray-install/main/install-release.sh)" @ install -f --version v${xray_version}
                if ! ${xray_bin_dir}/xray -version &> /dev/null; then
                    echo "Xray $(gettext "回滚失败")!" >>${log_file}
                    return 1
                fi
            fi
        fi
    else
        timeout "$(gettext "重装") Xray !"
        systemctl stop xray
        xray_version=${xray_online_version}
        bash -c "$(curl -L https://raw.githubusercontent.com/XTLS/Xray-install/main/install-release.sh)" @ install -f --version v${xray_online_version}
        judge "Xray $(gettext "重装")"
    fi
    xray_privilege_escalation
    [[ -f "${xray_default_conf}" ]] && rm -rf ${xray_default_conf}
    ln -s ${xray_conf} ${xray_default_conf}
    jq --arg xray_version "${xray_version}" '.xray_version = $xray_version' "${xray_qr_config_file}" > "${xray_qr_config_file}.tmp"
    mv "${xray_qr_config_file}.tmp" "${xray_qr_config_file}"
    systemctl daemon-reload
    systemctl start xray
    if ! ${xray_bin_dir}/xray -version &> /dev/null; then
        [[ ${auto_update} == "YES" ]] && echo "Xray $(gettext "更新失败")!" >>${log_file}
        [[ ${auto_update} != "YES" ]] && log_echo "${Error} ${RedBG} Xray $(gettext "更新失败")! ${Font}"
        return 1
    fi
    return 0
}

reality_balance_add_fq() {
    echo
    log_echo "${GreenBG} $(gettext "是否添加 Reality 负载均衡") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
    echo -e "${Warning} ${Green} $(gettext "使用此功能前,建议先阅读作者教程")! ${Font}"
    echo -e "${Warning} ${YellowBG} $(gettext "如不清楚具体用途, 请勿选择")! ${Font}"
    read -r reality_balance_add_fq
    case $reality_balance_add_fq in
        [yY][eE][sS] | [yY])
            reality_add_balance="on"
            log_echo "${OK} ${GreenBG} $(gettext "已启用") ${Font}"
        ;;
        *)
            reality_add_balance="off"
            log_echo "${OK} ${GreenBG} $(gettext "已跳过") ${Font}"
        ;;

    esac
}


reality_nginx_add_fq() {
    echo
    log_echo "${Warning} ${Green} $(gettext "Reality 协议有流量偷跑的风险") ${Font}"
    if [[ ${reality_add_balance} == "off" ]]; then
        log_echo "${GreenBG} $(gettext "是否额外安装 nginx 前置保护")($(gettext "推荐")) [${Red}Y${Font}${GreenBG}/N]? ${Font}"
        read -r reality_nginx_add_fq
        case $reality_nginx_add_fq in
            [nN][oO] | [nN])
                reality_add_nginx="off"
                if [[ -d "${nginx_dir}" ]]; then
                    echo
                    log_echo "${Warning} ${Green} $(gettext "检测到已安装") nginx ${Font}"
                    uninstall_nginx
                else
                    log_echo "${OK} ${GreenBG} $(gettext "已跳过安装") nginx ${Font}"
                fi
            ;;
            *)
                reality_add_nginx="on"
                nginx_exist_check
                nginx_systemd
                nginx_reality_conf_add
                nginx_reality_servers_add
                nginx_reality_serverNames_add
            ;;

        esac
    else
        log_echo "${Warning} ${Green} $(gettext "检测到已开启 Reality 负载均衡") ${Font}"
        log_echo "${Warning} ${Green} $(gettext "如用作 Reality 负载均衡主服务器必须安装") ${Font}"
        log_echo "${Warning} ${Green} $(gettext "如用作 Reality 负载均衡二级服务器则无需安装") ${Font}"
        log_echo "${GreenBG} $(gettext "是否额外安装 nginx 前置保护") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        read -r reality_nginx_add_fq
        case $reality_nginx_add_fq in
            [yY][eE][sS] | [yY])
                reality_add_nginx="on"
                nginx_exist_check
                nginx_systemd
                nginx_reality_conf_add
                nginx_reality_servers_add
                nginx_reality_serverNames_add
            ;;
            *)
                reality_add_nginx="off"
                if [[ -d "${nginx_dir}" ]]; then
                    echo
                    log_echo "${Warning} ${Green} $(gettext "检测到已安装") nginx ${Font}"
                    uninstall_nginx
                else
                    log_echo "${OK} ${GreenBG} $(gettext "已跳过安装") nginx ${Font}"
                fi
            ;;
        esac
    fi    
}

nginx_exist_check() {
    if [[ -f "${nginx_dir}/sbin/nginx" ]] && [[ -n "$(info_extraction nginx_build_version)" ]]; then
        if [[ -d "${nginx_conf_dir}" ]]; then
            rm -rf ${nginx_conf_dir}/*.conf
            if [[ -f "${nginx_conf_dir}/nginx.default" ]]; then
                cp -fp ${nginx_conf_dir}/nginx.default ${nginx_dir}/conf/nginx.conf
            elif [[ -f "${nginx_dir}/conf/nginx.conf.default" ]]; then
                cp -fp ${nginx_dir}/conf/nginx.conf.default ${nginx_dir}/conf/nginx.conf
            else
                sed -i "/if \(.*\) {$/,+2d" ${nginx_dir}/conf/nginx.conf
                sed -i "/^include.*\*\.conf;$/d" ${nginx_dir}/conf/nginx.conf
            fi
        else
            sed -i "/if \(.*\) {$/,+2d" ${nginx_dir}/conf/nginx.conf
            sed -i "/^include.*\*\.conf;$/d" ${nginx_dir}/conf/nginx.conf
        fi
        modify_nginx_origin_conf
        nginx_build_version=$(info_extraction nginx_build_version)
        log_echo "${OK} ${GreenBG} Nginx $(gettext "已存在, 跳过编译安装过程") ${Font}"
    elif [[ -d "/etc/nginx" ]] && [[ -n "$(info_extraction nginx_version)" ]]; then
        log_echo "${Error} ${RedBG} $(gettext "检测到其他套件安装的 Nginx, 继续安装会造成冲突, 请处理后安装")! ${Font}"
        exit 1
    else
        nginx_install
    fi
}

nginx_install() {
    local temp_dir=$(mktemp -d)
    local current_dir=$(pwd)

    cd "$temp_dir" || exit

    log_echo "${OK} ${GreenBG} $(gettext "即将下载已编译的") Nginx ${Font}"
    local nginx_filename
    case $(uname -m) in
        x86_64)
            nginx_filename="xray-nginx-custom-x86.tar.gz"
            ;;
        armv7l|armv8l|aarch64)
            nginx_filename="xray-nginx-custom-arm.tar.gz"
            ;;
        *)
            log_echo "${Error} ${RedBG} $(gettext "不支持的系统架构"): $(uname -m) ${Font}"
            cd "$current_dir" && rm -rf "$temp_dir"
            exit 1
            ;;
    esac

    local url="https://github.com/hello-yunshu/Xray_bash_onekey_Nginx/releases/download/v${nginx_build_version}/${nginx_filename}"

    if ! curl -L -# -o "$nginx_filename" "$url"; then
        log_echo "${Error} ${RedBG} Nginx $(gettext "下载失败") ${Font}"
        cd "$current_dir" && rm -rf "$temp_dir"
        exit 1
    fi
    log_echo "${OK} ${GreenBG} Nginx $(gettext "下载成功") ${Font}"

    if ! tar -xzf "$nginx_filename" -C ./; then
        log_echo "${Error} ${RedBG} Nginx $(gettext "解压失败") ${Font}"
        cd "$current_dir" && rm -rf "$temp_dir"
        exit 1
    fi

    [[ -d ${nginx_dir} ]] && rm -rf "${nginx_dir}"
    mv ./nginx "${nginx_dir}"

    cp -fp ${nginx_dir}/conf/nginx.conf ${nginx_conf_dir}/nginx.default

    # 修改基本配置
    #sed -i 's/#user  nobody;/user  root;/' ${nginx_dir}/conf/nginx.conf
    modify_nginx_origin_conf

    # 删除临时文件
    cd "$current_dir" && rm -rf "$temp_dir"
    chown -fR nobody:nogroup "${nginx_dir}"
    chmod -fR 755 "${nginx_dir}"
}

nginx_update() {
    [[ -f "/etc/idleleo/logs/update_failed.mark" ]] && rm -rf "/etc/idleleo/logs/update_failed.mark"
    if [[ -f "${nginx_dir}/sbin/nginx" ]]; then
        current_nginx_build_version=$(info_extraction nginx_build_version)
        if [[ ${nginx_build_version} != ${current_nginx_build_version} ]]; then
            ip_check
            if [[ -f "${xray_qr_config_file}" ]]; then
                domain=$(info_extraction host)
                if [[ ${tls_mode} == "TLS" ]]; then
                    port=$(info_extraction port)
                    if [[ ${ws_grpc_mode} == "onlyws" ]]; then
                        xport=$(info_extraction ws_port)
                        path=$(info_extraction path)
                        gport=$((RANDOM % 1000 + 30000))
                        while [[ ${gport} == ${xport} ]]; do gport=$((RANDOM % 1000 + 30000)); done
                        serviceName="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
                    elif [[ ${ws_grpc_mode} == "onlygRPC" ]]; then
                        gport=$(info_extraction grpc_port)
                        serviceName=$(info_extraction serviceName)
                        xport=$((RANDOM % 1000 + 20000))
                        path="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
                    elif [[ ${ws_grpc_mode} == "all" ]]; then
                        xport=$(info_extraction ws_port)
                        path=$(info_extraction path)
                        gport=$(info_extraction grpc_port)
                        serviceName=$(info_extraction serviceName)
                    fi
                    if [[ 0 -eq ${read_config_status} ]]; then
                        [[ ${auto_update} == "YES" ]] && echo "Nginx $(gettext "配置不完整, 退出更新")!" >>${log_file} && return 1
                        log_echo "${Error} ${RedBG} $(gettext "配置不完整, 退出更新")! ${Font}"
                        return 1
                    fi
                elif [[ ${tls_mode} == "Reality" ]] && [[ ${reality_add_nginx} == "on" ]]; then
                    port=$(info_extraction port)
                    serverNames=$(info_extraction serverNames)
                    if [[ 0 -eq ${read_config_status} ]]; then
                        [[ ${auto_update} == "YES" ]] && echo "Nginx $(gettext "配置不完整, 退出更新")!" >>${log_file} && return 1
                        log_echo "${Error} ${RedBG} $(gettext "配置不完整, 退出更新")! ${Font}"
                        return 1
                    fi
                elif [[ ${tls_mode} == "None" ]]; then
                    [[ ${auto_update} == "YES" ]] && echo "$(gettext "当前安装模式不需要") Nginx !" >>${log_file} && return 1
                    log_echo "${Error} ${RedBG} $(gettext "当前安装模式不需要") Nginx ! ${Font}"
                    return 1
                fi
            else
                [[ ${auto_update} == "YES" ]] && echo "Nginx $(gettext "配置不存在, 退出更新")!" >>${log_file} && return 1
                log_echo "${Error} ${RedBG} $(gettext "配置不存在, 退出更新")! ${Font}"
                return 1
            fi
            service_stop
            backup_nginx_dir="${nginx_dir}_backup_${current_nginx_build_version}"
            cp -r ${nginx_dir} ${backup_nginx_dir}
            judge "$(gettext "备份旧版") Nginx"
            timeout "$(gettext "删除旧版") Nginx !"
            rm -rf ${nginx_dir}
            if [[ ${auto_update} != "YES" ]]; then
                echo
                log_echo "${GreenBG} $(gettext "是否保留原 Nginx 配置文件") [${Red}Y${Font}${GreenBG}/N]? ${Font}"
                read -r save_originconf_fq
            else
                save_originconf_fq=1
            fi
            case $save_originconf_fq in
            [nN][oO] | [nN])
                rm -rf ${nginx_conf_dir}/*.conf
                log_echo "${OK} ${GreenBG} $(gettext "原配置文件已删除")! ${Font}"
                ;;
            *)
                save_originconf="Yes"
                log_echo "${OK} ${GreenBG} $(gettext "原配置文件已保留")! ${Font}"
                ;;
            esac
            nginx_install
            if [[ ${tls_mode} == "TLS" ]] && [[ ${save_originconf} != "Yes" ]]; then
                nginx_ssl_conf_add
                nginx_conf_add
                nginx_servers_conf_add
            elif [[ ${tls_mode} == "Reality" ]] && [[ ${reality_add_nginx} == "on" ]] && [[ ${save_originconf} != "Yes" ]]; then
                nginx_reality_conf_add
            fi
            service_start
            sleep 1
            if ! systemctl -q is-active nginx; then
                log_echo "${Error} ${RedBG} Nginx $(gettext "启动失败")! ${Font}"
                if [[ ${auto_update} != "YES" ]]; then
                    echo
                    log_echo "${GreenBG} $(gettext "是否回滚到之前的版本") [${Red}Y${Font}${GreenBG}/N]? ${Font}"
                    read -r rollback_fq
                else
                    service_stop
                    rm -rf ${nginx_dir}
                    mv ${backup_nginx_dir} ${nginx_dir}
                    service_start
                fi
                case $rollback_fq in
                [nN][oO] | [nN])
                    log_echo "${Info} ${YellowBG} $(gettext "未执行回滚操作")! ${Font}"
                    return 1
                    ;;
                *)
                    log_echo "${OK} ${GreenBG} $(gettext "正在回滚")... ${Font}"
                    service_stop
                    rm -rf ${nginx_dir}
                    mv ${backup_nginx_dir} ${nginx_dir}
                    service_start
                    sleep 1
                    if systemctl -q is-active nginx; then
                        log_echo "${OK} ${GreenBG} $(gettext "已成功回滚到之前的") Nginx $(gettext "版本")! ${Font}"
                        jq --arg nginx_build_version "${current_nginx_build_version}" '.nginx_build_version = $nginx_build_version' "${xray_qr_config_file}" > "${xray_qr_config_file}.tmp"
                        mv "${xray_qr_config_file}.tmp" "${xray_qr_config_file}"
                        rm -rf ${backup_nginx_dir}
                        return 1
                    else
                        log_echo "${Error} ${RedBG} $(gettext "回滚失败")! ${Font}"
                        return 1
                    fi
                    ;;
                esac
            else
                jq --arg nginx_build_version "${nginx_build_version}" '.nginx_build_version = $nginx_build_version' "${xray_qr_config_file}" > "${xray_qr_config_file}.tmp"
                mv "${xray_qr_config_file}.tmp" "${xray_qr_config_file}"
                judge "Nginx $(gettext "更新")"
                rm -rf ${backup_nginx_dir}
                judge "$(gettext "删除") Nginx $(gettext "备份")"
            fi
        else
            log_echo "${OK} ${GreenBG} Nginx $(gettext "已为最新版") ${Font}"
        fi
    else
        log_echo "${Error} ${RedBG} Nginx $(gettext "未安装") ${Font}"
    fi
    return 0
}

auto_update() {
    if [[ "${ID}" == "centos" ]]; then
        crontab_file="/var/spool/cron/root"
    else
        crontab_file="/var/spool/cron/crontabs/root"
    fi
    if [[ ! -f "${auto_update_file}" ]] || [[ $(crontab -l | grep -c "auto_update.sh") -lt 1 ]]; then
        echo
        log_echo "${GreenBG} $(gettext "设置后台定时自动更新程序 (包含: 脚本/Xray/Nginx)") ${Font}"
        log_echo "${Warning} ${YellowBG} $(gettext "可能自动更新后有兼容问题, 谨慎启用") ${Font}"
        log_echo "${GreenBG} $(gettext "是否启用") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        read -r auto_update_fq
        case $auto_update_fq in
        [yY][eE][sS] | [yY])
            curl -L -o "${idleleo_dir}/auto_update.sh" "https://raw.githubusercontent.com/hello-yunshu/Xray_bash_onekey/main/auto_update.sh" && chmod +x ${auto_update_file}
            echo "0 1 15 * * bash ${auto_update_file}" >>${crontab_file}
            judge "$(gettext "设置自动更新")"
            ;;
        *) ;;
        esac
    else
        log_echo "${OK} ${GreenBG} $(gettext "已设置自动更新") ${Font}"
        log_echo "${GreenBG} $(gettext "是否关闭") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        read -r auto_update_close_fq
        case $auto_update_close_fq in
        [yY][eE][sS] | [yY])
            sed -i "/auto_update.sh/d" ${crontab_file}
            rm -rf ${auto_update_file}
            judge "$(gettext "删除自动更新")"
            ;;
        *) ;;
        esac
    fi
}

ssl_install() {
    pkg_install "socat"
    judge "$(gettext "安装 SSL 证书生成脚本依赖")"
    curl https://get.acme.sh | sh -s email=${custom_email}
    judge "$(gettext "安装 SSL 证书生成脚本")"
}

domain_check() {
    if [[ "on" == ${old_config_status} ]] && [[ -n $(info_extraction host) ]] && [[ -n $(info_extraction ip_version) ]]; then
        echo
        log_echo "${GreenBG} $(gettext "检测到原域名配置存在, 是否跳过域名设置") [${Red}Y${Font}${GreenBG}/N]? ${Font}"
        read -r old_host_fq
        case $old_host_fq in
        [nN][oO] | [nN]) ;;
        *)
            domain=$(info_extraction host)
            ip_version=$(info_extraction ip_version)
            if [[ ${ip_version} == "IPv4" ]]; then
                local_ip=$(curl -4 ip.me 2>/dev/null || curl -4 ip.im)
            elif [[ ${ip_version} == "IPv6" ]]; then
                local_ip=$(curl -6 ip.me 2>/dev/null || curl -6 ip.im)
            else
                local_ip=${ip_version}
            fi
            if [[ -z ${local_ip} ]]; then
                log_echo "${Error} ${RedBG} $(gettext "无法获取公网IP地址"), $(gettext "安装终止")! ${Font}"
                return 1
            fi
            log_echo "${OK} ${GreenBG} $(gettext "已跳过域名设置") ${Font}"
            return 0
            ;;
        esac
    fi
    echo
    log_echo "${GreenBG} $(gettext "确定域名信息") ${Font}"
    read_optimize "$(gettext "请输入你的域名信息") (e.g. www.hey.run):" "domain" "NULL"
    echo -e "\n${GreenBG} $(gettext "请选择公网IP(IPv4/IPv6)或手动输入域名") ${Font}"
    echo -e "${Red}1${Font}: IPv4 ($(gettext "默认"))"
    echo "2: IPv6"
    echo "3: $(gettext "域名")"
    local ip_version_fq
    read_optimize "$(gettext "请输入"): " "ip_version_fq" 1 1 3 "$(gettext "请输入有效的数字")!"
    log_echo "${OK} ${GreenBG} $(gettext "正在获取公网IP信息, 请耐心等待") ${Font}"
    if [[ ${ip_version_fq} == 1 ]]; then
        local_ip=$(curl -4 ip.me 2>/dev/null || curl -4 ip.im)
        domain_ip=$(ping -4 "${domain}" -c 1 | sed '1{s/[^(]*(//;s/).*//;q}')
        ip_version="IPv4"
    elif [[ ${ip_version_fq} == 2 ]]; then
        local_ip=$(curl -6 ip.me 2>/dev/null || curl -6 ip.im)
        domain_ip=$(ping -6 "${domain}" -c 1 | sed '2{s/[^(]*(//;s/).*//;q}' | tail -n +2)
        ip_version="IPv6"
    elif [[ ${ip_version_fq} == 3 ]]; then
        log_echo "${Warning} ${GreenBG} $(gettext "此选项用于服务器商仅提供域名访问服务器") ${Font}"
        log_echo "${Warning} ${GreenBG} $(gettext "注意服务器商域名添加 CNAME 记录") ${Font}"
        read_optimize "$(gettext "请输入"): " "local_ip" "NULL"
        ip_version=${local_ip}
    else
        local_ip=$(curl -4 ip.me 2>/dev/null || curl -4 ip.im)
        domain_ip=$(ping -4 "${domain}" -c 1 | sed '1{s/[^(]*(//;s/).*//;q}')
        ip_version="IPv4"
    fi
    if [[ -z ${local_ip} ]]; then
        log_echo "${Error} ${RedBG} $(gettext "无法获取公网IP地址"), $(gettext "安装终止")! ${Font}"
        return 1
    fi
    log_echo "$(gettext "域名DNS解析IP"): ${domain_ip}"
    log_echo "$(gettext "公网IP/域名"): ${local_ip}"
    if [[ ${ip_version_fq} != 3 ]] && [[ ${local_ip} == ${domain_ip} ]]; then
        log_echo "${OK} ${GreenBG} $(gettext "域名DNS解析IP与公网IP匹配") ${Font}"
    else
        log_echo "${Warning} ${YellowBG} $(gettext "请确保域名添加了正确的 A/AAAA 记录, 否则将无法正常使用 Xray") ${Font}"
        log_echo "${Error} ${RedBG} $(gettext "域名DNS解析IP与公网IP不匹配, 请选择"): ${Font}"
        echo "1: $(gettext "继续安装")"
        echo "2: $(gettext "重新输入")"
        log_echo "${Red}3${Font}: $(gettext "终止安装") ($(gettext "默认"))"
        local install
        read_optimize "$(gettext "请输入"): " "install" 3 1 3 "$(gettext "请输入有效的数字")!"
        case $install in
        1)
            log_echo "${OK} ${GreenBG} $(gettext "继续安装") ${Font}"
            ;;
        2)
            domain_check
            ;;
        *)
            log_echo "${Error} ${RedBG} $(gettext "安装终止") ${Font}"
            exit 2
            ;;
        esac
    fi
}

ip_check() {
    if [[ "on" == ${old_config_status} || ${auto_update} == "YES" ]] && [[ -n $(info_extraction host) ]] && [[ -n $(info_extraction ip_version) ]]; then
        if [[ ${auto_update} != "YES" ]]; then
            echo
            log_echo "${GreenBG} $(gettext "检测到原IP配置存在, 是否跳过IP设置") [${Red}Y${Font}${GreenBG}/N]? ${Font}"
            read -r old_host_fq
        else
            old_host_fq=1
        fi
        case $old_host_fq in
        [nN][oO] | [nN]) ;;
        *)
            ip_version=$(info_extraction ip_version)
            if [[ ${ip_version} == "IPv4" ]]; then
                local_ip=$(curl -4 ip.me 2>/dev/null || curl -4 ip.im)
            elif [[ ${ip_version} == "IPv6" ]]; then
                local_ip=$(curl -6 ip.me 2>/dev/null || curl -6 ip.im)
            else
                local_ip=${ip_version}
            fi
            if [[ -z ${local_ip} ]]; then
                log_echo "${Error} ${RedBG} $(gettext "无法获取公网IP地址"), $(gettext "安装终止")! ${Font}"
                return 1
            fi
            echo
            log_echo "${OK} ${GreenBG} $(gettext "已跳过IP设置") ${Font}"
            return 0
            ;;
        esac
    fi
    echo
    log_echo "${GreenBG} $(gettext "确定公网IP信息") ${Font}"
    log_echo "${GreenBG} $(gettext "请选择公网IP为IPv4或IPv6") ${Font}"
    echo -e "${Red}1${Font}: IPv4 ($(gettext "默认"))"
    echo "2: IPv6"
    echo "3: $(gettext "手动输入")"
    local ip_version_fq
    read_optimize "$(gettext "请输入"): " "ip_version_fq" 1 1 3 "$(gettext "请输入有效的数字")!"
    [[ -z ${ip_version_fq} ]] && ip_version=1
    log_echo "${OK} ${GreenBG} $(gettext "正在获取公网IP信息, 请耐心等待") ${Font}"
    if [[ ${ip_version_fq} == 1 ]]; then
        local_ip=$(curl -4 ip.me 2>/dev/null || curl -4 ip.im)
        ip_version="IPv4"
    elif [[ ${ip_version_fq} == 2 ]]; then
        local_ip=$(curl -6 ip.me 2>/dev/null || curl -6 ip.im)
        ip_version="IPv6"
    elif [[ ${ip_version_fq} == 3 ]]; then
        read_optimize "$(gettext "请输入"): " "local_ip" "NULL"
        ip_version=${local_ip}
    else
        local_ip=$(curl -4 ip.me 2>/dev/null || curl -4 ip.im)
        ip_version="IPv4"
    fi
    if [[ -z ${local_ip} ]]; then
        log_echo "${Error} ${RedBG} $(gettext "无法获取公网IP地址"), $(gettext "安装终止")! ${Font}"
        return 1
    fi
    log_echo "$(gettext "公网IP/域名"): ${local_ip}"
}

port_exist_check() {
    if [[ 0 -eq $(lsof -i:"$1" | grep -i -c "listen") ]]; then
        log_echo "${OK} ${GreenBG} $1 $(gettext "端口未被占用") ${Font}"
    else
        log_echo "${Error} ${RedBG} $(gettext "检测到") $1 $(gettext "端口被占用"), $(gettext "以下为") $1 $(gettext "端口占用信息") ${Font}"
        lsof -i:"$1"
        timeout "$(gettext "尝试终止占用的进程")!"
        lsof -i:"$1" | awk '{print $2}' | grep -v "PID" | xargs kill -9
        log_echo "${OK} ${GreenBG} kill $(gettext "完成") ${Font}"
    fi
}

acme() {
    systemctl restart nginx
    #暂时解决ca问题
    if "$HOME"/.acme.sh/acme.sh --issue -d ${domain} -w ${idleleo_conf_dir} --server letsencrypt --keylength ec-256 --force --test; then
    #if "$HOME"/.acme.sh/acme.sh --issue -d ${domain} -w ${idleleo_conf_dir} --keylength ec-256 --force --test; then
        log_echo "${OK} ${GreenBG} SSL $(gettext "证书测试签发成功, 开始正式签发") ${Font}"
        rm -rf "$HOME/.acme.sh/${domain}_ecc"
    else
        log_echo "${Error} ${RedBG} SSL $(gettext "证书测试签发失败") ${Font}"
        rm -rf "$HOME/.acme.sh/${domain}_ecc"
        exit 1
    fi

    if "$HOME"/.acme.sh/acme.sh --issue -d ${domain} -w ${idleleo_conf_dir} --server letsencrypt --keylength ec-256 --force; then
    #if "$HOME"/.acme.sh/acme.sh --issue -d ${domain} -w ${idleleo_conf_dir} --keylength ec-256 --force; then
        log_echo "${OK} ${GreenBG} SSL $(gettext "证书生成成功") ${Font}"
        mkdir -p ${ssl_chainpath}
        if "$HOME"/.acme.sh/acme.sh --installcert -d ${domain} --fullchainpath ${ssl_chainpath}/xray.crt --keypath ${ssl_chainpath}/xray.key --ecc --force; then
            chmod -f a+rw ${ssl_chainpath}/xray.crt
            chmod -f a+rw ${ssl_chainpath}/xray.key
            chown -fR nobody:nogroup ${ssl_chainpath}/*
            log_echo "${OK} ${GreenBG} SSL $(gettext "证书配置成功") ${Font}"
            systemctl stop nginx
        fi
    else
        log_echo "${Error} ${RedBG} SSL $(gettext "证书生成失败") ${Font}"
        rm -rf "$HOME/.acme.sh/${domain}_ecc"
        exit 1
    fi
}

xray_conf_add() {
    if [[ $(info_extraction multi_user) != "yes" ]]; then
        if [[ ${tls_mode} == "TLS" ]]; then
            curl -L -o "${xray_conf}" "https://raw.githubusercontent.com/hello-yunshu/Xray_bash_onekey/main/VLESS_tls/config.json"
            modify_listen_address
            modify_path
            modify_inbound_port
        elif [[ ${tls_mode} == "Reality" ]]; then
            curl -L -o "${xray_conf}" "https://raw.githubusercontent.com/hello-yunshu/Xray_bash_onekey/main/VLESS_reality/config.json"
            modify_target_serverNames
            modify_privateKey_shortIds
            xray_reality_add_more
        elif [[ ${tls_mode} == "None" ]]; then
            curl -L -o "${xray_conf}" "https://raw.githubusercontent.com/hello-yunshu/Xray_bash_onekey/main/VLESS_tls/config.json"
            modify_listen_address
            modify_path
            modify_inbound_port
        elif [[ ${tls_mode} == "XTLS" ]]; then
            curl -L -o "${xray_conf}" "https://raw.githubusercontent.com/hello-yunshu/Xray_bash_onekey/main/VLESS_xtls/config.json"
            modify_listen_address
            modify_inbound_port
        fi
        modify_email_address
        modify_UUID
    else
        echo
        log_echo "${Warning} ${GreenBG} $(gettext "检测到 Xray 配置过多用户") ${Font}"
        log_echo "${GreenBG} $(gettext "是否保留原 Xray 配置文件") [${Red}Y${Font}${GreenBG}/N]? ${Font}"
        read -r save_originxray_fq
        case $save_originxray_fq in
        [nN][oO] | [nN])
            rm -rf ${xray_conf}
            jq 'del(.multi_user)' "${xray_qr_config_file}" > "${xray_qr_config_file}.tmp"
            mv "${xray_qr_config_file}.tmp" "${xray_qr_config_file}"
            log_echo "${OK} ${GreenBG} $(gettext "原配置文件已删除")! ${Font}"
            xray_conf_add
            ;;
        *) ;;
        esac
    fi
}

xray_reality_add_more() {
    if [[ ${reality_add_more} == "on" ]]; then
        modify_path
        modify_listen_address
        modify_inbound_port
        judge "$(gettext "添加简单 ws/gRPC 协议")"
    else
        modify_path
        modify_inbound_port
    fi

    if [[ ${reality_add_nginx} == "on" ]] && [[ ${reality_add_balance} == "off" ]]; then
        modify_reality_listen_address
    fi
}

old_config_exist_check() {
    if [[ -f "${xray_qr_config_file}" ]]; then
        if [[ ${old_tls_mode} == ${tls_mode} ]]; then
            echo
            log_echo "${GreenBG} $(gettext "检测到配置文件, 是否读取配置文件") [${Red}Y${Font}${GreenBG}/N]? ${Font}"
            read -r old_config_fq
            case $old_config_fq in
            [nN][oO] | [nN])
                rm -rf ${xray_qr_config_file}
                log_echo "${OK} ${GreenBG} $(gettext "已删除配置文件") ${Font}"
                ;;
            *)
                log_echo "${OK} ${GreenBG} $(gettext "已保留配置文件") ${Font}"
                old_config_status="on"
                old_config_input
                ;;
            esac
        else
            echo
            log_echo "${Warning} ${GreenBG} $(gettext "检测到当前安装模式与配置文件的安装模式不一致") ${Font}"
            log_echo "${GreenBG} $(gettext "是否保留配置文件 (强烈不建议)") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
            read -r old_config_fq
            case $old_config_fq in
            [yY][eE][sS] | [yY])
                log_echo "${Warning} ${GreenBG} $(gettext "请务必确保配置文件正确") ${Font}"
                log_echo "${OK} ${GreenBG} $(gettext "已保留配置文件") ${Font}"
                menu
                ;;
            *)
                rm -rf ${xray_qr_config_file}
                log_echo "${OK} ${GreenBG} $(gettext "已删除配置文件") ${Font}"
                ;;
            esac
        fi
    fi
}

old_config_input() {
    info_extraction_all=$(jq -rc . ${xray_qr_config_file})
    custom_email=$(info_extraction email)
    UUID5_char=$(info_extraction idc)
    UUID=$(info_extraction id)
    if [[ ${tls_mode} == "TLS" ]]; then
        port=$(info_extraction port)
        if [[ ${ws_grpc_mode} == "onlyws" ]]; then
            xport=$(info_extraction ws_port)
            path=$(info_extraction path)
            gport=$((RANDOM % 1000 + 30000))
            while [[ ${gport} == ${xport} ]]; do gport=$((RANDOM % 1000 + 30000)); done
            serviceName="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
        elif [[ ${ws_grpc_mode} == "onlygRPC" ]]; then
            gport=$(info_extraction grpc_port)
            serviceName=$(info_extraction serviceName)
            xport=$((RANDOM % 1000 + 20000))
            while [[ ${gport} == ${xport} ]]; do xport=$((RANDOM % 1000 + 20000)); done
            path="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
        elif [[ ${ws_grpc_mode} == "all" ]]; then
            xport=$(info_extraction ws_port)
            path=$(info_extraction path)
            gport=$(info_extraction grpc_port)
            serviceName=$(info_extraction serviceName)
        fi
    elif [[ ${tls_mode} == "Reality" ]]; then
        port=$(info_extraction port)
        target=$(info_extraction target)
        serverNames=$(info_extraction serverNames)
        privateKey=$(info_extraction privateKey)
        password=$(info_extraction password)
        shortIds=$(info_extraction shortIds)
        if [[ ${reality_add_more} == "on" ]]; then
            if [[ ${ws_grpc_mode} == "onlyws" ]]; then
                xport=$(info_extraction ws_port)
                path=$(info_extraction ws_path)
                gport=$((RANDOM % 1000 + 30000))
                while [[ ${gport} == ${xport} ]]; do gport=$((RANDOM % 1000 + 30000)); done
                serviceName="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
            elif [[ ${ws_grpc_mode} == "onlygRPC" ]]; then
                gport=$(info_extraction grpc_port)
                serviceName=$(info_extraction grpc_serviceName)
                xport=$((RANDOM % 1000 + 20000))
                while [[ ${gport} == ${xport} ]]; do xport=$((RANDOM % 1000 + 20000)); done
                path="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
            elif [[ ${ws_grpc_mode} == "all" ]]; then
                xport=$(info_extraction ws_port)
                path=$(info_extraction ws_path)
                gport=$(info_extraction grpc_port)
                serviceName=$(info_extraction grpc_serviceName)
            fi
        else
            path="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
            serviceName="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
            xport=$((RANDOM % 1000 + 20000))
            gport=$((RANDOM % 1000 + 30000))
        fi
    elif [[ ${tls_mode} == "None" ]]; then
        if [[ ${ws_grpc_mode} == "onlyws" ]]; then
            xport=$(info_extraction ws_port)
            path=$(info_extraction path)
            gport=$((RANDOM % 1000 + 30000))
            while [[ ${gport} == ${xport} ]]; do gport=$((RANDOM % 1000 + 30000)); done
            serviceName="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
        elif [[ ${ws_grpc_mode} == "onlygRPC" ]]; then
            gport=$(info_extraction grpc_port)
            serviceName=$(info_extraction serviceName)
            xport=$((RANDOM % 1000 + 20000))
            while [[ ${gport} == ${xport} ]]; do xport=$((RANDOM % 1000 + 20000)); done
            path="$(head -n 10 /dev/urandom | md5sum | head -c ${random_num})"
        elif [[ ${ws_grpc_mode} == "all" ]]; then
            xport=$(info_extraction ws_port)
            path=$(info_extraction path)
            gport=$(info_extraction grpc_port)
            serviceName=$(info_extraction serviceName)
        fi
    elif [[ ${tls_mode} == "XTLS" ]]; then
        port=$(info_extraction port)
    fi
    if [[ 0 -eq ${read_config_status} ]]; then
        echo
        log_echo "${GreenBG} $(gettext "检测到配置文件不完整, 是否保留配置文件") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
        read -r old_config_input_fq
        case $old_config_input_fq in
        [yY][eE][sS] | [yY])
            old_config_status="off"
            log_echo "${OK} ${GreenBG} $(gettext "已保留配置文件") ${Font}"
            ;;
        *)
            rm -rf ${xray_qr_config_file}
            old_config_status="off"
            log_echo "${OK} ${GreenBG} $(gettext "已删除配置文件") ${Font}"
            ;;
        esac
    fi
}

nginx_ssl_conf_add() {
    touch ${nginx_ssl_conf}
    cat >${nginx_ssl_conf} <<EOF
server {
    listen 80;
    listen [::]:80;
    server_name serveraddr.com;

    location ^~ /.well-known/acme-challenge/ {
        root ${idleleo_dir}/conf;
        default_type "text/plain";
        allow all;
    }
    location = /.well-known/acme-challenge/ {
        return 404;
    }

    location / {
        return 301 https://www.hey.run\$request_uri;
    }
}
EOF
    modify_nginx_ssl_other
    judge "Nginx SSL $(gettext "配置修改")"
}

nginx_conf_add() {
    touch ${nginx_conf}
    cat >${nginx_conf} <<EOF
server {
    listen 443 ssl reuseport;
    listen [::]:443 ssl reuseport;
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;

    http2 on;
    set_real_ip_from      127.0.0.1;
    real_ip_header        X-Forwarded-For;
    real_ip_recursive     on;
    ssl_certificate       ${idleleo_dir}/cert/xray.crt;
    ssl_certificate_key   ${idleleo_dir}/cert/xray.key;
    ssl_protocols         TLSv1.3;
    ssl_ciphers           ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305;
    ssl_ecdh_curve        X25519:prime256v1:secp384r1;
    server_name           serveraddr.com;
    index index.html index.htm;
    root /403.html;
    error_page 403 https://hey.run/helloworld;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
    ssl_early_data on;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_prefer_server_ciphers on;
    add_header Strict-Transport-Security "max-age=31536000";

    location grpc
    {
        #grpc_pass grpc://xray-grpc-server;
        grpc_connect_timeout 60s;
        grpc_read_timeout 720m;
        grpc_send_timeout 720m;
        client_max_body_size 0;
        grpc_set_header X-Real-IP \$remote_addr;
        grpc_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        grpc_set_header Early-Data \$ssl_early_data;
    }

    location ws
    {
        #proxy_pass http://xray-ws-server;
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_connect_timeout 60s;
        proxy_send_timeout 720m;
        proxy_read_timeout 720m;
        proxy_buffering off;
        client_max_body_size 0;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host \$http_host;
    }

    location /
    {
        return 403;
    }
}
EOF
    modify_nginx_port
    modify_nginx_other
    judge "Nginx $(gettext "配置修改")"
}

nginx_reality_conf_add() {
    touch ${nginx_conf}
    cat >${nginx_conf} <<EOF

stream {
    map \$ssl_preread_protocol \$is_valid_protocol {
        #TLSv1.2    1;
        TLSv1.3    1;
        default    0;
    }

    map \$ssl_preread_server_name \$sni_upstream {
        include ${nginx_conf_dir}/*.serverNames;
        default deny;
    }

    map "\$sni_upstream:\$is_valid_protocol" \$final_upstream {
        # 格式:上游名称:协议标记 => 最终上游
        ~^reality:1\$     reality;
        default          deny;
    }

    map \$final_upstream \$is_abnormal {
        deny    1;
        default 0;
    }

    map "\$sni_upstream:\$is_valid_protocol" \$error_type {
        ~^reality:0\$     "tls_error";
        ~^deny:1\$        "sni_error";
        ~^deny:0\$        "tls_sni_error";
        default          "other_error";
    }

    map \$error_type \$is_tls_error {
        "tls_error"      1;
        #"tls_sni_error"  1;
        default          0;
    }

    map \$error_type \$is_sni_error {
        "sni_error"      1;
        "tls_sni_error"  1;
        default          0;
    }

    upstream reality {
        include ${nginx_conf_dir}/*.realityServers;
    }

    upstream deny {
        server 127.0.0.1:9403;
    }

    log_format tls_error_log '\$remote_addr [\$time_local] "\$ssl_preread_server_name" ' 
                             '\$ssl_preread_protocol \$status';

    log_format sni_error_log '\$remote_addr [\$time_local] "\$ssl_preread_server_name" ' 
                             '\$ssl_preread_protocol \$status';

    server {
        listen 443 reuseport so_keepalive=on backlog=65535;
        proxy_pass \$final_upstream;
        ssl_preread on;
        proxy_connect_timeout 5s;
        proxy_timeout 300s;
        access_log ${nginx_dir}/logs/tls_error.log tls_error_log buffer=8k flush=3s if=\$is_tls_error;
        access_log ${nginx_dir}/logs/sni_error.log sni_error_log buffer=8k flush=3s if=\$is_sni_error;
    }

    server {
        listen 127.0.0.1:9403 reuseport;
        #ssl_preread on;
        ssl_reject_handshake on;
        return 444;
        access_log off;
        error_log /dev/null;
    }
}
EOF
    modify_nginx_port
    modify_nginx_other
    judge "Nginx $(gettext "配置修改")"
}

nginx_reality_servers_add () {
    touch ${nginx_conf_dir}/127.0.0.1.realityServers
    cat >${nginx_conf_dir}/127.0.0.1.realityServers <<EOF
server 127.0.0.1:9443 weight=50 max_fails=2 fail_timeout=10;
EOF
    judge "Nginx servers $(gettext "配置修改")"

}

nginx_reality_serverNames_add () {
    touch ${nginx_conf_dir}/${serverNames}.serverNames
    cat >${nginx_conf_dir}/${serverNames}.serverNames <<EOF
${serverNames} reality;
EOF
    judge "Nginx serverNames $(gettext "配置修改")"

}

nginx_reality_serverNames_del () {
    [[ -f "${nginx_conf_dir}/${serverNames}.serverNames" ]] && rm -f "${nginx_conf_dir}/${serverNames}.serverNames"
    # modify_nginx_reality_serverNames
    judge "Nginx serverNames $(gettext "配置删除")"

}

nginx_servers_conf_add() {
    touch ${nginx_upstream_conf}
    cat >${nginx_upstream_conf} <<EOF
upstream xray-ws-server {
    include ${nginx_conf_dir}/*.wsServers;
}

upstream xray-grpc-server {
    include ${nginx_conf_dir}/*.grpcServers;
}
EOF
    nginx_servers_add
    judge "Nginx servers $(gettext "配置修改")"
}

enable_process_systemd() {
    if [[ ${tls_mode} == "TLS" ]] || [[ ${reality_add_nginx} == "on" ]]; then
        [[ -f "${nginx_systemd_file}" ]] && systemctl enable nginx && judge "$(gettext "设置 Nginx 开机自启")"
    fi
    systemctl enable xray
    judge "$(gettext "设置") Xray $(gettext "开机自启")"
}

disable_process_systemd() {
    if [[ ${tls_mode} == "TLS" ]] || [[ ${reality_add_nginx} == "on" ]]; then
        [[ -f "${nginx_systemd_file}" ]] && systemctl stop nginx && systemctl disable nginx && judge "$(gettext "关闭 Nginx 开机自启")"
    fi
    systemctl disable xray
    judge "$(gettext "关闭") Xray $(gettext "开机自启")"
}

stop_service_all() {
    [[ -f "${nginx_systemd_file}" ]] && systemctl stop nginx && systemctl disable nginx
    systemctl stop xray
    systemctl disable xray
    log_echo "${OK} ${GreenBG} $(gettext "停止") ${Font}"
}

service_restart() {
    systemctl daemon-reload
    if [[ ${tls_mode} == "TLS" ]] || [[ ${reality_add_nginx} == "on" ]]; then
        [[ -f "${nginx_systemd_file}" ]] && systemctl restart nginx && judge "Nginx $(gettext "重启")"
    fi
    systemctl restart xray
    judge "Xray $(gettext "重启")"
}

service_start() {
    if [[ ${tls_mode} == "TLS" ]] || [[ ${reality_add_nginx} == "on" ]]; then
        [[ -f "${nginx_systemd_file}" ]] && systemctl start nginx && judge "Nginx $(gettext "启动")"
    fi
    systemctl start xray
    judge "Xray $(gettext "启动")"
}

service_stop() {
    if [[ ${tls_mode} == "TLS" ]] || [[ ${reality_add_nginx} == "on" ]]; then
        [[ -f "${nginx_systemd_file}" ]] && systemctl stop nginx && judge "Nginx $(gettext "停止")"
    fi
    systemctl stop xray
    judge "Xray $(gettext "停止")"
}

acme_cron_update() {
    if [[ ${tls_mode} == "TLS" ]]; then
        local crontab_file
        if [[ "${ID}" == "centos" ]]; then
            crontab_file="/var/spool/cron/root"
        else
            crontab_file="/var/spool/cron/crontabs/root"
        fi
        if [[ -f "${ssl_update_file}" ]] && [[ $(crontab -l | grep -c "ssl_update.sh") == "1" ]]; then
            echo
            log_echo "${Warning} ${GreenBG} $(gettext "新版本已自动设置证书自动更新") ${Font}"
            log_echo "${Warning} ${GreenBG} $(gettext "老版本请及时删除 废弃的 改版证书自动更新")! ${Font}"
            log_echo "${GreenBG} $(gettext "已设置改版证书自动更新") ${Font}"
            log_echo "${GreenBG} $(gettext "是否需要删除改版证书自动更新 (请删除)") [${Red}Y${Font}${GreenBG}/N]? ${Font}"
            read -r remove_acme_cron_update_fq
            case $remove_acme_cron_update_fq in
            [nN][oO] | [nN]) ;;
            *)
                sed -i "/ssl_update.sh/d" ${crontab_file}
                rm -rf ${ssl_update_file}
                judge "$(gettext "删除改版证书自动更新")"
                ;;

            esac
        else
            echo
            log_echo "${OK} ${GreenBG} $(gettext "新版本已自动设置证书自动更新") ${Font}"
        fi
    else
        log_echo "${Error} ${RedBG} $(gettext "当前模式不支持此操作")! ${Font}"
    fi
}

check_cert_status() {
    if [[ ${tls_mode} == "TLS" ]]; then
        host="$(info_extraction host)"
        if [[ -d "$HOME/.acme.sh/${host}_ecc" ]] && [[ -f "$HOME/.acme.sh/${host}_ecc/${host}.key" ]] && [[ -f "$HOME/.acme.sh/${host}_ecc/${host}.cer" ]]; then
            modifyTime=$(stat "$HOME/.acme.sh/${host}_ecc/${host}.cer" | sed -n '7,6p' | awk '{print $2" "$3" "$4" "$5}')
            modifyTime=$(date +%s -d "${modifyTime}")
            currentTime=$(date +%s)
            ((stampDiff = currentTime - modifyTime))
            ((days = stampDiff / 86400))
            ((remainingDays = 90 - days))
            tlsStatus=${remainingDays}
            [[ ${remainingDays} -le 0 ]] && tlsStatus="${Red}$(gettext "已过期")${Font}"
            echo
            log_echo "${Green}$(gettext "证书生成日期"): $(date -d "@${modifyTime}" +"%F %H:%M:%S")${Font}"
            log_echo "${Green}$(gettext "证书生成天数"): ${days}${Font}"
            log_echo "${Green}$(gettext "证书剩余天数"): ${tlsStatus}${Font}"
            echo
            if [[ ${remainingDays} -le 0 ]]; then
                echo
                log_echo "${Warning} ${YellowBG} $(gettext "是否立即更新证书") [Y/${Red}N${Font}${YellowBG}]? ${Font}"
                read -r cert_update_manuel_fq
                case $cert_update_manuel_fq in
                [yY][eE][sS] | [yY])
                    systemctl stop xray
                    judge "Xray $(gettext "停止")"
                    cert_update_manuel
                    ;;
                *) ;;
                esac
            fi
        else
            log_echo "${Error} ${RedBG} $(gettext "证书签发工具不存在, 请确认是否证书为脚本签发")! ${Font}"
        fi
    else
        log_echo "${Error} ${RedBG} $(gettext "当前模式不支持此操作")! ${Font}"
    fi
}

cert_update_manuel() {
    if [[ ${tls_mode} == "TLS" ]]; then
        if [[ -f "${amce_sh_file}" ]]; then
            "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh"
        else
            log_echo "${Error} ${RedBG} $(gettext "证书签发工具不存在, 请确认是否证书为脚本签发")! ${Font}"
        fi
        host="$(info_extraction host)"
        "$HOME"/.acme.sh/acme.sh --installcert -d "${host}" --fullchainpath ${ssl_chainpath}/xray.crt --keypath ${ssl_chainpath}/xray.key --ecc
        judge "$(gettext "证书更新")"
        service_restart
    else
        log_echo "${Error} ${RedBG} $(gettext "当前模式不支持此操作")! ${Font}"
    fi
}

set_fail2ban() {
    mf_remote_url="https://raw.githubusercontent.com/hello-yunshu/Xray_bash_onekey/main/fail2ban_manager.sh"
    if [ ! -f "${idleleo_dir}/fail2ban_manager.sh" ]; then
        log_echo "${Info} ${Green} $(gettext "本地文件 fail2ban_manager.sh 不存在, 正在下载")... ${Font}"
        curl -sL "$mf_remote_url" -o "${idleleo_dir}/fail2ban_manager.sh"
        if [ $? -ne 0 ]; then
            log_echo "${Error} ${RedBG} $(gettext "下载失败, 请手动下载并安装新版本") ${Font}"
            return 1
        fi
        chmod +x "${idleleo_dir}/fail2ban_manager.sh"
    fi
    source "${idleleo_dir}/fail2ban_manager.sh"
}

setup_auto_clean_logs() {
    local logrotate_config
    echo

    log_echo "${GreenBG} $(gettext "是否需要设置自动清理日志") [${Red}Y${Font}${GreenBG}/N]? ${Font}"
    read -r auto_clean_logs_fq
    case $auto_clean_logs_fq in
    [nN][oO] | [nN])
        log_echo "${OK} ${Green} $(gettext "已跳过设置自动清理日志") ${Font}"
        ;;
    *)
        log_echo "${OK} ${Green} $(gettext "将在 每周三 04:00 自动清空日志") ${Font}"

        logrotate_config="/etc/logrotate.d/xray_log_cleanup"

        if [[ -f "$logrotate_config" ]]; then
            log_echo "${Warning} ${YellowBG} $(gettext "已设置自动清理日志任务") ${Font}"
            log_echo "${GreenBG} $(gettext "是否需要删除现有自动清理日志任务") [Y/${Red}N${Font}${GreenBG}]? ${Font}"
            read -r delete_task
            case $delete_task in
            [yY][eE][sS] | [yY])
                rm -f "$logrotate_config"
                judge "$(gettext "删除自动清理日志任务")"
                ;;
            *)
                log_echo "${OK} ${Green} $(gettext "保留现有自动清理日志任务") ${Font}"
                return
                ;;
            esac
        fi

        echo "/var/log/xray/*.log ${nginx_dir}/logs/*.log {" > "$logrotate_config"
        echo "    weekly" >> "$logrotate_config"
        echo "    rotate 3" >> "$logrotate_config"
        echo "    compress" >> "$logrotate_config"
        echo "    missingok" >> "$logrotate_config"
        echo "    notifempty" >> "$logrotate_config"
        echo "    create 640 nobody nogroup" >> "$logrotate_config"
        echo "}" >> "$logrotate_config"

        judge "$(gettext "设置自动清理日志")"
        ;;
    esac
}

clean_logs() {
    echo
    log_echo "${Green} $(gettext "检测到日志文件大小如下:") ${Font}"
    log_echo "${Green}$(du -sh /var/log/xray ${nginx_dir}/logs 2>/dev/null)${Font}"
    timeout "$(gettext "即将清除")!"
    for i in $(find /var/log/xray/ ${nginx_dir}/logs -name "*.log" 2>/dev/null); do cat /dev/null >"$i" 2>/dev/null; done
    judge "$(gettext "日志清理")"
    setup_auto_clean_logs
}

vless_qr_config_tls_ws() {
    cat >${xray_qr_config_file} <<-EOF
{
    "shell_mode": "${shell_mode}",
    "ws_grpc_mode": "${ws_grpc_mode}",
    "host": "${domain}",
    "ip_version": "${ip_version}",
    "port": ${port},
    "ws_port": "${artxport}",
    "grpc_port": "${artgport}",
    "tls": "TLS",
    "email": "${custom_email}",
    "idc": "${UUID5_char}",
    "id": "${UUID}",
    "net": "ws/gRPC",
    "path": "${artpath}",
    "serviceName": "${artserviceName}",
    "shell_version": "${shell_version}",
    "xray_version": "${xray_version}",
    "nginx_build_version": "${nginx_build_version}"
}
EOF
    info_extraction_all=$(jq -rc . ${xray_qr_config_file})
}

vless_qr_config_reality() {
    cat >${xray_qr_config_file} <<-EOF
{
    "shell_mode": "${shell_mode}",
    "ws_grpc_mode": "${ws_grpc_mode}",
    "host": "${local_ip}",
    "ip_version": "${ip_version}",
    "port": ${port},
    "email": "${custom_email}",
    "idc": "${UUID5_char}",
    "id": "${UUID}",
    "net": "raw",
    "tls": "Reality",
    "target": "${target}",
    "serverNames":"${serverNames}",
    "privateKey":"${privateKey}",
    "password":"${password}",
    "shortIds":"${shortIds}",
    "reality_add_nginx": "${reality_add_nginx}",
    "reality_add_balance": "${reality_add_balance}",
    "reality_add_more": "${reality_add_more}",
    "ws_port": "${artxport}",
    "grpc_port": "${artgport}",
    "ws_path": "${artpath}",
    "grpc_serviceName": "${artserviceName}",
    "shell_version": "${shell_version}",
    "xray_version": "${xray_version}"
}
EOF
    if [[ ${reality_add_nginx} == "on" ]]; then
        jq --arg nginx_build_version "${nginx_build_version}" '. + {"nginx_build_version": $nginx_build_version}' "${xray_qr_config_file}" > "${xray_qr_config_file}.tmp"
        mv "${xray_qr_config_file}.tmp" "${xray_qr_config_file}"
    fi
    info_extraction_all=$(jq -rc . ${xray_qr_config_file})
}

vless_qr_config_xtls_only() {
    if [[ "on" == ${old_config_status} ]]; then
        port=$(info_extraction port)
    fi
    cat >${xray_qr_config_file} <<-EOF
{
    "shell_mode": "${shell_mode}",
    "host": "${local_ip}",
    "ip_version": "${ip_version}",
    "port": "${port}",
    "tls": "XTLS",
    "email": "${custom_email}",
    "idc": "${UUID5_char}",
    "id": "${UUID}",
    "net": "raw",
    "security": "none",
    "flow": "xtls-rprx-vision",
    "shell_version": "${shell_version}",
    "xray_version": "${xray_version}"
}
EOF
    info_extraction_all=$(jq -rc . ${xray_qr_config_file})
}

vless_qr_config_ws_only() {
    cat >${xray_qr_config_file} <<-EOF
{
    "shell_mode": "${shell_mode}",
    "ws_grpc_mode": "${ws_grpc_mode}",
    "host": "${local_ip}",
    "ip_version": "${ip_version}",
    "ws_port": "${artxport}",
    "grpc_port": "${artgport}",
    "tls": "None",
    "email": "${custom_email}",
    "idc": "${UUID5_char}",
    "id": "${UUID}",
    "net": "ws/gRPC",
    "path": "${artpath}",
    "serviceName": "${artserviceName}",
    "shell_version": "${shell_version}",
    "xray_version": "${xray_version}"
}
EOF
    info_extraction_all=$(jq -rc . ${xray_qr_config_file})
}

vless_urlquote() {
    [[ $# = 0 ]] && return 1
    python3 -c "import urllib.request,sys;print(urllib.request.quote(sys.argv[1]))" "$1"
}

vless_qr_link_image() {
    if [[ ${tls_mode} == "TLS" ]]; then
        if [[ ${ws_grpc_mode} == "onlyws" ]]; then
            vless_ws_link="vless://$(info_extraction id)@$(vless_urlquote $(info_extraction host)):$(info_extraction port)?path=%2f$(vless_urlquote $(info_extraction path))%3Fed%3D2048&security=tls&encryption=none&host=$(vless_urlquote $(info_extraction host))&type=ws&fp=chrome#$(vless_urlquote $(info_extraction host))+ws%E5%8D%8F%E8%AE%AE"
        elif [[ ${ws_grpc_mode} == "onlygRPC" ]]; then
            vless_grpc_link="vless://$(info_extraction id)@$(vless_urlquote $(info_extraction host)):$(info_extraction port)?serviceName=$(vless_urlquote $(info_extraction serviceName))&security=tls&encryption=none&host=$(vless_urlquote $(info_extraction host))&type=grpc&fp=chrome#$(vless_urlquote $(info_extraction host))+gRPC%E5%8D%8F%E8%AE%AE"
        elif [[ ${ws_grpc_mode} == "all" ]]; then
            vless_ws_link="vless://$(info_extraction id)@$(vless_urlquote $(info_extraction host)):$(info_extraction port)?path=%2f$(vless_urlquote $(info_extraction path))%3Fed%3D2048&security=tls&encryption=none&host=$(vless_urlquote $(info_extraction host))&type=ws&fp=chrome#$(vless_urlquote $(info_extraction host))+ws%E5%8D%8F%E8%AE%AE"
            vless_grpc_link="vless://$(info_extraction id)@$(vless_urlquote $(info_extraction host)):$(info_extraction port)?serviceName=$(vless_urlquote $(info_extraction serviceName))&security=tls&encryption=none&host=$(vless_urlquote $(info_extraction host))&type=grpc&fp=chrome#$(vless_urlquote $(info_extraction host))+gRPC%E5%8D%8F%E8%AE%AE"
        fi
    elif [[ ${tls_mode} == "Reality" ]]; then
        vless_link="vless://$(info_extraction id)@$(vless_urlquote $(info_extraction host)):$(info_extraction port)?security=reality&flow=xtls-rprx-vision&fp=chrome&pbk=$(info_extraction password)&sni=$(info_extraction serverNames)&target=$(info_extraction target)&sid=$(info_extraction shortIds)#$(vless_urlquote $(info_extraction host))+Reality%E5%8D%8F%E8%AE%AE"
        if [[ ${reality_add_more} == "on" ]]; then
            if [[ ${ws_grpc_mode} == "onlyws" ]]; then
                vless_ws_link="vless://$(info_extraction id)@$(vless_urlquote $(info_extraction host)):$(info_extraction ws_port)?path=%2f$(vless_urlquote $(info_extraction path))%3Fed%3D2048&encryption=none&type=ws&fp=chrome#$(vless_urlquote $(info_extraction host))+%E5%8D%95%E7%8B%ADws%E5%8D%8F%E8%AE%AE"
            elif [[ ${ws_grpc_mode} == "onlygRPC" ]]; then
                vless_grpc_link="vless://$(info_extraction id)@$(vless_urlquote $(info_extraction host)):$(info_extraction grpc_port)?serviceName=$(vless_urlquote $(info_extraction serviceName))&encryption=none&type=grpc&fp=chrome#$(vle
Download .txt
gitextract_9rt8erfj/

├── .dockerignore
├── .editorconfig
├── .github/
│   └── workflows/
│       ├── cleanup.yml
│       ├── i18n.yml
│       └── translate_readme.yml
├── DOCKER.md
├── Dockerfile
├── LICENSE
├── README.md
├── VLESS_reality/
│   └── config.json
├── VLESS_tls/
│   └── config.json
├── VLESS_xtls/
│   └── config.json
├── auto_update.sh
├── docker-compose.yml
├── docker-entrypoint.sh
├── fail2ban_manager.sh
├── fake-systemctl
├── file_manager.sh
├── install.sh
├── languages/
│   ├── en/
│   │   ├── DOCKER.md
│   │   ├── LC_MESSAGES/
│   │   │   ├── version
│   │   │   └── xray_install.mo
│   │   └── README.md
│   ├── fa/
│   │   ├── DOCKER.md
│   │   ├── LC_MESSAGES/
│   │   │   ├── version
│   │   │   └── xray_install.mo
│   │   └── README.md
│   ├── fr/
│   │   ├── DOCKER.md
│   │   ├── LC_MESSAGES/
│   │   │   ├── version
│   │   │   └── xray_install.mo
│   │   └── README.md
│   ├── ko/
│   │   ├── DOCKER.md
│   │   ├── LC_MESSAGES/
│   │   │   ├── version
│   │   │   └── xray_install.mo
│   │   └── README.md
│   ├── ru/
│   │   ├── DOCKER.md
│   │   ├── LC_MESSAGES/
│   │   │   ├── version
│   │   │   └── xray_install.mo
│   │   └── README.md
│   └── zh_CN/
│       └── LC_MESSAGES/
│           ├── version
│           └── xray_install.mo
├── po/
│   ├── POTFILES.in
│   ├── cache_English.json
│   ├── cache_French.json
│   ├── cache_Korean.json
│   ├── cache_Persian.json
│   ├── cache_Russian.json
│   ├── en.po
│   ├── fa.po
│   ├── fr.po
│   ├── ko.po
│   ├── ru.po
│   ├── xray_install.pot
│   └── zh_CN.po
├── ssl_update.sh
├── status_config.json
├── translate.py
└── translate_readme.py
Download .txt
SYMBOL INDEX (18 symbols across 2 files)

FILE: translate.py
  function load_translation_cache (line 12) | def load_translation_cache(cache_file):
  function save_translation_cache (line 24) | def save_translation_cache(cache_file, translations):
  function get_version (line 32) | def get_version(version_file):
  function update_version (line 38) | def update_version(version_file):
  function contains_chinese (line 44) | def contains_chinese(text):
  function translate_text_qwen_mt (line 55) | def translate_text_qwen_mt(text, target_lang):
  function translate_text_google (line 85) | def translate_text_google(text, target_lang):
  function needs_fallback_translation (line 95) | def needs_fallback_translation(translated_text):
  function clean_translation (line 98) | def clean_translation(text):
  function translate_po_file (line 101) | def translate_po_file(input_file, output_file, target_lang_code, target_...

FILE: translate_readme.py
  function load_translation_cache (line 12) | def load_translation_cache(cache_file):
  function save_translation_cache (line 18) | def save_translation_cache(cache_file, translations):
  function get_cache_key (line 22) | def get_cache_key(text, target_lang):
  function translate_text (line 26) | def translate_text(text, target_lang, translator, translations):
  function should_translate (line 41) | def should_translate(segment):
  function extract_text_segments (line 54) | def extract_text_segments(content):
  function replace_translation (line 72) | def replace_translation(original_content, translated_segments):
  function translate_readme (line 89) | def translate_readme(input_file, output_dir):
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,007K chars).
[
  {
    "path": ".dockerignore",
    "chars": 165,
    "preview": ".github/\n.git/\n.gitignore\n.editorconfig\nLICENSE\npo/\ntranslate.py\ntranslate_readme.py\nDockerfile\ndocker-compose.yml\ndocke"
  },
  {
    "path": ".editorconfig",
    "chars": 79,
    "preview": "root = true\n[*]\nend_of_line = crlf\ninsert_final_newline = true\ncharset = utf-8\n"
  },
  {
    "path": ".github/workflows/cleanup.yml",
    "chars": 456,
    "preview": "name: Cleanup Workflow Runs\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '0 0 * * *'\n\njobs:\n  del_runs:\n    runs-on"
  },
  {
    "path": ".github/workflows/i18n.yml",
    "chars": 4660,
    "preview": "name: Update Translations\n\non:\n  push:\n    paths:\n      - 'install.sh'\n      - 'fail2ban_manager.sh'\n      - 'file_manag"
  },
  {
    "path": ".github/workflows/translate_readme.yml",
    "chars": 1725,
    "preview": "name: Translate README\n\non:\n  push:\n    paths:\n      - 'README.md'\n    branches:\n      - main\n  workflow_dispatch:\n\njobs"
  },
  {
    "path": "DOCKER.md",
    "chars": 2396,
    "preview": "# Docker 部署指南\n\n简体中文 | [English](/languages/en/DOCKER.md) | [Français](/languages/fr/DOCKER.md) | [Русский](/languages/ru"
  },
  {
    "path": "Dockerfile",
    "chars": 2864,
    "preview": "FROM debian:bookworm-slim\n\nARG XRAY_VERSION=26.3.27\nARG NGINX_BUILD_VERSION=2026.04.14\n\nENV DEBIAN_FRONTEND=noninteracti"
  },
  {
    "path": "LICENSE",
    "chars": 35149,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
  },
  {
    "path": "README.md",
    "chars": 3944,
    "preview": "# Xray 一键安装脚本 — Reality / VLESS WebSocket/gRPC+TLS + Nginx\n\n简体中文 | [English](/languages/en/README.md) | [Français](/lang"
  },
  {
    "path": "VLESS_reality/config.json",
    "chars": 3386,
    "preview": "{\n    \"log\": {\n        \"access\": \"/var/log/xray/access.log\",\n        \"error\": \"/var/log/xray/error.log\",\n        \"loglev"
  },
  {
    "path": "VLESS_tls/config.json",
    "chars": 2478,
    "preview": "{\n    \"log\": {\n        \"access\": \"/var/log/xray/access.log\",\n        \"error\": \"/var/log/xray/error.log\",\n        \"loglev"
  },
  {
    "path": "VLESS_xtls/config.json",
    "chars": 1659,
    "preview": "{\n    \"log\": {\n        \"access\": \"/var/log/xray/access.log\",\n        \"error\": \"/var/log/xray/error.log\",\n        \"loglev"
  },
  {
    "path": "auto_update.sh",
    "chars": 4781,
    "preview": "#!/usr/bin/env bash\nPATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin\nexport PATH\n\nVERSION=\"1.0.4\""
  },
  {
    "path": "docker-compose.yml",
    "chars": 534,
    "preview": "services:\n  xray-onekey:\n    build:\n      context: .\n      dockerfile: Dockerfile\n    container_name: xray-onekey\n    re"
  },
  {
    "path": "docker-entrypoint.sh",
    "chars": 3020,
    "preview": "#!/bin/bash\nset -e\n\nexport PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin\n\nXRAY_BIN=\"/usr/local/bin/x"
  },
  {
    "path": "fail2ban_manager.sh",
    "chars": 16933,
    "preview": "#!/bin/bash\n\n# 定义当前版本号\nmf_SCRIPT_VERSION=\"1.2.1\"\n\nmf_main_menu() {\n    check_system\n    while true; do\n        echo\n    "
  },
  {
    "path": "fake-systemctl",
    "chars": 4487,
    "preview": "#!/bin/bash\n\nXRAY_PID_FILE=\"/var/run/xray.pid\"\nNGINX_PID_FILE=\"/usr/local/nginx/logs/nginx.pid\"\nXRAY_BIN=\"/usr/local/bin"
  },
  {
    "path": "file_manager.sh",
    "chars": 8827,
    "preview": "#!/bin/bash\n\n# 定义当前版本号\nfm_SCRIPT_VERSION=\"1.2.4\"\n\nif [ -z \"$1\" ]; then\n    echo \"$(gettext \"用法\"):\" $0 <$(gettext \"文件扩展名\""
  },
  {
    "path": "install.sh",
    "chars": 178117,
    "preview": "#!/bin/bash\n\nPATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin\nexport PATH\n#stty erase ^?\n\ncd \"$(\n"
  },
  {
    "path": "languages/en/DOCKER.md",
    "chars": 3536,
    "preview": "# Docker Deployment Guide\n\n[简体中文](/DOCKER.md) | English | [Français](/languages/fr/DOCKER.md) | [Русский](/languages/ru/"
  },
  {
    "path": "languages/en/LC_MESSAGES/version",
    "chars": 10,
    "preview": "1777024069"
  },
  {
    "path": "languages/en/README.md",
    "chars": 5997,
    "preview": "# Xray One-Click Installation Script — Reality / VLESS WebSocket/gRPC+TLS + Nginx\n\n[简体中文](/README.md) | English | [Franç"
  },
  {
    "path": "languages/fa/DOCKER.md",
    "chars": 3452,
    "preview": "# راهنمای استقرار Docker\n\n[简体中文](/DOCKER.md) | [English](/languages/en/DOCKER.md) | [Français](/languages/fr/DOCKER.md) "
  },
  {
    "path": "languages/fa/LC_MESSAGES/version",
    "chars": 10,
    "preview": "1777024069"
  },
  {
    "path": "languages/fa/README.md",
    "chars": 5764,
    "preview": "# اسکریپت نصب خودکار Xray — Reality / VLESS WebSocket/gRPC+TLS + Nginx\n\n[简体中文](/README.md) | [English](/languages/en/REA"
  },
  {
    "path": "languages/fr/DOCKER.md",
    "chars": 3994,
    "preview": "# Guide de déploiement Docker\n\n[简体中文](/DOCKER.md) | [English](/languages/en/DOCKER.md) | Français | [Русский](/languages"
  },
  {
    "path": "languages/fr/LC_MESSAGES/version",
    "chars": 10,
    "preview": "1777024070"
  },
  {
    "path": "languages/fr/README.md",
    "chars": 6607,
    "preview": "# Script d'installation automatique Xray — Reality / VLESS WebSocket/gRPC+TLS + Nginx\n\n[简体中文](/README.md) | [English](/l"
  },
  {
    "path": "languages/ko/DOCKER.md",
    "chars": 2688,
    "preview": "# Docker 배포 가이드\n\n[简体中文](/DOCKER.md) | [English](/languages/en/DOCKER.md) | [Français](/languages/fr/DOCKER.md) | [Русски"
  },
  {
    "path": "languages/ko/LC_MESSAGES/version",
    "chars": 10,
    "preview": "1777024070"
  },
  {
    "path": "languages/ko/README.md",
    "chars": 4406,
    "preview": "# Xray 원클릭 설치 스크립트 — Reality / VLESS WebSocket/gRPC+TLS + Nginx\n\n[简体中文](/README.md) | [English](/languages/en/README.md)"
  },
  {
    "path": "languages/ru/DOCKER.md",
    "chars": 3677,
    "preview": "# Руководство по развёртыванию Docker\n\n[简体中文](/DOCKER.md) | [English](/languages/en/DOCKER.md) | [Français](/languages/f"
  },
  {
    "path": "languages/ru/LC_MESSAGES/version",
    "chars": 10,
    "preview": "1777024070"
  },
  {
    "path": "languages/ru/README.md",
    "chars": 6444,
    "preview": "# Скрипт автоматической установки Xray — Reality / VLESS WebSocket/gRPC+TLS + Nginx\n\n[简体中文](/README.md) | [English](/lan"
  },
  {
    "path": "languages/zh_CN/LC_MESSAGES/version",
    "chars": 11,
    "preview": "1737125065\n"
  },
  {
    "path": "po/POTFILES.in",
    "chars": 47,
    "preview": "install.sh\nfail2ban_manager.sh\nfile_manager.sh\n"
  },
  {
    "path": "po/cache_English.json",
    "chars": 24430,
    "preview": "{\n  \"错误\": \"error\",\n  \"警告\": \"warning\",\n  \"未安装\": \"not installed\",\n  \"日志文件归档失败\": \"log file archiving failed\",\n  \"日志文件已轮转并归档"
  },
  {
    "path": "po/cache_French.json",
    "chars": 28111,
    "preview": "{\n  \"错误\": \"erreur\",\n  \"警告\": \"avertissement\",\n  \"未安装\": \"non installé\",\n  \"日志文件归档失败\": \"échec de l'archivage du fichier jou"
  },
  {
    "path": "po/cache_Korean.json",
    "chars": 16515,
    "preview": "{\n  \"错误\": \"오류\",\n  \"警告\": \"경고\",\n  \"未安装\": \"미설치\",\n  \"日志文件归档失败\": \"로그 파일 보관 실패\",\n  \"日志文件已轮转并归档为\": \"로그 파일이 회전되어 다음과 같이 보관되었습니다\""
  },
  {
    "path": "po/cache_Persian.json",
    "chars": 24078,
    "preview": "{\n  \"错误\": \"اشتباه\",\n  \"警告\": \"هشدار\",\n  \"未安装\": \"ننصب نشده است\",\n  \"日志文件归档失败\": \"ارشیو شدن فایل روزنامه با موفقیت انجام نشد"
  },
  {
    "path": "po/cache_Russian.json",
    "chars": 26019,
    "preview": "{\n  \"错误\": \"ошибка\",\n  \"警告\": \"предупреждение\",\n  \"未安装\": \"не установлен\",\n  \"日志文件归档失败\": \"не удалось архивировать файл журн"
  },
  {
    "path": "po/en.po",
    "chars": 47817,
    "preview": "# English translations for xray_install package.\n# Copyright (C) 2026 yunshu\n# This file is distributed under the same l"
  },
  {
    "path": "po/fa.po",
    "chars": 47418,
    "preview": "# Persian translations for xray_install package.\n# Copyright (C) 2026 yunshu\n# This file is distributed under the same l"
  },
  {
    "path": "po/fr.po",
    "chars": 51496,
    "preview": "# French translations for xray_install package.\n# Copyright (C) 2026 yunshu\n# This file is distributed under the same li"
  },
  {
    "path": "po/ko.po",
    "chars": 39894,
    "preview": "# Korean translations for xray_install package.\n# Copyright (C) 2026 yunshu\n# This file is distributed under the same li"
  },
  {
    "path": "po/ru.po",
    "chars": 49483,
    "preview": "# Russian translations for xray_install package.\n# Copyright (C) 2026 yunshu\n# This file is distributed under the same l"
  },
  {
    "path": "po/xray_install.pot",
    "chars": 32520,
    "preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR yunshu\n# This file is distributed under the same license as the xray_inst"
  },
  {
    "path": "po/zh_CN.po",
    "chars": 32516,
    "preview": "# Chinese translations for xray_install package.\n# Copyright (C) 2026 yunshu\n# This file is distributed under the same l"
  },
  {
    "path": "ssl_update.sh",
    "chars": 905,
    "preview": "#!/usr/bin/env bash\nPATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin\nexport PATH\n\ncert_group=\"nob"
  },
  {
    "path": "status_config.json",
    "chars": 537,
    "preview": "{\n    \"api\": {\n        \"tag\": \"api\",\n        \"services\": [\n            \"StatsService\"\n        ]\n    },\n    \"stats\": {},\n"
  },
  {
    "path": "translate.py",
    "chars": 8688,
    "preview": "import re\nimport time\nimport json\nimport os\nfrom openai import OpenAI\nfrom googletrans import Translator\nfrom langdetect"
  },
  {
    "path": "translate_readme.py",
    "chars": 5021,
    "preview": "import os\nimport json\nimport re\nimport markdown\nfrom bs4 import BeautifulSoup\nfrom googletrans import Translator\nimport "
  }
]

// ... and 6 more files (download for full content)

About this extraction

This page contains the full source code of the hello-yunshu/Xray_bash_onekey GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 58 files (740.0 KB), approximately 250.5k tokens, and a symbol index with 18 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!