diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index db90dff..ef05880 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -1,159 +1,181 @@
-name: Docker Image CI
-on:
- push:
- tags:
- - "*"
- workflow_dispatch:
-
-jobs:
- frontend-build:
- runs-on: ubuntu-24.04
- steps:
- - name: Checkout repository
- uses: actions/checkout@v6.0.2
- with:
- submodules: recursive
- - name: Set up Node.js
- uses: actions/setup-node@v6
- with:
- node-version: 25
- - name: Install dependencies and build frontend
- run: |
- cd frontend
- npm install
- npm run build
- - name: Upload frontend build artifact
- uses: actions/upload-artifact@v7
- with:
- name: frontend-dist
- path: frontend/dist/
-
- build:
- needs: frontend-build
- strategy:
- fail-fast: false
- matrix:
- include:
- - { platform: linux/amd64 }
- - { platform: linux/386 }
- - { platform: linux/arm64/v8 }
- - { platform: linux/arm/v7 }
- - { platform: linux/arm/v6 }
- runs-on: ubuntu-24.04
- steps:
- - name: Checkout repository
- uses: actions/checkout@v6.0.2
- - name: Download frontend build artifact
- uses: actions/download-artifact@v8
- with:
- name: frontend-dist
- path: frontend_dist
- - name: Prepare
- run: |
- platform="${{ matrix.platform }}"
- echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- - name: Docker meta
- id: meta
- uses: docker/metadata-action@v6
- with:
- images: |
- alireza7/s-ui
- ghcr.io/alireza0/s-ui
- tags: |
- type=ref,event=branch
- type=ref,event=tag
- type=pep440,pattern={{version}}
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v4
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v4
- - name: Cache Docker layers
- uses: actions/cache@v5
- with:
- path: /tmp/.buildx-cache
- key: ${{ runner.os }}-buildx-${{ matrix.platform }}-${{ github.sha }}
- restore-keys: |
- ${{ runner.os }}-buildx-${{ matrix.platform }}-
- - name: Login to Docker Hub
- uses: docker/login-action@v4
- with:
- username: ${{ secrets.DOCKER_HUB_USERNAME }}
- password: ${{ secrets.DOCKER_HUB_TOKEN }}
- - name: Login to GHCR
- uses: docker/login-action@v4
- with:
- registry: ghcr.io
- username: ${{ github.repository_owner }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Build and push by digest
- id: build
- uses: docker/build-push-action@v7
- with:
- context: .
- file: Dockerfile.frontend-artifact
- platforms: ${{ matrix.platform }}
- labels: ${{ steps.meta.outputs.labels }}
- tags: |
- alireza7/s-ui
- ghcr.io/alireza0/s-ui
- cache-from: type=local,src=/tmp/.buildx-cache
- cache-to: type=local,dest=/tmp/.buildx-cache,mode=max
- outputs: type=image,push-by-digest=true,name-canonical=true,push=true
- - name: Export digest
- run: |
- mkdir -p ${{ runner.temp }}/digests
- digest="${{ steps.build.outputs.digest }}"
- echo "${digest#sha256:}" > "${{ runner.temp }}/digests/${digest#sha256:}"
- - name: Upload digest
- uses: actions/upload-artifact@v7
- with:
- name: digests-${{ env.PLATFORM_PAIR }}
- path: ${{ runner.temp }}/digests/*
- if-no-files-found: error
- retention-days: 1
-
- merge:
- needs: build
- runs-on: ubuntu-24.04
- steps:
- - name: Download digests
- uses: actions/download-artifact@v8
- with:
- path: ${{ runner.temp }}/digests
- pattern: digests-*
- merge-multiple: true
- - name: Login to Docker Hub
- uses: docker/login-action@v4
- with:
- username: ${{ secrets.DOCKER_HUB_USERNAME }}
- password: ${{ secrets.DOCKER_HUB_TOKEN }}
- - name: Login to GHCR
- uses: docker/login-action@v4
- with:
- registry: ghcr.io
- username: ${{ github.repository_owner }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v4
- - name: Docker meta
- id: meta
- uses: docker/metadata-action@v6
- with:
- images: |
- alireza7/s-ui
- ghcr.io/alireza0/s-ui
- tags: |
- type=ref,event=branch
- type=ref,event=tag
- type=pep440,pattern={{version}}
- - name: Create manifest list and push
- env:
- DOCKER_METADATA_OUTPUT_JSON: ${{ steps.meta.outputs.json }}
- working-directory: ${{ runner.temp }}/digests
- run: |
- set -e
- for img in alireza7/s-ui ghcr.io/alireza0/s-ui; do
- TAGS_ARGS=$(echo "$DOCKER_METADATA_OUTPUT_JSON" | jq -cr --arg img "$img" '.tags | map(select(startswith($img))) | map("-t " + .) | join(" ")')
- DIGEST_REFS=$(for f in *; do echo -n "${img}@sha256:$(cat "$f") "; done)
- docker buildx imagetools create $TAGS_ARGS $DIGEST_REFS
- done
+name: Docker 镜像 CI
+
+on:
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: "发布标签"
+ required: true
+ default: "v1.4.1"
+ type: string
+
+jobs:
+ frontend-build:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: 检出仓库
+ uses: actions/checkout@v6.0.2
+ with:
+ ref: ${{ inputs.tag }}
+ submodules: recursive
+
+ - name: 设置 Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: 25
+
+ - name: 安装依赖并构建前端
+ run: |
+ cd frontend
+ npm install
+ npm run build
+
+ - name: 上传前端构建产物
+ uses: actions/upload-artifact@v7
+ with:
+ name: frontend-dist
+ path: frontend/dist/
+
+ build:
+ needs: frontend-build
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - { platform: linux/amd64 }
+ - { platform: linux/386 }
+ - { platform: linux/arm64/v8 }
+ - { platform: linux/arm/v7 }
+ - { platform: linux/arm/v6 }
+ runs-on: ubuntu-24.04
+ steps:
+ - name: 检出仓库
+ uses: actions/checkout@v6.0.2
+ with:
+ ref: ${{ inputs.tag }}
+
+ - name: 下载前端构建产物
+ uses: actions/download-artifact@v8
+ with:
+ name: frontend-dist
+ path: frontend_dist
+
+ - name: 准备
+ run: |
+ platform="${{ matrix.platform }}"
+ echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
+
+ - name: Docker 元数据
+ id: meta
+ uses: docker/metadata-action@v6
+ with:
+ images: |
+ alireza7/s-ui
+ ghcr.io/alireza0/s-ui
+ tags: |
+ type=raw,value=${{ inputs.tag }}
+
+ - name: 设置 QEMU
+ uses: docker/setup-qemu-action@v4
+
+ - name: 设置 Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ - name: 缓存 Docker 层
+ uses: actions/cache@v5
+ with:
+ path: /tmp/.buildx-cache
+ key: ${{ runner.os }}-buildx-${{ matrix.platform }}-${{ github.sha }}
+ restore-keys: |
+ ${{ runner.os }}-buildx-${{ matrix.platform }}-
+
+ - name: 登录 Docker Hub
+ uses: docker/login-action@v4
+ with:
+ username: ${{ secrets.DOCKER_HUB_USERNAME }}
+ password: ${{ secrets.DOCKER_HUB_TOKEN }}
+
+ - name: 登录 GHCR
+ uses: docker/login-action@v4
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: 按摘要构建并推送
+ id: build
+ uses: docker/build-push-action@v7
+ with:
+ context: .
+ file: Dockerfile.frontend-artifact
+ platforms: ${{ matrix.platform }}
+ labels: ${{ steps.meta.outputs.labels }}
+ tags: |
+ alireza7/s-ui
+ ghcr.io/alireza0/s-ui
+ cache-from: type=local,src=/tmp/.buildx-cache
+ cache-to: type=local,dest=/tmp/.buildx-cache,mode=max
+ outputs: type=image,push-by-digest=true,name-canonical=true,push=true
+
+ - name: 导出摘要
+ run: |
+ mkdir -p ${{ runner.temp }}/digests
+ digest="${{ steps.build.outputs.digest }}"
+ echo "${digest#sha256:}" > "${{ runner.temp }}/digests/${digest#sha256:}"
+
+ - name: 上传摘要
+ uses: actions/upload-artifact@v7
+ with:
+ name: digests-${{ env.PLATFORM_PAIR }}
+ path: ${{ runner.temp }}/digests/*
+ if-no-files-found: error
+ retention-days: 1
+
+ merge:
+ needs: build
+ runs-on: ubuntu-24.04
+ steps:
+ - name: 下载摘要
+ uses: actions/download-artifact@v8
+ with:
+ path: ${{ runner.temp }}/digests
+ pattern: digests-*
+ merge-multiple: true
+
+ - name: 登录 Docker Hub
+ uses: docker/login-action@v4
+ with:
+ username: ${{ secrets.DOCKER_HUB_USERNAME }}
+ password: ${{ secrets.DOCKER_HUB_TOKEN }}
+
+ - name: 登录 GHCR
+ uses: docker/login-action@v4
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: 设置 Docker Buildx
+ uses: docker/setup-buildx-action@v4
+
+ - name: Docker 元数据
+ id: meta
+ uses: docker/metadata-action@v6
+ with:
+ images: |
+ alireza7/s-ui
+ ghcr.io/alireza0/s-ui
+ tags: |
+ type=raw,value=${{ inputs.tag }}
+
+ - name: 创建清单列表并推送
+ env:
+ DOCKER_METADATA_OUTPUT_JSON: ${{ steps.meta.outputs.json }}
+ working-directory: ${{ runner.temp }}/digests
+ run: |
+ set -e
+ for img in alireza7/s-ui ghcr.io/alireza0/s-ui; do
+ TAGS_ARGS=$(echo "$DOCKER_METADATA_OUTPUT_JSON" | jq -cr --arg img "$img" '.tags | map(select(startswith($img))) | map("-t " + .) | join(" ")')
+ DIGEST_REFS=$(for f in *; do echo -n "${img}@sha256:$(cat "$f") "; done)
+ docker buildx imagetools create $TAGS_ARGS $DIGEST_REFS
+ done
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 151fb49..36b8017 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,202 +1,193 @@
-name: Release S-UI
-
-on:
- workflow_dispatch:
- release:
- types: [published]
- push:
- branches:
- - main
- tags:
- - "*"
- paths:
- - '.github/workflows/release.yml'
- - 'frontend/**'
- - '**.sh'
- - '**.go'
- - 'go.mod'
- - 'go.sum'
- - 's-ui.service'
-
-env:
- NODE_VERSION: "25"
- CRONET_GO_VERSION: "2fef65f9dba90ddb89a87d00a6eb6165487c10c1"
- CRONET_GO_REPO: https://github.com/sagernet/cronet-go.git
- BOOTLIN_BASE_URL: https://toolchains.bootlin.com/downloads/releases/toolchains
-
-jobs:
- build-frontend:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repository (frontend only)
- uses: actions/checkout@v6.0.2
- with:
- submodules: recursive
- fetch-depth: 1
-
- - name: Setup Node.js
- uses: actions/setup-node@v6
- with:
- node-version: ${{ env.NODE_VERSION }}
- cache: 'npm'
- cache-dependency-path: frontend/package-lock.json
-
- - name: Build frontend
- run: |
- cd frontend
- npm install
- npm run build
- cd ..
-
- - name: Upload frontend dist
- uses: actions/upload-artifact@v7
- with:
- name: frontend-dist
- path: frontend/dist/
-
- build-linux:
- name: build-${{ matrix.platform }}
- needs: build-frontend
- strategy:
- fail-fast: false
- matrix:
- include:
- - { platform: amd64, arch: amd64, bootlin: x86-64, naive: true }
- - { platform: arm64, arch: arm64, bootlin: aarch64, naive: true }
- - { platform: armv7, arch: arm, goarm: "7", bootlin: armv7-eabihf, naive: true }
- - { platform: armv6, arch: arm, goarm: "6", bootlin: armv6-eabihf, naive: true }
- - { platform: armv5, arch: arm, goarm: "5", bootlin: armv5-eabi, naive: false }
- - { platform: "386", arch: "386", bootlin: x86-i686, naive: true }
- - { platform: s390x, arch: s390x, bootlin: s390x-z13, naive: false }
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@v6.0.2
-
- - name: Download frontend dist
- uses: actions/download-artifact@v8
- with:
- name: frontend-dist
- path: web/html
-
- - name: Setup Go
- uses: actions/setup-go@v6
- with:
- cache: false
- go-version-file: go.mod
-
- # Naive platforms: use cronet toolchain only (no Bootlin).
- - name: Clone cronet-go (cronet toolchain for naive)
- if: matrix.naive
- run: |
- set -e
- git init ~/cronet-go
- git -C ~/cronet-go remote add origin ${{ env.CRONET_GO_REPO }}
- git -C ~/cronet-go fetch --depth=1 origin "${{ env.CRONET_GO_VERSION }}"
- git -C ~/cronet-go checkout FETCH_HEAD
- git -C ~/cronet-go submodule update --init --recursive --depth=1
-
- - name: Regenerate Debian keyring (cronet sysroot)
- if: matrix.naive
- run: |
- set -e
- rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
- cd ~/cronet-go
- GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
-
- - name: Cache Chromium toolchain
- if: matrix.naive
- id: cache-chromium-toolchain
- uses: actions/cache@v5
- with:
- path: |
- ~/cronet-go/naiveproxy/src/third_party/llvm-build/
- ~/cronet-go/naiveproxy/src/gn/out/
- ~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
- ~/cronet-go/naiveproxy/src/out/sysroot-build/
- key: chromium-toolchain-${{ matrix.platform }}-musl-${{ env.CRONET_GO_VERSION }}
-
- - name: Build cronet lib and set toolchain env (CC, CXX, CGO_LDFLAGS, PATH)
- if: matrix.naive
- run: |
- set -e
- cd ~/cronet-go
- go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
- go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env | while IFS= read -r line; do
- line="${line#export }"
- [[ -z "$line" ]] && continue
- echo "$line" >> $GITHUB_ENV
- done
-
- - name: Set Go build env (all platforms)
- run: |
- echo "CGO_ENABLED=1" >> $GITHUB_ENV
- echo "GOOS=linux" >> $GITHUB_ENV
- echo "GOARCH=${{ matrix.arch }}" >> $GITHUB_ENV
- if [ -n "${{ matrix.goarm }}" ]; then echo "GOARM=${{ matrix.goarm }}" >> $GITHUB_ENV; fi
-
- # Non-naive platforms only: Bootlin musl (armv5, s390x).
- - name: Set up Bootlin musl (armv5, s390x)
- if: ${{ matrix.naive != true }}
- run: |
- set -e
- BOOTLIN_ARCH="${{ matrix.bootlin }}"
- echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
- TARBALL_BASE="${{ env.BOOTLIN_BASE_URL }}/$BOOTLIN_ARCH/tarballs/"
- TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
- [ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
- echo "Downloading: $TARBALL_URL"
- cd /tmp
- curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
- tar -xf "$(basename "$TARBALL_URL")"
- TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
- TOOLCHAIN_DIR="$(realpath "$TOOLCHAIN_DIR")"
- BIN_DIR="$TOOLCHAIN_DIR/bin"
- echo "PATH=$BIN_DIR:$PATH" >> $GITHUB_ENV
- CC=$(find "$BIN_DIR" -maxdepth 1 \( -name '*-gcc.br_real' -o -name '*-gcc' \) -type f -executable 2>/dev/null | grep -v g++ | head -n1)
- [ -z "$CC" ] && { echo "No gcc found in $BIN_DIR" >&2; exit 1; }
- echo "CC=$(realpath "$CC")" >> $GITHUB_ENV
- SYSROOT=""
- F=$(find "$TOOLCHAIN_DIR" -name "libc-header-start.h" 2>/dev/null | head -1)
- if [ -n "$F" ]; then SYSROOT=$(dirname "$(dirname "$(dirname "$(dirname "$F")")")"); fi
- if [ -n "$SYSROOT" ] && [ -d "$SYSROOT" ]; then
- echo "CGO_CFLAGS=--sysroot=$SYSROOT" >> $GITHUB_ENV
- echo "CGO_LDFLAGS=--sysroot=$SYSROOT -static" >> $GITHUB_ENV
- fi
-
- - name: Build s-ui
- run: |
- set -e
- BUILD_TAGS="with_quic,with_grpc,with_utls,with_acme,with_gvisor,badlinkname,tfogo_checklinkname0,with_tailscale"
- [ "${{ matrix.naive }}" = "true" ] && BUILD_TAGS="${BUILD_TAGS},with_naive_outbound,with_musl"
- go build -ldflags="-w -s -checklinkname=0 -linkmode external -extldflags '-static'" -tags "$BUILD_TAGS" -o sui main.go
- file sui
- ldd sui 2>/dev/null || echo "Static binary confirmed"
-
- mkdir s-ui
- cp sui s-ui/
- cp s-ui.service s-ui/
- cp s-ui.sh s-ui/
-
- - name: Package
- run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui
-
- - name: Upload artifact
- uses: actions/upload-artifact@v7
- with:
- name: s-ui-linux-${{ matrix.platform }}
- path: ./s-ui-linux-${{ matrix.platform }}.tar.gz
- retention-days: 30
-
- - name: Upload to Release
- uses: svenstaro/upload-release-action@v2
- if: |
- (github.event_name == 'release' && github.event.action == 'published') ||
- (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
- with:
- repo_token: ${{ secrets.GITHUB_TOKEN }}
- tag: ${{ github.event_name == 'release' && github.event.release.tag_name || github.ref_name }}
- file: s-ui-linux-${{ matrix.platform }}.tar.gz
- asset_name: s-ui-linux-${{ matrix.platform }}.tar.gz
- prerelease: true
- overwrite: true
+name: 发布 S-UI
+
+on:
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: "发布标签"
+ required: true
+ default: "v1.4.1"
+ type: string
+
+env:
+ NODE_VERSION: "25"
+ CRONET_GO_VERSION: "2fef65f9dba90ddb89a87d00a6eb6165487c10c1"
+ CRONET_GO_REPO: https://github.com/sagernet/cronet-go.git
+ BOOTLIN_BASE_URL: https://toolchains.bootlin.com/downloads/releases/toolchains
+
+jobs:
+ build-frontend:
+ runs-on: ubuntu-latest
+ steps:
+ - name: 检出仓库(仅前端)
+ uses: actions/checkout@v6.0.2
+ with:
+ ref: ${{ inputs.tag }}
+ submodules: recursive
+ fetch-depth: 1
+
+ - name: 设置 Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'npm'
+ cache-dependency-path: frontend/package-lock.json
+
+ - name: 构建前端
+ run: |
+ cd frontend
+ npm install
+ npm run build
+ cd ..
+
+ - name: 上传前端 dist
+ uses: actions/upload-artifact@v7
+ with:
+ name: frontend-dist
+ path: frontend/dist/
+
+ build-linux:
+ name: 构建-${{ matrix.platform }}
+ needs: build-frontend
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - { platform: amd64, arch: amd64, bootlin: x86-64, naive: true }
+ - { platform: arm64, arch: arm64, bootlin: aarch64, naive: true }
+ - { platform: armv7, arch: arm, goarm: "7", bootlin: armv7-eabihf, naive: true }
+ - { platform: armv6, arch: arm, goarm: "6", bootlin: armv6-eabihf, naive: true }
+ - { platform: armv5, arch: arm, goarm: "5", bootlin: armv5-eabi, naive: false }
+ - { platform: "386", arch: "386", bootlin: x86-i686, naive: true }
+ - { platform: s390x, arch: s390x, bootlin: s390x-z13, naive: false }
+ runs-on: ubuntu-latest
+ steps:
+ - name: 检出仓库
+ uses: actions/checkout@v6.0.2
+ with:
+ ref: ${{ inputs.tag }}
+
+ - name: 下载前端 dist
+ uses: actions/download-artifact@v8
+ with:
+ name: frontend-dist
+ path: web/html
+
+ - name: 设置 Go
+ uses: actions/setup-go@v6
+ with:
+ cache: false
+ go-version-file: go.mod
+
+ # Naive 平台:仅使用 cronet 工具链(不使用 Bootlin)。
+ - name: 克隆 cronet-go(naive 使用的 cronet 工具链)
+ if: matrix.naive
+ run: |
+ set -e
+ git init ~/cronet-go
+ git -C ~/cronet-go remote add origin ${{ env.CRONET_GO_REPO }}
+ git -C ~/cronet-go fetch --depth=1 origin "${{ env.CRONET_GO_VERSION }}"
+ git -C ~/cronet-go checkout FETCH_HEAD
+ git -C ~/cronet-go submodule update --init --recursive --depth=1
+
+ - name: 重新生成 Debian keyring(cronet sysroot)
+ if: matrix.naive
+ run: |
+ set -e
+ rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
+ cd ~/cronet-go
+ GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
+
+ - name: 缓存 Chromium 工具链
+ if: matrix.naive
+ id: cache-chromium-toolchain
+ uses: actions/cache@v5
+ with:
+ path: |
+ ~/cronet-go/naiveproxy/src/third_party/llvm-build/
+ ~/cronet-go/naiveproxy/src/gn/out/
+ ~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
+ ~/cronet-go/naiveproxy/src/out/sysroot-build/
+ key: chromium-toolchain-${{ matrix.platform }}-musl-${{ env.CRONET_GO_VERSION }}
+
+ - name: 构建 cronet 库并设置工具链环境(CC、CXX、CGO_LDFLAGS、PATH)
+ if: matrix.naive
+ run: |
+ set -e
+ cd ~/cronet-go
+ go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
+ go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env | while IFS= read -r line; do
+ line="${line#export }"
+ [[ -z "$line" ]] && continue
+ echo "$line" >> $GITHUB_ENV
+ done
+
+ - name: 设置 Go 构建环境(所有平台)
+ run: |
+ echo "CGO_ENABLED=1" >> $GITHUB_ENV
+ echo "GOOS=linux" >> $GITHUB_ENV
+ echo "GOARCH=${{ matrix.arch }}" >> $GITHUB_ENV
+ if [ -n "${{ matrix.goarm }}" ]; then echo "GOARM=${{ matrix.goarm }}" >> $GITHUB_ENV; fi
+
+ # 仅非 naive 平台:Bootlin musl(armv5、s390x)。
+ - name: 设置 Bootlin musl(armv5、s390x)
+ if: ${{ matrix.naive != true }}
+ run: |
+ set -e
+ BOOTLIN_ARCH="${{ matrix.bootlin }}"
+ echo "正在解析 arch=$BOOTLIN_ARCH 的 Bootlin musl 工具链(平台=${{ matrix.platform }})"
+ TARBALL_BASE="${{ env.BOOTLIN_BASE_URL }}/$BOOTLIN_ARCH/tarballs/"
+ TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
+ [ -z "$TARBALL_URL" ] && { echo "未找到 arch=$BOOTLIN_ARCH 的 Bootlin musl 工具链" >&2; exit 1; }
+ echo "正在下载:$TARBALL_URL"
+ cd /tmp
+ curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
+ tar -xf "$(basename "$TARBALL_URL")"
+ TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
+ TOOLCHAIN_DIR="$(realpath "$TOOLCHAIN_DIR")"
+ BIN_DIR="$TOOLCHAIN_DIR/bin"
+ echo "PATH=$BIN_DIR:$PATH" >> $GITHUB_ENV
+ CC=$(find "$BIN_DIR" -maxdepth 1 \( -name '*-gcc.br_real' -o -name '*-gcc' \) -type f -executable 2>/dev/null | grep -v g++ | head -n1)
+ [ -z "$CC" ] && { echo "在 $BIN_DIR 中未找到 gcc" >&2; exit 1; }
+ echo "CC=$(realpath "$CC")" >> $GITHUB_ENV
+ SYSROOT=""
+ F=$(find "$TOOLCHAIN_DIR" -name "libc-header-start.h" 2>/dev/null | head -1)
+ if [ -n "$F" ]; then SYSROOT=$(dirname "$(dirname "$(dirname "$(dirname "$F")")")"); fi
+ if [ -n "$SYSROOT" ] && [ -d "$SYSROOT" ]; then
+ echo "CGO_CFLAGS=--sysroot=$SYSROOT" >> $GITHUB_ENV
+ echo "CGO_LDFLAGS=--sysroot=$SYSROOT -static" >> $GITHUB_ENV
+ fi
+
+ - name: 构建 s-ui
+ run: |
+ set -e
+ BUILD_TAGS="with_quic,with_grpc,with_utls,with_acme,with_gvisor,badlinkname,tfogo_checklinkname0,with_tailscale"
+ [ "${{ matrix.naive }}" = "true" ] && BUILD_TAGS="${BUILD_TAGS},with_naive_outbound,with_musl"
+ go build -ldflags="-w -s -checklinkname=0 -linkmode external -extldflags '-static'" -tags "$BUILD_TAGS" -o sui main.go
+ file sui
+ ldd sui 2>/dev/null || echo "已确认静态二进制文件"
+
+ mkdir s-ui
+ cp sui s-ui/
+ cp s-ui.service s-ui/
+ cp s-ui.sh s-ui/
+
+ - name: 打包
+ run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui
+
+ - name: 上传构建产物
+ uses: actions/upload-artifact@v7
+ with:
+ name: s-ui-linux-${{ matrix.platform }}
+ path: ./s-ui-linux-${{ matrix.platform }}.tar.gz
+ retention-days: 30
+
+ - name: 上传到 Release
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ tag: ${{ inputs.tag }}
+ file: s-ui-linux-${{ matrix.platform }}.tar.gz
+ asset_name: s-ui-linux-${{ matrix.platform }}.tar.gz
+ prerelease: true
+ overwrite: true
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 4aaca53..e189281 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -1,140 +1,132 @@
-name: Build S-UI for Windows
-
-on:
- workflow_dispatch:
- release:
- types: [published]
- push:
- branches:
- - main
- tags:
- - "*"
- paths:
- - '.github/workflows/windows.yml'
- - 'frontend/**'
- - '**.go'
- - 'go.mod'
- - 'go.sum'
- - 'windows/**'
-
-env:
- NODE_VERSION: "25"
- TAGS: "with_quic,with_grpc,with_utls,with_acme,with_gvisor,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0,with_tailscale"
- LIBCRONET_BASE_URL: "https://github.com/SagerNet/cronet-go/releases/latest/download"
-
-jobs:
- build-frontend:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@v6.0.2
- with:
- submodules: recursive
- fetch-depth: 1
-
- - name: Setup Node.js
- uses: actions/setup-node@v6
- with:
- node-version: ${{ env.NODE_VERSION }}
- registry-url: 'https://registry.npmjs.org'
-
- - name: Build frontend
- run: |
- cd frontend
- npm install
- npm run build
- cd ..
-
- - name: Upload frontend artifact
- uses: actions/upload-artifact@v7
- with:
- name: frontend-dist
- path: frontend/dist
- retention-days: 1
-
- build-windows:
- needs: build-frontend
- name: build-windows-${{ matrix.arch }}
- strategy:
- fail-fast: false
- matrix:
- include:
- - { arch: amd64, runner: windows-latest, cgo: "1" }
- - { arch: arm64, runner: ubuntu-latest, cgo: "0" }
- runs-on: ${{ matrix.runner }}
- steps:
- - name: Checkout repository
- uses: actions/checkout@v6.0.2
-
- - name: Download frontend artifact
- uses: actions/download-artifact@v8
- with:
- name: frontend-dist
- path: web/html
-
- - name: Setup Go
- uses: actions/setup-go@v6
- with:
- cache: false
- go-version-file: go.mod
-
- - name: Install zip for Windows
- if: matrix.arch == 'amd64'
- shell: powershell
- run: |
- # Install Chocolatey if not available
- if (!(Get-Command choco -ErrorAction SilentlyContinue)) {
- Set-ExecutionPolicy Bypass -Scope Process -Force
- [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
- iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
- }
- # Install zip
- choco install zip -y
-
- - name: Build s-ui
- shell: bash
- run: |
- export CGO_ENABLED=${{ matrix.cgo }}
- export GOOS=windows
- export GOARCH=${{ matrix.arch }}
-
- echo "Building for Windows ${{ matrix.arch }}"
- go version
- go env GOOS GOARCH
-
- go build -ldflags="-w -s -checklinkname=0" -tags "${{ env.TAGS }}" -o sui.exe main.go
- file sui.exe
-
- mkdir s-ui-windows
- cp sui.exe s-ui-windows/
- cp -r windows/* s-ui-windows/
-
- - name: Download libcronet-go
- shell: bash
- run: |
- curl -qsL -o s-ui-windows/libcronet.dll ${{ env.LIBCRONET_BASE_URL }}/libcronet-windows-${{ matrix.arch }}.dll
-
- - name: Package
- shell: bash
- run: |
- zip -r "s-ui-windows-${{ matrix.arch }}.zip" s-ui-windows
-
- - name: Upload files to Artifacts
- uses: actions/upload-artifact@v7
- with:
- name: s-ui-windows-${{ matrix.arch }}
- path: ./s-ui-windows-${{ matrix.arch }}.zip
- retention-days: 30
-
- - name: Upload to Release
- uses: svenstaro/upload-release-action@v2
- if: |
- (github.event_name == 'release' && github.event.action == 'published') ||
- (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
- with:
- repo_token: ${{ secrets.GITHUB_TOKEN }}
- tag: ${{ github.ref }}
- file: s-ui-windows-${{ matrix.arch }}.zip
- asset_name: s-ui-windows-${{ matrix.arch }}.zip
- prerelease: true
- overwrite: true
+name: 构建 Windows 版 S-UI
+
+on:
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: "发布标签"
+ required: true
+ default: "v1.4.1"
+ type: string
+
+env:
+ NODE_VERSION: "25"
+ TAGS: "with_quic,with_grpc,with_utls,with_acme,with_gvisor,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0,with_tailscale"
+ LIBCRONET_BASE_URL: "https://github.com/SagerNet/cronet-go/releases/latest/download"
+
+jobs:
+ build-frontend:
+ runs-on: ubuntu-latest
+ steps:
+ - name: 检出仓库
+ uses: actions/checkout@v6.0.2
+ with:
+ ref: ${{ inputs.tag }}
+ submodules: recursive
+ fetch-depth: 1
+
+ - name: 设置 Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ registry-url: 'https://registry.npmjs.org'
+
+ - name: 构建前端
+ run: |
+ cd frontend
+ npm install
+ npm run build
+ cd ..
+
+ - name: 上传前端构建产物
+ uses: actions/upload-artifact@v7
+ with:
+ name: frontend-dist
+ path: frontend/dist
+ retention-days: 1
+
+ build-windows:
+ needs: build-frontend
+ name: 构建-windows-${{ matrix.arch }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - { arch: amd64, runner: windows-latest, cgo: "1" }
+ - { arch: arm64, runner: ubuntu-latest, cgo: "0" }
+ runs-on: ${{ matrix.runner }}
+ steps:
+ - name: 检出仓库
+ uses: actions/checkout@v6.0.2
+ with:
+ ref: ${{ inputs.tag }}
+
+ - name: 下载前端构建产物
+ uses: actions/download-artifact@v8
+ with:
+ name: frontend-dist
+ path: web/html
+
+ - name: 设置 Go
+ uses: actions/setup-go@v6
+ with:
+ cache: false
+ go-version-file: go.mod
+
+ - name: 为 Windows 安装 zip
+ if: matrix.arch == 'amd64'
+ shell: powershell
+ run: |
+ # 如果 Chocolatey 不可用,则安装 Chocolatey
+ if (!(Get-Command choco -ErrorAction SilentlyContinue)) {
+ Set-ExecutionPolicy Bypass -Scope Process -Force
+ [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
+ iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
+ }
+ # 安装 zip
+ choco install zip -y
+
+ - name: 构建 s-ui
+ shell: bash
+ run: |
+ export CGO_ENABLED=${{ matrix.cgo }}
+ export GOOS=windows
+ export GOARCH=${{ matrix.arch }}
+
+ echo "正在为 Windows ${{ matrix.arch }} 构建"
+ go version
+ go env GOOS GOARCH
+
+ go build -ldflags="-w -s -checklinkname=0" -tags "${{ env.TAGS }}" -o sui.exe main.go
+ file sui.exe
+
+ mkdir s-ui-windows
+ cp sui.exe s-ui-windows/
+ cp -r windows/* s-ui-windows/
+
+ - name: 下载 libcronet-go
+ shell: bash
+ run: |
+ curl -qsL -o s-ui-windows/libcronet.dll ${{ env.LIBCRONET_BASE_URL }}/libcronet-windows-${{ matrix.arch }}.dll
+
+ - name: 打包
+ shell: bash
+ run: |
+ zip -r "s-ui-windows-${{ matrix.arch }}.zip" s-ui-windows
+
+ - name: 上传文件到构建产物
+ uses: actions/upload-artifact@v7
+ with:
+ name: s-ui-windows-${{ matrix.arch }}
+ path: ./s-ui-windows-${{ matrix.arch }}.zip
+ retention-days: 30
+
+ - name: 上传到 Release
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ tag: ${{ inputs.tag }}
+ file: s-ui-windows-${{ matrix.arch }}.zip
+ asset_name: s-ui-windows-${{ matrix.arch }}.zip
+ prerelease: true
+ overwrite: true
diff --git a/README.md b/README.md
index dfe78b5..1164b8b 100644
--- a/README.md
+++ b/README.md
@@ -1,259 +1,239 @@
-# S-UI
-**An Advanced Web Panel • Built on SagerNet/Sing-Box**
-
-
-
-[](https://goreportcard.com/report/github.com/admin8800/s-ui)
-[](https://img.shields.io/github/downloads/alireza0/s-ui/total.svg)
-[](https://www.gnu.org/licenses/gpl-3.0.en.html)
-
-> **Disclaimer:** This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment
-
-**If you think this project is helpful to you, you may wish to give a**:star2:
-
-**Want to contribute?** See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding conventions, testing, and the pull request process.
-
-[](https://www.buymeacoffee.com/alireza7)
-
-
-
-
-
-## Quick Overview
-| Features | Enable? |
-| -------------------------------------- | :----------------: |
-| Multi-Protocol | :heavy_check_mark: |
-| Multi-Language | :heavy_check_mark: |
-| Multi-Client/Inbound | :heavy_check_mark: |
-| Advanced Traffic Routing Interface | :heavy_check_mark: |
-| Client & Traffic & System Status | :heavy_check_mark: |
-| Subscription Link (link/json/clash + info)| :heavy_check_mark: |
-| Dark/Light Theme | :heavy_check_mark: |
-| API Interface | :heavy_check_mark: |
-
-## Supported Platforms
-| Platform | Architecture | Status |
-|----------|--------------|---------|
-| Linux | amd64, arm64, armv7, armv6, armv5, 386, s390x | ✅ Supported |
-| Windows | amd64, 386, arm64 | ✅ Supported |
-| macOS | amd64, arm64 | 🚧 Experimental |
-
-## Screenshots
-
-
-
-[Other UI Screenshots](https://github.com/admin8800/s-ui-frontend/blob/main/screenshots.md)
-
-## API Documentation
-
-[API-Documentation Wiki](https://github.com/admin8800/s-ui/wiki/API-Documentation)
-
-## Default Installation Information
-- Panel Port: 2095
-- Panel Path: /app/
-- Subscription Port: 2096
-- Subscription Path: /sub/
-- User/Password: admin
-
-## Install & Upgrade to Latest Version
-
-### Linux/macOS
-```sh
-bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh)
-```
-
-### Windows
-1. Download the latest Windows release from [GitHub Releases](https://github.com/admin8800/s-ui/releases/latest)
-2. Extract the ZIP file
-3. Run `install-windows.bat` as Administrator
-4. Follow the installation wizard
-
-## Install legacy Version
-
-**Step 1:** To install your desired legacy version, add the version to the end of the installation command. e.g., ver `1.0.0`:
-
-```sh
-VERSION=1.0.0 && bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/$VERSION/install.sh) $VERSION
-```
-
-## Manual installation
-
-### Linux/macOS
-1. Get the latest version of S-UI based on your OS/Architecture from GitHub: [https://github.com/admin8800/s-ui/releases/latest](https://github.com/admin8800/s-ui/releases/latest)
-2. **OPTIONAL** Get the latest version of `s-ui.sh` [https://raw.githubusercontent.com/alireza0/s-ui/master/s-ui.sh](https://raw.githubusercontent.com/alireza0/s-ui/master/s-ui.sh)
-3. **OPTIONAL** Copy `s-ui.sh` to /usr/bin/ and run `chmod +x /usr/bin/s-ui`.
-4. Extract s-ui tar.gz file to a directory of your choice and navigate to the directory where you extracted the tar.gz file.
-5. Copy *.service files to /etc/systemd/system/ and run `systemctl daemon-reload`.
-6. Enable autostart and start S-UI service using `systemctl enable s-ui --now`
-7. Start sing-box service using `systemctl enable sing-box --now`
-
-### Windows
-1. Get the latest Windows version from GitHub: [https://github.com/admin8800/s-ui/releases/latest](https://github.com/admin8800/s-ui/releases/latest)
-2. Download the appropriate Windows package (e.g., `s-ui-windows-amd64.zip`)
-3. Extract the ZIP file to a directory of your choice
-4. Run `install-windows.bat` as Administrator
-5. Follow the installation wizard
-6. Access the panel at http://localhost:2095/app
-
-## Uninstall S-UI
-
-```sh
-sudo -i
-
-systemctl disable s-ui --now
-
-rm -f /etc/systemd/system/sing-box.service
-systemctl daemon-reload
-
-rm -fr /usr/local/s-ui
-rm /usr/bin/s-ui
-```
-
-## Install using Docker
-
-
- Click for details
-
-### Usage
-
-**Step 1:** Install Docker
-
-```shell
-curl -fsSL https://get.docker.com | sh
-```
-
-**Step 2:** Install S-UI
-
-> Docker compose method
-
-```shell
-mkdir s-ui && cd s-ui
-wget -q https://raw.githubusercontent.com/alireza0/s-ui/master/docker-compose.yml
-docker compose up -d
-```
-
-> Use docker
-
-```shell
-mkdir s-ui && cd s-ui
-docker run -itd \
- -p 2095:2095 -p 2096:2096 -p 443:443 -p 80:80 \
- -v $PWD/db/:/app/db/ \
- -v $PWD/cert/:/root/cert/ \
- --name s-ui --restart=unless-stopped \
- alireza7/s-ui:latest
-```
-
-> Build your own image
-
-```shell
-git clone https://github.com/admin8800/s-ui
-git submodule update --init --recursive
-docker build -t s-ui .
-```
-
-
-
-## Manual run ( contribution )
-
-
- Click for details
-
-### Build and run whole project
-```shell
-./runSUI.sh
-```
-
-### Clone the repository
-```shell
-# clone repository
-git clone https://github.com/admin8800/s-ui
-# clone submodules
-git submodule update --init --recursive
-```
-
-
-### - Frontend
-
-Visit [s-ui-frontend](https://github.com/admin8800/s-ui-frontend) for frontend code
-
-### - Backend
-> Please build frontend once before!
-
-To build backend:
-```shell
-# remove old frontend compiled files
-rm -fr web/html/*
-# apply new frontend compiled files
-cp -R frontend/dist/ web/html/
-# build
-go build -o sui main.go
-```
-
-To run backend (from root folder of repository):
-```shell
-./sui
-```
-
-
-
-## Languages
-
-- English
-- Farsi
-- Vietnamese
-- Chinese (Simplified)
-- Chinese (Traditional)
-- Russian
-
-## Features
-
-- Supported protocols:
- - General: Mixed, SOCKS, HTTP, HTTPS, Direct, Redirect, TProxy
- - V2Ray based: VLESS, VMess, Trojan, Shadowsocks
- - Other protocols: ShadowTLS, Hysteria, Hysteria2, Naive, TUIC
-- Supports XTLS protocols
-- An advanced interface for routing traffic, incorporating PROXY Protocol, External, and Transparent Proxy, SSL Certificate, and Port
-- An advanced interface for inbound and outbound configuration
-- Clients’ traffic cap and expiration date
-- Displays online clients, inbounds and outbounds with traffic statistics, and system status monitoring
-- Subscription service with ability to add external links and subscription
-- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate)
-- Dark/Light theme
-
-## Environment Variables
-
-
- Click for details
-
-### Usage
-
-| Variable | Type | Default |
-| -------------- | :--------------------------------------------: | :------------ |
-| SUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
-| SUI_DEBUG | `boolean` | `false` |
-| SUI_BIN_FOLDER | `string` | `"bin"` |
-| SUI_DB_FOLDER | `string` | `"db"` |
-| SINGBOX_API | `string` | - |
-
-
-
-## SSL Certificate
-
-
- Click for details
-
-### Certbot
-
-```bash
-snap install core; snap refresh core
-snap install --classic certbot
-ln -s /snap/bin/certbot /usr/bin/certbot
-
-certbot certonly --standalone --register-unsafely-without-email --non-interactive --agree-tos -d
-```
-
-
-
-## Stargazers over Time
-[](https://starchart.cc/alireza0/s-ui)
+# S-UI
+**基于 SagerNet/Sing-Box 构建的高级 Web 面板**
+
+
+> **免责声明:** 本项目仅供个人学习与交流使用,请勿用于非法用途,请勿在生产环境中使用。
+
+
+## 快速概览
+| 功能 | 是否支持 |
+| -------------------------------------- | :----------------: |
+| 多协议 | :heavy_check_mark: |
+| 多语言 | :heavy_check_mark: |
+| 多客户端/入站 | :heavy_check_mark: |
+| 高级流量路由界面 | :heavy_check_mark: |
+| 客户端、流量与系统状态 | :heavy_check_mark: |
+| 订阅链接(link/json/clash + info) | :heavy_check_mark: |
+| 深色/浅色主题 | :heavy_check_mark: |
+| API 接口 | :heavy_check_mark: |
+
+## 支持平台
+| 平台 | 架构 | 状态 |
+|----------|--------------|---------|
+| Linux | amd64, arm64, armv7, armv6, armv5, 386, s390x | 支持 |
+| Windows | amd64, 386, arm64 | 支持 |
+| macOS | amd64, arm64 | 实验性支持 |
+
+## 截图
+
+
+
+## API 文档
+
+[API 文档 Wiki](https://github.com/admin8800/s-ui/wiki/API-Documentation)
+
+## 默认安装信息
+- 面板端口:2095
+- 面板路径:/app/
+- 订阅端口:2096
+- 订阅路径:/sub/
+- 用户名/密码:admin
+
+## 安装或升级到最新版本
+
+### Linux/macOS
+```sh
+bash <(curl -Ls https://raw.githubusercontent.com/admin8800/s-ui/main/install.sh)
+```
+
+### Windows
+1. 从 [GitHub Releases](https://github.com/admin8800/s-ui/releases/latest) 下载最新 Windows 版本。
+2. 解压 ZIP 文件。
+3. 以管理员身份运行 `install-windows.bat`。
+4. 按照安装向导操作。
+
+## 安装旧版本
+
+**步骤 1:** 如果要安装指定旧版本,请在安装命令末尾追加版本号。例如版本 `1.0.0`:
+
+```sh
+VERSION=1.0.0 && bash <(curl -Ls https://raw.githubusercontent.com/admin8800/s-ui/$VERSION/install.sh) $VERSION
+```
+
+## 手动安装
+
+### Linux/macOS
+1. 根据你的系统和架构,从 GitHub 获取最新版本 S-UI:[https://github.com/admin8800/s-ui/releases/latest](https://github.com/admin8800/s-ui/releases/latest)
+2. **可选:** 获取最新版 `s-ui.sh`:[https://raw.githubusercontent.com/admin8800/s-ui/main/s-ui.sh](https://raw.githubusercontent.com/admin8800/s-ui/main/s-ui.sh)
+3. **可选:** 将 `s-ui.sh` 复制到 `/usr/bin/`,并执行 `chmod +x /usr/bin/s-ui`。
+4. 将 s-ui tar.gz 文件解压到你选择的目录,并进入解压后的目录。
+5. 将 `*.service` 文件复制到 `/etc/systemd/system/`,然后执行 `systemctl daemon-reload`。
+6. 使用 `systemctl enable s-ui --now` 启用开机自启并启动 S-UI 服务。
+7. 使用 `systemctl enable sing-box --now` 启动 sing-box 服务。
+
+### Windows
+1. 从 GitHub 获取最新 Windows 版本:[https://github.com/admin8800/s-ui/releases/latest](https://github.com/admin8800/s-ui/releases/latest)
+2. 下载适合的 Windows 包,例如 `s-ui-windows-amd64.zip`。
+3. 将 ZIP 文件解压到你选择的目录。
+4. 以管理员身份运行 `install-windows.bat`。
+5. 按照安装向导操作。
+6. 访问面板:http://localhost:2095/app
+
+## 卸载 S-UI
+
+```sh
+sudo -i
+
+systemctl disable s-ui --now
+
+rm -f /etc/systemd/system/sing-box.service
+systemctl daemon-reload
+
+rm -fr /usr/local/s-ui
+rm /usr/bin/s-ui
+```
+
+## 使用 Docker 安装
+
+
+ 点击查看详情
+
+### 使用方式
+
+**步骤 1:** 安装 Docker
+
+```shell
+curl -fsSL https://get.docker.com | sh
+```
+
+**步骤 2:** 安装 S-UI
+
+> Docker compose 方式
+
+```shell
+mkdir s-ui && cd s-ui
+wget -q https://raw.githubusercontent.com/admin8800/s-ui/main/docker-compose.yml
+docker compose up -d
+```
+
+> 直接使用 docker
+
+```shell
+mkdir s-ui && cd s-ui
+docker run -itd \
+ -p 2095:2095 -p 2096:2096 -p 443:443 -p 80:80 \
+ -v $PWD/db/:/app/db/ \
+ -v $PWD/cert/:/root/cert/ \
+ --name s-ui --restart=unless-stopped \
+ alireza7/s-ui:latest
+```
+
+> 自行构建镜像
+
+```shell
+git clone https://github.com/admin8800/s-ui
+git submodule update --init --recursive
+docker build -t s-ui .
+```
+
+
+
+## 手动运行(贡献开发)
+
+
+ 点击查看详情
+
+### 构建并运行完整项目
+```shell
+./runSUI.sh
+```
+
+### 克隆仓库
+```shell
+# 克隆仓库
+git clone https://github.com/admin8800/s-ui
+# 克隆子模块
+git submodule update --init --recursive
+```
+
+### - 前端
+
+前端代码请查看 [s-ui-frontend](https://github.com/admin8800/s-ui-frontend)。
+
+### - 后端
+> 请先至少构建一次前端。
+
+构建后端:
+```shell
+# 删除旧的前端编译文件
+rm -fr web/html/*
+# 应用新的前端编译文件
+cp -R frontend/dist/ web/html/
+# 构建
+go build -o sui main.go
+```
+
+运行后端(在仓库根目录执行):
+```shell
+./sui
+```
+
+
+
+## 语言
+
+- 英语
+- 波斯语
+- 越南语
+- 简体中文
+- 繁体中文
+- 俄语
+
+## 功能
+
+- 支持的协议:
+ - 通用协议:Mixed、SOCKS、HTTP、HTTPS、Direct、Redirect、TProxy
+ - 基于 V2Ray 的协议:VLESS、VMess、Trojan、Shadowsocks
+ - 其他协议:ShadowTLS、Hysteria、Hysteria2、Naive、TUIC
+- 支持 XTLS 协议。
+- 提供高级流量路由界面,支持 PROXY Protocol、External、透明代理、SSL 证书和端口配置。
+- 提供高级入站和出站配置界面。
+- 支持客户端流量上限和到期时间。
+- 显示在线客户端、入站、出站流量统计和系统状态监控。
+- 订阅服务支持添加外部链接和订阅。
+- Web 面板和订阅服务支持 HTTPS 安全访问(需自行提供域名和 SSL 证书)。
+- 深色/浅色主题。
+
+## 环境变量
+
+
+ 点击查看详情
+
+### 使用方式
+
+| 变量 | 类型 | 默认值 |
+| -------------- | :--------------------------------------------: | :------------ |
+| SUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
+| SUI_DEBUG | `boolean` | `false` |
+| SUI_BIN_FOLDER | `string` | `"bin"` |
+| SUI_DB_FOLDER | `string` | `"db"` |
+| SINGBOX_API | `string` | - |
+
+
+
+## SSL 证书
+
+
+ 点击查看详情
+
+### Certbot
+
+```bash
+snap install core; snap refresh core
+snap install --classic certbot
+ln -s /snap/bin/certbot /usr/bin/certbot
+
+certbot certonly --standalone --register-unsafely-without-email --non-interactive --agree-tos -d <你的域名>
+```
+
+
diff --git a/docker-compose.yml b/docker-compose.yml
index 04e7582..a0fa863 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,7 +1,6 @@
----
services:
s-ui:
- image: alireza7/s-ui
+ image: admin8800/s-ui
container_name: s-ui
hostname: "s-ui"
volumes:
@@ -15,8 +14,4 @@ services:
networks:
- s-ui
entrypoint: "./entrypoint.sh"
-
-networks:
- s-ui:
- driver: bridge
\ No newline at end of file
diff --git a/install.sh b/install.sh
index 3d2a7a3..efc1d8f 100644
--- a/install.sh
+++ b/install.sh
@@ -1,188 +1,188 @@
-#!/bin/bash
-
-red='\033[0;31m'
-green='\033[0;32m'
-yellow='\033[0;33m'
-plain='\033[0m'
-
-cur_dir=$(pwd)
-
-# check root
-[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
-
-# Check OS and set release variable
-if [[ -f /etc/os-release ]]; then
- source /etc/os-release
- release=$ID
-elif [[ -f /usr/lib/os-release ]]; then
- source /usr/lib/os-release
- release=$ID
-else
- echo "Failed to check the system OS, please contact the author!" >&2
- exit 1
-fi
-echo "The OS release is: $release"
-
-arch() {
- case "$(uname -m)" in
- x86_64 | x64 | amd64) echo 'amd64' ;;
- i*86 | x86) echo '386' ;;
- armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
- armv7* | armv7 | arm) echo 'armv7' ;;
- armv6* | armv6) echo 'armv6' ;;
- armv5* | armv5) echo 'armv5' ;;
- s390x) echo 's390x' ;;
- *) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
- esac
-}
-
-echo "arch: $(arch)"
-
-install_base() {
- case "${release}" in
- centos | almalinux | rocky | oracle)
- yum -y update && yum install -y -q wget curl tar tzdata
- ;;
- fedora)
- dnf -y update && dnf install -y -q wget curl tar tzdata
- ;;
- arch | manjaro | parch)
- pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
- ;;
- opensuse-tumbleweed)
- zypper refresh && zypper -q install -y wget curl tar timezone
- ;;
- *)
- apt-get update && apt-get install -y -q wget curl tar tzdata
- ;;
- esac
-}
-
-config_after_install() {
- echo -e "${yellow}Migration... ${plain}"
- /usr/local/s-ui/sui migrate
-
- echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
- read -p "Do you want to continue with the modification [y/n]? ": config_confirm
- if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
- echo -e "Enter the ${yellow}panel port${plain} (leave blank for existing/default value):"
- read config_port
- echo -e "Enter the ${yellow}panel path${plain} (leave blank for existing/default value):"
- read config_path
-
- # Sub configuration
- echo -e "Enter the ${yellow}subscription port${plain} (leave blank for existing/default value):"
- read config_subPort
- echo -e "Enter the ${yellow}subscription path${plain} (leave blank for existing/default value):"
- read config_subPath
-
- # Set configs
- echo -e "${yellow}Initializing, please wait...${plain}"
- params=""
- [ -z "$config_port" ] || params="$params -port $config_port"
- [ -z "$config_path" ] || params="$params -path $config_path"
- [ -z "$config_subPort" ] || params="$params -subPort $config_subPort"
- [ -z "$config_subPath" ] || params="$params -subPath $config_subPath"
- /usr/local/s-ui/sui setting ${params}
-
- read -p "Do you want to change admin credentials [y/n]? ": admin_confirm
- if [[ "${admin_confirm}" == "y" || "${admin_confirm}" == "Y" ]]; then
- # First admin credentials
- read -p "Please set up your username:" config_account
- read -p "Please set up your password:" config_password
-
- # Set credentials
- echo -e "${yellow}Initializing, please wait...${plain}"
- /usr/local/s-ui/sui admin -username ${config_account} -password ${config_password}
- else
- echo -e "${yellow}Your current admin credentials: ${plain}"
- /usr/local/s-ui/sui admin -show
- fi
- else
- echo -e "${red}cancel...${plain}"
- if [[ ! -f "/usr/local/s-ui/db/s-ui.db" ]]; then
- local usernameTemp=$(head -c 6 /dev/urandom | base64)
- local passwordTemp=$(head -c 6 /dev/urandom | base64)
- echo -e "this is a fresh installation,will generate random login info for security concerns:"
- echo -e "###############################################"
- echo -e "${green}username:${usernameTemp}${plain}"
- echo -e "${green}password:${passwordTemp}${plain}"
- echo -e "###############################################"
- echo -e "${red}if you forgot your login info,you can type ${green}s-ui${red} for configuration menu${plain}"
- /usr/local/s-ui/sui admin -username ${usernameTemp} -password ${passwordTemp}
- else
- echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type ${green}s-ui${red} for configuration menu${plain}"
- fi
- fi
-}
-
-prepare_services() {
- if [[ -f "/etc/systemd/system/sing-box.service" ]]; then
- echo -e "${yellow}Stopping sing-box service... ${plain}"
- systemctl stop sing-box
- rm -f /usr/local/s-ui/bin/sing-box /usr/local/s-ui/bin/runSingbox.sh /usr/local/s-ui/bin/signal
- fi
- if [[ -e "/usr/local/s-ui/bin" ]]; then
- echo -e "###############################################################"
- echo -e "${green}/usr/local/s-ui/bin${red} directory exists yet!"
- echo -e "Please check the content and delete it manually after migration ${plain}"
- echo -e "###############################################################"
- fi
- systemctl daemon-reload
-}
-
-install_s-ui() {
- cd /tmp/
-
- if [ $# == 0 ]; then
- last_version=$(curl -Ls "https://api.github.com/repos/alireza0/s-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
- if [[ ! -n "$last_version" ]]; then
- echo -e "${red}Failed to fetch s-ui version, it maybe due to Github API restrictions, please try it later${plain}"
- exit 1
- fi
- echo -e "Got s-ui latest version: ${last_version}, beginning the installation..."
- wget -N --no-check-certificate -O /tmp/s-ui-linux-$(arch).tar.gz https://github.com/admin8800/s-ui/releases/download/${last_version}/s-ui-linux-$(arch).tar.gz
- if [[ $? -ne 0 ]]; then
- echo -e "${red}Downloading s-ui failed, please be sure that your server can access Github ${plain}"
- exit 1
- fi
- else
- last_version=$1
- url="https://github.com/admin8800/s-ui/releases/download/${last_version}/s-ui-linux-$(arch).tar.gz"
- echo -e "Beginning the install s-ui v$1"
- wget -N --no-check-certificate -O /tmp/s-ui-linux-$(arch).tar.gz ${url}
- if [[ $? -ne 0 ]]; then
- echo -e "${red}download s-ui v$1 failed,please check the version exists${plain}"
- exit 1
- fi
- fi
-
- if [[ -e /usr/local/s-ui/ ]]; then
- systemctl stop s-ui
- fi
-
- tar zxvf s-ui-linux-$(arch).tar.gz
- rm s-ui-linux-$(arch).tar.gz -f
-
- chmod +x s-ui/sui s-ui/s-ui.sh
- cp s-ui/s-ui.sh /usr/bin/s-ui
- cp -rf s-ui /usr/local/
- cp -f s-ui/*.service /etc/systemd/system/
- rm -rf s-ui
-
- config_after_install
- prepare_services
-
- systemctl enable s-ui --now
-
- echo -e "${green}s-ui v${last_version}${plain} installation finished, it is up and running now..."
- echo -e "You may access the Panel with following URL(s):${green}"
- /usr/local/s-ui/sui uri
- echo -e "${plain}"
- echo -e ""
- s-ui help
-}
-
-echo -e "${green}Executing...${plain}"
-install_base
-install_s-ui $1
+#!/bin/bash
+
+red='\033[0;31m'
+green='\033[0;32m'
+yellow='\033[0;33m'
+plain='\033[0m'
+
+cur_dir=$(pwd)
+
+# 检查 root 权限
+[[ $EUID -ne 0 ]] && echo -e "${red}致命错误:${plain}请使用 root 权限运行此脚本 \n " && exit 1
+
+# 检查系统并设置 release 变量
+if [[ -f /etc/os-release ]]; then
+ source /etc/os-release
+ release=$ID
+elif [[ -f /usr/lib/os-release ]]; then
+ source /usr/lib/os-release
+ release=$ID
+else
+ echo "检测系统失败,请联系作者!" >&2
+ exit 1
+fi
+echo "当前系统发行版为:$release"
+
+arch() {
+ case "$(uname -m)" in
+ x86_64 | x64 | amd64) echo 'amd64' ;;
+ i*86 | x86) echo '386' ;;
+ armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
+ armv7* | armv7 | arm) echo 'armv7' ;;
+ armv6* | armv6) echo 'armv6' ;;
+ armv5* | armv5) echo 'armv5' ;;
+ s390x) echo 's390x' ;;
+ *) echo -e "${green}不支持的 CPU 架构!${plain}" && rm -f install.sh && exit 1 ;;
+ esac
+}
+
+echo "架构:$(arch)"
+
+install_base() {
+ case "${release}" in
+ centos | almalinux | rocky | oracle)
+ yum -y update && yum install -y -q wget curl tar tzdata
+ ;;
+ fedora)
+ dnf -y update && dnf install -y -q wget curl tar tzdata
+ ;;
+ arch | manjaro | parch)
+ pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
+ ;;
+ opensuse-tumbleweed)
+ zypper refresh && zypper -q install -y wget curl tar timezone
+ ;;
+ *)
+ apt-get update && apt-get install -y -q wget curl tar tzdata
+ ;;
+ esac
+}
+
+config_after_install() {
+ echo -e "${yellow}正在迁移... ${plain}"
+ /usr/local/s-ui/sui migrate
+
+ echo -e "${yellow}安装/更新完成!出于安全考虑,建议修改面板设置 ${plain}"
+ read -p "是否继续修改设置 [y/n]?": config_confirm
+ if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
+ echo -e "请输入${yellow}面板端口${plain}(留空则使用现有/默认值):"
+ read config_port
+ echo -e "请输入${yellow}面板路径${plain}(留空则使用现有/默认值):"
+ read config_path
+
+ # 订阅配置
+ echo -e "请输入${yellow}订阅端口${plain}(留空则使用现有/默认值):"
+ read config_subPort
+ echo -e "请输入${yellow}订阅路径${plain}(留空则使用现有/默认值):"
+ read config_subPath
+
+ # 设置配置
+ echo -e "${yellow}正在初始化,请稍候...${plain}"
+ params=""
+ [ -z "$config_port" ] || params="$params -port $config_port"
+ [ -z "$config_path" ] || params="$params -path $config_path"
+ [ -z "$config_subPort" ] || params="$params -subPort $config_subPort"
+ [ -z "$config_subPath" ] || params="$params -subPath $config_subPath"
+ /usr/local/s-ui/sui setting ${params}
+
+ read -p "是否修改管理员账号密码 [y/n]?": admin_confirm
+ if [[ "${admin_confirm}" == "y" || "${admin_confirm}" == "Y" ]]; then
+ # 首个管理员账号密码
+ read -p "请设置用户名:" config_account
+ read -p "请设置密码:" config_password
+
+ # 设置账号密码
+ echo -e "${yellow}正在初始化,请稍候...${plain}"
+ /usr/local/s-ui/sui admin -username ${config_account} -password ${config_password}
+ else
+ echo -e "${yellow}当前管理员账号密码:${plain}"
+ /usr/local/s-ui/sui admin -show
+ fi
+ else
+ echo -e "${red}已取消...${plain}"
+ if [[ ! -f "/usr/local/s-ui/db/s-ui.db" ]]; then
+ local usernameTemp=$(head -c 6 /dev/urandom | base64)
+ local passwordTemp=$(head -c 6 /dev/urandom | base64)
+ echo -e "这是全新安装,出于安全考虑将生成随机登录信息:"
+ echo -e "###############################################"
+ echo -e "${green}用户名:${usernameTemp}${plain}"
+ echo -e "${green}密码:${passwordTemp}${plain}"
+ echo -e "###############################################"
+ echo -e "${red}如果忘记登录信息,可以输入 ${green}s-ui${red} 打开配置菜单${plain}"
+ /usr/local/s-ui/sui admin -username ${usernameTemp} -password ${passwordTemp}
+ else
+ echo -e "${red}这是升级安装,将保留旧设置;如果忘记登录信息,可以输入 ${green}s-ui${red} 打开配置菜单${plain}"
+ fi
+ fi
+}
+
+prepare_services() {
+ if [[ -f "/etc/systemd/system/sing-box.service" ]]; then
+ echo -e "${yellow}正在停止 sing-box 服务... ${plain}"
+ systemctl stop sing-box
+ rm -f /usr/local/s-ui/bin/sing-box /usr/local/s-ui/bin/runSingbox.sh /usr/local/s-ui/bin/signal
+ fi
+ if [[ -e "/usr/local/s-ui/bin" ]]; then
+ echo -e "###############################################################"
+ echo -e "${green}/usr/local/s-ui/bin${red} 目录已存在!"
+ echo -e "请检查其中内容,并在迁移后手动删除 ${plain}"
+ echo -e "###############################################################"
+ fi
+ systemctl daemon-reload
+}
+
+install_s-ui() {
+ cd /tmp/
+
+ if [ $# == 0 ]; then
+ last_version=$(curl -Ls "https://api.github.com/repos/admin8800/s-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
+ if [[ ! -n "$last_version" ]]; then
+ echo -e "${red}获取 s-ui 版本失败,可能是 Github API 限制导致,请稍后重试${plain}"
+ exit 1
+ fi
+ echo -e "已获取 s-ui 最新版本:${last_version},开始安装..."
+ wget -N --no-check-certificate -O /tmp/s-ui-linux-$(arch).tar.gz https://github.com/admin8800/s-ui/releases/download/${last_version}/s-ui-linux-$(arch).tar.gz
+ if [[ $? -ne 0 ]]; then
+ echo -e "${red}下载 s-ui 失败,请确认服务器可以访问 Github ${plain}"
+ exit 1
+ fi
+ else
+ last_version=$1
+ url="https://github.com/admin8800/s-ui/releases/download/${last_version}/s-ui-linux-$(arch).tar.gz"
+ echo -e "开始安装 s-ui v$1"
+ wget -N --no-check-certificate -O /tmp/s-ui-linux-$(arch).tar.gz ${url}
+ if [[ $? -ne 0 ]]; then
+ echo -e "${red}下载 s-ui v$1 失败,请检查该版本是否存在${plain}"
+ exit 1
+ fi
+ fi
+
+ if [[ -e /usr/local/s-ui/ ]]; then
+ systemctl stop s-ui
+ fi
+
+ tar zxvf s-ui-linux-$(arch).tar.gz
+ rm s-ui-linux-$(arch).tar.gz -f
+
+ chmod +x s-ui/sui s-ui/s-ui.sh
+ cp s-ui/s-ui.sh /usr/bin/s-ui
+ cp -rf s-ui /usr/local/
+ cp -f s-ui/*.service /etc/systemd/system/
+ rm -rf s-ui
+
+ config_after_install
+ prepare_services
+
+ systemctl enable s-ui --now
+
+ echo -e "${green}s-ui v${last_version}${plain} 安装完成,现已启动并运行..."
+ echo -e "你可以通过以下 URL 访问面板:${green}"
+ /usr/local/s-ui/sui uri
+ echo -e "${plain}"
+ echo -e ""
+ s-ui help
+}
+
+echo -e "${green}正在执行...${plain}"
+install_base
+install_s-ui $1
diff --git a/s-ui.sh b/s-ui.sh
index a693779..d6dade6 100644
--- a/s-ui.sh
+++ b/s-ui.sh
@@ -1,934 +1,934 @@
-#!/bin/bash
-
-red='\033[0;31m'
-green='\033[0;32m'
-yellow='\033[0;33m'
-plain='\033[0m'
-
-function LOGD() {
- echo -e "${yellow}[DEG] $* ${plain}"
-}
-
-function LOGE() {
- echo -e "${red}[ERR] $* ${plain}"
-}
-
-function LOGI() {
- echo -e "${green}[INF] $* ${plain}"
-}
-
-[[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1
-
-if [[ -f /etc/os-release ]]; then
- source /etc/os-release
- release=$ID
-elif [[ -f /usr/lib/os-release ]]; then
- source /usr/lib/os-release
- release=$ID
-else
- echo "Failed to check the system OS, please contact the author!" >&2
- exit 1
-fi
-
-echo "The OS release is: $release"
-
-confirm() {
- if [[ $# > 1 ]]; then
- echo && read -p "$1 [Default$2]: " temp
- if [[ x"${temp}" == x"" ]]; then
- temp=$2
- fi
- else
- read -p "$1 [y/n]: " temp
- fi
- if [[ x"${temp}" == x"y" || x"${temp}" == x"Y" ]]; then
- return 0
- else
- return 1
- fi
-}
-
-confirm_restart() {
- confirm "Restart the ${1} service" "y"
- if [[ $? == 0 ]]; then
- restart
- else
- show_menu
- fi
-}
-
-before_show_menu() {
- echo && echo -n -e "${yellow}Press enter to return to the main menu: ${plain}" && read temp
- show_menu
-}
-
-install() {
- bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/main/install.sh)
- if [[ $? == 0 ]]; then
- if [[ $# == 0 ]]; then
- start
- else
- start 0
- fi
- fi
-}
-
-update() {
- confirm "This function will forcefully reinstall the latest version, and the data will not be lost. Do you want to continue?" "n"
- if [[ $? != 0 ]]; then
- LOGE "Cancelled"
- if [[ $# == 0 ]]; then
- before_show_menu
- fi
- return 0
- fi
- bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/main/install.sh)
- if [[ $? == 0 ]]; then
- LOGI "Update is complete, Panel has automatically restarted "
- exit 0
- fi
-}
-
-custom_version() {
- echo "Enter the panel version (like 0.0.1):"
- read panel_version
-
- if [ -z "$panel_version" ]; then
- echo "Panel version cannot be empty. Exiting."
- exit 1
- fi
-
- download_link="https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh"
-
- install_command="bash <(curl -Ls $download_link) $panel_version"
-
- echo "Downloading and installing panel version $panel_version..."
- eval $install_command
-}
-
-uninstall() {
- confirm "Are you sure you want to uninstall the panel?" "n"
- if [[ $? != 0 ]]; then
- if [[ $# == 0 ]]; then
- show_menu
- fi
- return 0
- fi
- systemctl stop s-ui
- systemctl disable s-ui
- rm /etc/systemd/system/s-ui.service -f
- systemctl daemon-reload
- systemctl reset-failed
- rm /etc/s-ui/ -rf
- rm /usr/local/s-ui/ -rf
-
- echo ""
- echo -e "Uninstalled Successfully, If you want to remove this script, then after exiting the script run ${green}rm /usr/local/s-ui -f${plain} to delete it."
- echo ""
-
- if [[ $# == 0 ]]; then
- before_show_menu
- fi
-}
-
-reset_admin() {
- echo "It is not recommended to set admin's credentials to default!"
- confirm "Are you sure you want to reset admin's credentials to default ?" "n"
- if [[ $? == 0 ]]; then
- /usr/local/s-ui/sui admin -reset
- fi
- before_show_menu
-}
-
-set_admin() {
- echo "It is not recommended to set admin's credentials to a complex text."
- read -p "Please set up your username:" config_account
- read -p "Please set up your password:" config_password
- /usr/local/s-ui/sui admin -username ${config_account} -password ${config_password}
- before_show_menu
-}
-
-view_admin() {
- /usr/local/s-ui/sui admin -show
- before_show_menu
-}
-
-reset_setting() {
- confirm "Are you sure you want to reset settings to default ?" "n"
- if [[ $? == 0 ]]; then
- /usr/local/s-ui/sui setting -reset
- fi
- before_show_menu
-}
-
-set_setting() {
- echo -e "Enter the ${yellow}panel port${plain} (leave blank for existing/default value):"
- read config_port
- echo -e "Enter the ${yellow}panel path${plain} (leave blank for existing/default value):"
- read config_path
-
- echo -e "Enter the ${yellow}subscription port${plain} (leave blank for existing/default value):"
- read config_subPort
- echo -e "Enter the ${yellow}subscription path${plain} (leave blank for existing/default value):"
- read config_subPath
-
- echo -e "${yellow}Initializing, please wait...${plain}"
- params=""
- [ -z "$config_port" ] || params="$params -port $config_port"
- [ -z "$config_path" ] || params="$params -path $config_path"
- [ -z "$config_subPort" ] || params="$params -subPort $config_subPort"
- [ -z "$config_subPath" ] || params="$params -subPath $config_subPath"
- /usr/local/s-ui/sui setting ${params}
- before_show_menu
-}
-
-view_setting() {
- /usr/local/s-ui/sui setting -show
- view_uri
- before_show_menu
-}
-
-view_uri() {
- info=$(/usr/local/s-ui/sui uri)
- if [[ $? != 0 ]]; then
- LOGE "Get current uri error"
- before_show_menu
- fi
- LOGI "You may access the Panel with following URL(s):"
- echo -e "${green}${info}${plain}"
-}
-
-start() {
- check_status $1
- if [[ $? == 0 ]]; then
- echo ""
- LOGI -e "${1} is running, No need to start again, If you need to restart, please select restart"
- else
- systemctl start $1
- sleep 2
- check_status $1
- if [[ $? == 0 ]]; then
- LOGI "${1} Started Successfully"
- else
- LOGE "Failed to start ${1}, Probably because it takes longer than two seconds to start, Please check the log information later"
- fi
- fi
-
- if [[ $# == 1 ]]; then
- before_show_menu
- fi
-}
-
-stop() {
- check_status $1
- if [[ $? == 1 ]]; then
- echo ""
- LOGI "${1} stopped, No need to stop again!"
- else
- systemctl stop $1
- sleep 2
- check_status
- if [[ $? == 1 ]]; then
- LOGI "${1} stopped successfully"
- else
- LOGE "Failed to stop ${1}, Probably because the stop time exceeds two seconds, Please check the log information later"
- fi
- fi
-
- if [[ $# == 1 ]]; then
- before_show_menu
- fi
-}
-
-restart() {
- systemctl restart $1
- sleep 2
- check_status $1
- if [[ $? == 0 ]]; then
- LOGI "${1} Restarted successfully"
- else
- LOGE "Failed to restart ${1}, Probably because it takes longer than two seconds to start, Please check the log information later"
- fi
- if [[ $# == 1 ]]; then
- before_show_menu
- fi
-}
-
-status() {
- systemctl status s-ui -l
- if [[ $# == 0 ]]; then
- before_show_menu
- fi
-}
-
-enable() {
- systemctl enable $1
- if [[ $? == 0 ]]; then
- LOGI "Set ${1} to boot automatically on startup successfully"
- else
- LOGE "Failed to set ${1} Autostart"
- fi
-
- if [[ $# == 1 ]]; then
- before_show_menu
- fi
-}
-
-disable() {
- systemctl disable $1
- if [[ $? == 0 ]]; then
- LOGI "Autostart ${1} Cancelled successfully"
- else
- LOGE "Failed to cancel ${1} autostart"
- fi
-
- if [[ $# == 1 ]]; then
- before_show_menu
- fi
-}
-
-show_log() {
- journalctl -u $1.service -e --no-pager -f
- if [[ $# == 1 ]]; then
- before_show_menu
- fi
-}
-
-update_shell() {
- wget -O /usr/bin/s-ui -N --no-check-certificate https://github.com/admin8800/s-ui/raw/main/s-ui.sh
- if [[ $? != 0 ]]; then
- echo ""
- LOGE "Failed to download script, Please check whether the machine can connect Github"
- before_show_menu
- else
- chmod +x /usr/bin/s-ui
- LOGI "Upgrade script succeeded, Please rerun the script" && exit 0
- fi
-}
-
-check_status() {
- if [[ ! -f "/etc/systemd/system/$1.service" ]]; then
- return 2
- fi
- temp=$(systemctl status "$1" | grep Active | awk '{print $3}' | cut -d "(" -f2 | cut -d ")" -f1)
- if [[ x"${temp}" == x"running" ]]; then
- return 0
- else
- return 1
- fi
-}
-
-check_enabled() {
- temp=$(systemctl is-enabled $1)
- if [[ x"${temp}" == x"enabled" ]]; then
- return 0
- else
- return 1
- fi
-}
-
-check_uninstall() {
- check_status s-ui
- if [[ $? != 2 ]]; then
- echo ""
- LOGE "Panel is already installed, Please do not reinstall"
- if [[ $# == 0 ]]; then
- before_show_menu
- fi
- return 1
- else
- return 0
- fi
-}
-
-check_install() {
- check_status s-ui
- if [[ $? == 2 ]]; then
- echo ""
- LOGE "Please install the panel first"
- if [[ $# == 0 ]]; then
- before_show_menu
- fi
- return 1
- else
- return 0
- fi
-}
-
-show_status() {
- check_status $1
- case $? in
- 0)
- echo -e "${1} state: ${green}Running${plain}"
- show_enable_status $1
- ;;
- 1)
- echo -e "${1} state: ${yellow}Not Running${plain}"
- show_enable_status $1
- ;;
- 2)
- echo -e "${1} state: ${red}Not Installed${plain}"
- ;;
- esac
-}
-
-show_enable_status() {
- check_enabled $1
- if [[ $? == 0 ]]; then
- echo -e "Start ${1} automatically: ${green}Yes${plain}"
- else
- echo -e "Start ${1} automatically: ${red}No${plain}"
- fi
-}
-
-check_s-ui_status() {
- count=$(ps -ef | grep "sui" | grep -v "grep" | wc -l)
- if [[ count -ne 0 ]]; then
- return 0
- else
- return 1
- fi
-}
-
-show_s-ui_status() {
- check_s-ui_status
- if [[ $? == 0 ]]; then
- echo -e "s-ui state: ${green}Running${plain}"
- else
- echo -e "s-ui state: ${red}Not Running${plain}"
- fi
-}
-
-bbr_menu() {
- echo -e "${green}\t1.${plain} Enable BBR"
- echo -e "${green}\t2.${plain} Disable BBR"
- echo -e "${green}\t0.${plain} Back to Main Menu"
- read -p "Choose an option: " choice
- case "$choice" in
- 0)
- show_menu
- ;;
- 1)
- enable_bbr
- ;;
- 2)
- disable_bbr
- ;;
- *) echo "Invalid choice" ;;
- esac
-}
-
-disable_bbr() {
- if ! grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf || ! grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
- echo -e "${yellow}BBR is not currently enabled.${plain}"
- exit 0
- fi
- sed -i 's/net.core.default_qdisc=fq/net.core.default_qdisc=pfifo_fast/' /etc/sysctl.conf
- sed -i 's/net.ipv4.tcp_congestion_control=bbr/net.ipv4.tcp_congestion_control=cubic/' /etc/sysctl.conf
- sysctl -p
- if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "cubic" ]]; then
- echo -e "${green}BBR has been replaced with CUBIC successfully.${plain}"
- else
- echo -e "${red}Failed to replace BBR with CUBIC. Please check your system configuration.${plain}"
- fi
-}
-
-enable_bbr() {
- if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
- echo -e "${green}BBR is already enabled!${plain}"
- exit 0
- fi
- case "${release}" in
- ubuntu | debian | armbian)
- apt-get update && apt-get install -yqq --no-install-recommends ca-certificates
- ;;
- centos | almalinux | rocky | oracle)
- yum -y update && yum -y install ca-certificates
- ;;
- fedora)
- dnf -y update && dnf -y install ca-certificates
- ;;
- arch | manjaro | parch)
- pacman -Sy --noconfirm ca-certificates
- ;;
- *)
- echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
- exit 1
- ;;
- esac
- echo "net.core.default_qdisc=fq" | tee -a /etc/sysctl.conf
- echo "net.ipv4.tcp_congestion_control=bbr" | tee -a /etc/sysctl.conf
- sysctl -p
- if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
- echo -e "${green}BBR has been enabled successfully.${plain}"
- else
- echo -e "${red}Failed to enable BBR. Please check your system configuration.${plain}"
- fi
-}
-
-install_acme() {
- cd ~
- LOGI "install acme..."
- curl https://get.acme.sh | sh
- if [ $? -ne 0 ]; then
- LOGE "install acme failed"
- return 1
- else
- LOGI "install acme succeed"
- fi
- return 0
-}
-
-ssl_cert_issue_main() {
- echo -e "${green}\t1.${plain} Get SSL"
- echo -e "${green}\t2.${plain} Revoke"
- echo -e "${green}\t3.${plain} Force Renew"
- echo -e "${green}\t4.${plain} Self-signed Certificate"
- read -p "Choose an option: " choice
- case "$choice" in
- 1) ssl_cert_issue ;;
- 2)
- local domain=""
- read -p "Please enter your domain name to revoke the certificate: " domain
- ~/.acme.sh/acme.sh --revoke -d ${domain}
- LOGI "Certificate revoked"
- ;;
- 3)
- local domain=""
- read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain
- ~/.acme.sh/acme.sh --renew -d ${domain} --force ;;
- 4)
- generate_self_signed_cert
- ;;
- *) echo "Invalid choice" ;;
- esac
-}
-
-ssl_cert_issue() {
- if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
- echo "acme.sh could not be found. we will install it"
- install_acme
- if [ $? -ne 0 ]; then
- LOGE "install acme failed, please check logs"
- exit 1
- fi
- fi
- case "${release}" in
- ubuntu | debian | armbian)
- apt update && apt install socat -y
- ;;
- centos | almalinux | rocky | oracle)
- yum -y update && yum -y install socat
- ;;
- fedora)
- dnf -y update && dnf -y install socat
- ;;
- arch | manjaro | parch)
- pacman -Sy --noconfirm socat
- ;;
- *)
- echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
- exit 1
- ;;
- esac
- if [ $? -ne 0 ]; then
- LOGE "install socat failed, please check logs"
- exit 1
- else
- LOGI "install socat succeed..."
- fi
-
- local domain=""
- read -p "Please enter your domain name:" domain
- LOGD "your domain is:${domain},check it..."
- local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}')
-
- if [ ${currentCert} == ${domain} ]; then
- local certInfo=$(~/.acme.sh/acme.sh --list)
- LOGE "system already has certs here,can not issue again,current certs details:"
- LOGI "$certInfo"
- exit 1
- else
- LOGI "your domain is ready for issuing cert now..."
- fi
-
- certPath="/root/cert/${domain}"
- if [ ! -d "$certPath" ]; then
- mkdir -p "$certPath"
- else
- rm -rf "$certPath"
- mkdir -p "$certPath"
- fi
-
- local WebPort=80
- read -p "please choose which port do you use,default will be 80 port:" WebPort
- if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
- LOGE "your input ${WebPort} is invalid,will use default port"
- fi
- LOGI "will use port:${WebPort} to issue certs,please make sure this port is open..."
- ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
- ~/.acme.sh/acme.sh --issue -d ${domain} --standalone --httpport ${WebPort}
- if [ $? -ne 0 ]; then
- LOGE "issue certs failed,please check logs"
- rm -rf ~/.acme.sh/${domain}
- exit 1
- else
- LOGE "issue certs succeed,installing certs..."
- fi
- ~/.acme.sh/acme.sh --installcert -d ${domain} \
- --key-file /root/cert/${domain}/privkey.pem \
- --fullchain-file /root/cert/${domain}/fullchain.pem
-
- if [ $? -ne 0 ]; then
- LOGE "install certs failed,exit"
- rm -rf ~/.acme.sh/${domain}
- exit 1
- else
- LOGI "install certs succeed,enable auto renew..."
- fi
-
- ~/.acme.sh/acme.sh --upgrade --auto-upgrade
- if [ $? -ne 0 ]; then
- LOGE "auto renew failed, certs details:"
- ls -lah cert/*
- chmod 755 $certPath/*
- exit 1
- else
- LOGI "auto renew succeed, certs details:"
- ls -lah cert/*
- chmod 755 $certPath/*
- fi
-}
-
-ssl_cert_issue_CF() {
- echo -E ""
- LOGD "******Instructions for use******"
- echo "1) New certificate from Cloudflare"
- echo "2) Force renew existing Certificates"
- echo "3) Back to Menu"
- read -p "Enter your choice [1-3]: " choice
-
- certPath="/root/cert-CF"
-
- case $choice in
- 1|2)
- force_flag=""
- if [ "$choice" -eq 2 ]; then
- force_flag="--force"
- echo "Forcing SSL certificate reissuance..."
- else
- echo "Starting SSL certificate issuance..."
- fi
-
- LOGD "******Instructions for use******"
- LOGI "This Acme script requires the following data:"
- LOGI "1.Cloudflare Registered e-mail"
- LOGI "2.Cloudflare Global API Key"
- LOGI "3.The domain name that has been resolved DNS to the current server by Cloudflare"
- LOGI "4.The script applies for a certificate. The default installation path is /root/cert "
- confirm "Confirmed?[y/n]" "y"
- if [ $? -eq 0 ]; then
- if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
- echo "acme.sh could not be found. Installing..."
- install_acme
- if [ $? -ne 0 ]; then
- LOGE "Install acme failed, please check logs"
- exit 1
- fi
- fi
-
- CF_Domain=""
- if [ ! -d "$certPath" ]; then
- mkdir -p $certPath
- else
- rm -rf $certPath
- mkdir -p $certPath
- fi
-
- LOGD "Please set a domain name:"
- read -p "Input your domain here: " CF_Domain
- LOGD "Your domain name is set to: ${CF_Domain}"
-
- CF_GlobalKey=""
- CF_AccountEmail=""
- LOGD "Please set the API key:"
- read -p "Input your key here: " CF_GlobalKey
- LOGD "Your API key is: ${CF_GlobalKey}"
-
- LOGD "Please set up registered email:"
- read -p "Input your email here: " CF_AccountEmail
- LOGD "Your registered email address is: ${CF_AccountEmail}"
-
- ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
- if [ $? -ne 0 ]; then
- LOGE "Default CA, Let's Encrypt failed, script exiting..."
- exit 1
- fi
-
- export CF_Key="${CF_GlobalKey}"
- export CF_Email="${CF_AccountEmail}"
-
- ~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} $force_flag --log
- if [ $? -ne 0 ]; then
- LOGE "Certificate issuance failed, script exiting..."
- exit 1
- else
- LOGI "Certificate issued Successfully, Installing..."
- fi
-
- mkdir -p ${certPath}/${CF_Domain}
- if [ $? -ne 0 ]; then
- LOGE "Failed to create directory: ${certPath}/${CF_Domain}"
- exit 1
- fi
-
- ~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} \
- --fullchain-file ${certPath}/${CF_Domain}/fullchain.pem \
- --key-file ${certPath}/${CF_Domain}/privkey.pem
-
- if [ $? -ne 0 ]; then
- LOGE "Certificate installation failed, script exiting..."
- exit 1
- else
- LOGI "Certificate installed Successfully, Turning on automatic updates..."
- fi
-
- ~/.acme.sh/acme.sh --upgrade --auto-upgrade
- if [ $? -ne 0 ]; then
- LOGE "Auto update setup failed, script exiting..."
- exit 1
- else
- LOGI "The certificate is installed and auto-renewal is turned on."
- ls -lah ${certPath}/${CF_Domain}
- chmod 755 ${certPath}/${CF_Domain}
- fi
- fi
- show_menu
- ;;
- 3)
- echo "Exiting..."
- show_menu
- ;;
- *)
- echo "Invalid choice, please select again."
- show_menu
- ;;
- esac
-}
-
-generate_self_signed_cert() {
- cert_dir="/etc/sing-box"
- mkdir -p "$cert_dir"
- LOGI "Choose certificate type:"
- echo -e "${green}\t1.${plain} Ed25519 (*recommended*)"
- echo -e "${green}\t2.${plain} RSA 2048"
- echo -e "${green}\t3.${plain} RSA 4096"
- echo -e "${green}\t4.${plain} ECDSA prime256v1"
- echo -e "${green}\t5.${plain} ECDSA secp384r1"
- read -p "Enter your choice [1-5, default 1]: " cert_type
- cert_type=${cert_type:-1}
-
- case "$cert_type" in
- 1)
- algo="ed25519"
- key_opt="-newkey ed25519"
- ;;
- 2)
- algo="rsa"
- key_opt="-newkey rsa:2048"
- ;;
- 3)
- algo="rsa"
- key_opt="-newkey rsa:4096"
- ;;
- 4)
- algo="ecdsa"
- key_opt="-newkey ec -pkeyopt ec_paramgen_curve:prime256v1"
- ;;
- 5)
- algo="ecdsa"
- key_opt="-newkey ec -pkeyopt ec_paramgen_curve:secp384r1"
- ;;
- *)
- algo="ed25519"
- key_opt="-newkey ed25519"
- ;;
- esac
-
- LOGI "Generating self-signed certificate ($algo)..."
- sudo openssl req -x509 -nodes -days 3650 $key_opt \
- -keyout "${cert_dir}/self.key" \
- -out "${cert_dir}/self.crt" \
- -subj "/CN=myserver"
- if [[ $? -eq 0 ]]; then
- sudo chmod 600 "${cert_dir}/self."*
- LOGI "Self-signed certificate generated successfully!"
- LOGI "Certificate path: ${cert_dir}/self.crt"
- LOGI "Key path: ${cert_dir}/self.key"
- else
- LOGE "Failed to generate self-signed certificate."
- fi
- before_show_menu
-}
-
-show_usage() {
- echo -e "S-UI Control Menu Usage"
- echo -e "------------------------------------------"
- echo -e "SUBCOMMANDS:"
- echo -e "s-ui - Admin Management Script"
- echo -e "s-ui start - Start s-ui"
- echo -e "s-ui stop - Stop s-ui"
- echo -e "s-ui restart - Restart s-ui"
- echo -e "s-ui status - Current Status of s-ui"
- echo -e "s-ui enable - Enable Autostart on OS Startup"
- echo -e "s-ui disable - Disable Autostart on OS Startup"
- echo -e "s-ui log - Check s-ui Logs"
- echo -e "s-ui update - Update"
- echo -e "s-ui install - Install"
- echo -e "s-ui uninstall - Uninstall"
- echo -e "s-ui help - Control Menu Usage"
- echo -e "------------------------------------------"
-}
-
-show_menu() {
- echo -e "
- ${green}S-UI Admin Management Script ${plain}
-————————————————————————————————
- ${green}0.${plain} Exit
-————————————————————————————————
- ${green}1.${plain} Install
- ${green}2.${plain} Update
- ${green}3.${plain} Custom Version
- ${green}4.${plain} Uninstall
-————————————————————————————————
- ${green}5.${plain} Reset admin credentials to default
- ${green}6.${plain} Set admin credentials
- ${green}7.${plain} View admin credentials
-————————————————————————————————
- ${green}8.${plain} Reset Panel Settings
- ${green}9.${plain} Set Panel settings
- ${green}10.${plain} View Panel Settings
-————————————————————————————————
- ${green}11.${plain} S-UI Start
- ${green}12.${plain} S-UI Stop
- ${green}13.${plain} S-UI Restart
- ${green}14.${plain} S-UI Check State
- ${green}15.${plain} S-UI Check Logs
- ${green}16.${plain} S-UI Enable Autostart
- ${green}17.${plain} S-UI Disable Autostart
-————————————————————————————————
- ${green}18.${plain} Enable or Disable BBR
- ${green}19.${plain} SSL Certificate Management
- ${green}20.${plain} Cloudflare SSL Certificate
-————————————————————————————————
- "
- show_status s-ui
- echo && read -p "Please enter your selection [0-20]: " num
-
- case "${num}" in
- 0)
- exit 0
- ;;
- 1)
- check_uninstall && install
- ;;
- 2)
- check_install && update
- ;;
- 3)
- check_install && custom_version
- ;;
- 4)
- check_install && uninstall
- ;;
- 5)
- check_install && reset_admin
- ;;
- 6)
- check_install && set_admin
- ;;
- 7)
- check_install && view_admin
- ;;
- 8)
- check_install && reset_setting
- ;;
- 9)
- check_install && set_setting
- ;;
- 10)
- check_install && view_setting
- ;;
- 11)
- check_install && start s-ui
- ;;
- 12)
- check_install && stop s-ui
- ;;
- 13)
- check_install && restart s-ui
- ;;
- 14)
- check_install && status s-ui
- ;;
- 15)
- check_install && show_log s-ui
- ;;
- 16)
- check_install && enable s-ui
- ;;
- 17)
- check_install && disable s-ui
- ;;
- 18)
- bbr_menu
- ;;
- 19)
- ssl_cert_issue_main
- ;;
- 20)
- ssl_cert_issue_CF
- ;;
- *)
- LOGE "Please enter the correct number [0-20]"
- ;;
- esac
-}
-
-if [[ $# > 0 ]]; then
- case $1 in
- "start")
- check_install 0 && start s-ui 0
- ;;
- "stop")
- check_install 0 && stop s-ui 0
- ;;
- "restart")
- check_install 0 && restart s-ui 0
- ;;
- "status")
- check_install 0 && status 0
- ;;
- "enable")
- check_install 0 && enable s-ui 0
- ;;
- "disable")
- check_install 0 && disable s-ui 0
- ;;
- "log")
- check_install 0 && show_log s-ui 0
- ;;
- "update")
- check_install 0 && update 0
- ;;
- "install")
- check_uninstall 0 && install 0
- ;;
- "uninstall")
- check_install 0 && uninstall 0
- ;;
- *) show_usage ;;
- esac
-else
- show_menu
-fi
+#!/bin/bash
+
+red='\033[0;31m'
+green='\033[0;32m'
+yellow='\033[0;33m'
+plain='\033[0m'
+
+function LOGD() {
+ echo -e "${yellow}[调试] $* ${plain}"
+}
+
+function LOGE() {
+ echo -e "${red}[错误] $* ${plain}"
+}
+
+function LOGI() {
+ echo -e "${green}[信息] $* ${plain}"
+}
+
+[[ $EUID -ne 0 ]] && LOGE "错误:必须使用 root 权限运行此脚本!\n" && exit 1
+
+if [[ -f /etc/os-release ]]; then
+ source /etc/os-release
+ release=$ID
+elif [[ -f /usr/lib/os-release ]]; then
+ source /usr/lib/os-release
+ release=$ID
+else
+ echo "检测系统失败,请联系作者!" >&2
+ exit 1
+fi
+
+echo "当前系统发行版为:$release"
+
+confirm() {
+ if [[ $# > 1 ]]; then
+ echo && read -p "$1 [默认$2]: " temp
+ if [[ x"${temp}" == x"" ]]; then
+ temp=$2
+ fi
+ else
+ read -p "$1 [y/n]: " temp
+ fi
+ if [[ x"${temp}" == x"y" || x"${temp}" == x"Y" ]]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+confirm_restart() {
+ confirm "重启 ${1} 服务" "y"
+ if [[ $? == 0 ]]; then
+ restart
+ else
+ show_menu
+ fi
+}
+
+before_show_menu() {
+ echo && echo -n -e "${yellow}按回车返回主菜单:${plain}" && read temp
+ show_menu
+}
+
+install() {
+ bash <(curl -Ls https://raw.githubusercontent.com/admin8800/s-ui/main/install.sh)
+ if [[ $? == 0 ]]; then
+ if [[ $# == 0 ]]; then
+ start
+ else
+ start 0
+ fi
+ fi
+}
+
+update() {
+ confirm "此功能将强制重装最新版本,数据不会丢失。是否继续?" "n"
+ if [[ $? != 0 ]]; then
+ LOGE "已取消"
+ if [[ $# == 0 ]]; then
+ before_show_menu
+ fi
+ return 0
+ fi
+ bash <(curl -Ls https://raw.githubusercontent.com/admin8800/s-ui/main/install.sh)
+ if [[ $? == 0 ]]; then
+ LOGI "更新完成,面板已自动重启"
+ exit 0
+ fi
+}
+
+custom_version() {
+ echo "请输入面板版本(例如 0.0.1):"
+ read panel_version
+
+ if [ -z "$panel_version" ]; then
+ echo "面板版本不能为空。正在退出。"
+ exit 1
+ fi
+
+ download_link="https://raw.githubusercontent.com/admin8800/s-ui/main/install.sh"
+
+ install_command="bash <(curl -Ls $download_link) $panel_version"
+
+ echo "正在下载并安装面板版本 $panel_version..."
+ eval $install_command
+}
+
+uninstall() {
+ confirm "确定要卸载面板吗?" "n"
+ if [[ $? != 0 ]]; then
+ if [[ $# == 0 ]]; then
+ show_menu
+ fi
+ return 0
+ fi
+ systemctl stop s-ui
+ systemctl disable s-ui
+ rm /etc/systemd/system/s-ui.service -f
+ systemctl daemon-reload
+ systemctl reset-failed
+ rm /etc/s-ui/ -rf
+ rm /usr/local/s-ui/ -rf
+
+ echo ""
+ echo -e "卸载成功。如果要删除此脚本,请在退出脚本后运行 ${green}rm /usr/local/s-ui -f${plain}。"
+ echo ""
+
+ if [[ $# == 0 ]]; then
+ before_show_menu
+ fi
+}
+
+reset_admin() {
+ echo "不建议将管理员账号密码设置为默认值!"
+ confirm "确定要将管理员账号密码重置为默认值吗?" "n"
+ if [[ $? == 0 ]]; then
+ /usr/local/s-ui/sui admin -reset
+ fi
+ before_show_menu
+}
+
+set_admin() {
+ echo "不建议将管理员账号密码设置为过于复杂的文本。"
+ read -p "请设置用户名:" config_account
+ read -p "请设置密码:" config_password
+ /usr/local/s-ui/sui admin -username ${config_account} -password ${config_password}
+ before_show_menu
+}
+
+view_admin() {
+ /usr/local/s-ui/sui admin -show
+ before_show_menu
+}
+
+reset_setting() {
+ confirm "确定要将设置重置为默认值吗?" "n"
+ if [[ $? == 0 ]]; then
+ /usr/local/s-ui/sui setting -reset
+ fi
+ before_show_menu
+}
+
+set_setting() {
+ echo -e "请输入${yellow}面板端口${plain}(留空则使用现有/默认值):"
+ read config_port
+ echo -e "请输入${yellow}面板路径${plain}(留空则使用现有/默认值):"
+ read config_path
+
+ echo -e "请输入${yellow}订阅端口${plain}(留空则使用现有/默认值):"
+ read config_subPort
+ echo -e "请输入${yellow}订阅路径${plain}(留空则使用现有/默认值):"
+ read config_subPath
+
+ echo -e "${yellow}正在初始化,请稍候...${plain}"
+ params=""
+ [ -z "$config_port" ] || params="$params -port $config_port"
+ [ -z "$config_path" ] || params="$params -path $config_path"
+ [ -z "$config_subPort" ] || params="$params -subPort $config_subPort"
+ [ -z "$config_subPath" ] || params="$params -subPath $config_subPath"
+ /usr/local/s-ui/sui setting ${params}
+ before_show_menu
+}
+
+view_setting() {
+ /usr/local/s-ui/sui setting -show
+ view_uri
+ before_show_menu
+}
+
+view_uri() {
+ info=$(/usr/local/s-ui/sui uri)
+ if [[ $? != 0 ]]; then
+ LOGE "获取当前 URI 失败"
+ before_show_menu
+ fi
+ LOGI "你可以通过以下 URL 访问面板:"
+ echo -e "${green}${info}${plain}"
+}
+
+start() {
+ check_status $1
+ if [[ $? == 0 ]]; then
+ echo ""
+ LOGI -e "${1} 正在运行,无需再次启动;如果需要重启,请选择重启"
+ else
+ systemctl start $1
+ sleep 2
+ check_status $1
+ if [[ $? == 0 ]]; then
+ LOGI "${1} 启动成功"
+ else
+ LOGE "启动 ${1} 失败,可能是启动时间超过两秒,请稍后查看日志信息"
+ fi
+ fi
+
+ if [[ $# == 1 ]]; then
+ before_show_menu
+ fi
+}
+
+stop() {
+ check_status $1
+ if [[ $? == 1 ]]; then
+ echo ""
+ LOGI "${1} 已停止,无需再次停止!"
+ else
+ systemctl stop $1
+ sleep 2
+ check_status
+ if [[ $? == 1 ]]; then
+ LOGI "${1} 停止成功"
+ else
+ LOGE "停止 ${1} 失败,可能是停止时间超过两秒,请稍后查看日志信息"
+ fi
+ fi
+
+ if [[ $# == 1 ]]; then
+ before_show_menu
+ fi
+}
+
+restart() {
+ systemctl restart $1
+ sleep 2
+ check_status $1
+ if [[ $? == 0 ]]; then
+ LOGI "${1} 重启成功"
+ else
+ LOGE "重启 ${1} 失败,可能是启动时间超过两秒,请稍后查看日志信息"
+ fi
+ if [[ $# == 1 ]]; then
+ before_show_menu
+ fi
+}
+
+status() {
+ systemctl status s-ui -l
+ if [[ $# == 0 ]]; then
+ before_show_menu
+ fi
+}
+
+enable() {
+ systemctl enable $1
+ if [[ $? == 0 ]]; then
+ LOGI "已成功设置 ${1} 开机自启"
+ else
+ LOGE "设置 ${1} 开机自启失败"
+ fi
+
+ if [[ $# == 1 ]]; then
+ before_show_menu
+ fi
+}
+
+disable() {
+ systemctl disable $1
+ if [[ $? == 0 ]]; then
+ LOGI "已成功取消 ${1} 开机自启"
+ else
+ LOGE "取消 ${1} 开机自启失败"
+ fi
+
+ if [[ $# == 1 ]]; then
+ before_show_menu
+ fi
+}
+
+show_log() {
+ journalctl -u $1.service -e --no-pager -f
+ if [[ $# == 1 ]]; then
+ before_show_menu
+ fi
+}
+
+update_shell() {
+ wget -O /usr/bin/s-ui -N --no-check-certificate https://github.com/admin8800/s-ui/raw/main/s-ui.sh
+ if [[ $? != 0 ]]; then
+ echo ""
+ LOGE "下载脚本失败,请检查当前机器是否可以连接 Github"
+ before_show_menu
+ else
+ chmod +x /usr/bin/s-ui
+ LOGI "脚本升级成功,请重新运行脚本" && exit 0
+ fi
+}
+
+check_status() {
+ if [[ ! -f "/etc/systemd/system/$1.service" ]]; then
+ return 2
+ fi
+ temp=$(systemctl status "$1" | grep Active | awk '{print $3}' | cut -d "(" -f2 | cut -d ")" -f1)
+ if [[ x"${temp}" == x"running" ]]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+check_enabled() {
+ temp=$(systemctl is-enabled $1)
+ if [[ x"${temp}" == x"enabled" ]]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+check_uninstall() {
+ check_status s-ui
+ if [[ $? != 2 ]]; then
+ echo ""
+ LOGE "面板已安装,请勿重复安装"
+ if [[ $# == 0 ]]; then
+ before_show_menu
+ fi
+ return 1
+ else
+ return 0
+ fi
+}
+
+check_install() {
+ check_status s-ui
+ if [[ $? == 2 ]]; then
+ echo ""
+ LOGE "请先安装面板"
+ if [[ $# == 0 ]]; then
+ before_show_menu
+ fi
+ return 1
+ else
+ return 0
+ fi
+}
+
+show_status() {
+ check_status $1
+ case $? in
+ 0)
+ echo -e "${1} 状态:${green}运行中${plain}"
+ show_enable_status $1
+ ;;
+ 1)
+ echo -e "${1} 状态:${yellow}未运行${plain}"
+ show_enable_status $1
+ ;;
+ 2)
+ echo -e "${1} 状态:${red}未安装${plain}"
+ ;;
+ esac
+}
+
+show_enable_status() {
+ check_enabled $1
+ if [[ $? == 0 ]]; then
+ echo -e "${1} 开机自启:${green}是${plain}"
+ else
+ echo -e "${1} 开机自启:${red}否${plain}"
+ fi
+}
+
+check_s-ui_status() {
+ count=$(ps -ef | grep "sui" | grep -v "grep" | wc -l)
+ if [[ count -ne 0 ]]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+show_s-ui_status() {
+ check_s-ui_status
+ if [[ $? == 0 ]]; then
+ echo -e "s-ui 状态:${green}运行中${plain}"
+ else
+ echo -e "s-ui 状态:${red}未运行${plain}"
+ fi
+}
+
+bbr_menu() {
+ echo -e "${green}\t1.${plain} 启用 BBR"
+ echo -e "${green}\t2.${plain} 禁用 BBR"
+ echo -e "${green}\t0.${plain} 返回主菜单"
+ read -p "请选择一个选项: " choice
+ case "$choice" in
+ 0)
+ show_menu
+ ;;
+ 1)
+ enable_bbr
+ ;;
+ 2)
+ disable_bbr
+ ;;
+ *) echo "无效选择" ;;
+ esac
+}
+
+disable_bbr() {
+ if ! grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf || ! grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
+ echo -e "${yellow}当前未启用 BBR。${plain}"
+ exit 0
+ fi
+ sed -i 's/net.core.default_qdisc=fq/net.core.default_qdisc=pfifo_fast/' /etc/sysctl.conf
+ sed -i 's/net.ipv4.tcp_congestion_control=bbr/net.ipv4.tcp_congestion_control=cubic/' /etc/sysctl.conf
+ sysctl -p
+ if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "cubic" ]]; then
+ echo -e "${green}已成功将 BBR 替换为 CUBIC。${plain}"
+ else
+ echo -e "${red}将 BBR 替换为 CUBIC 失败。请检查系统配置。${plain}"
+ fi
+}
+
+enable_bbr() {
+ if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
+ echo -e "${green}BBR 已启用!${plain}"
+ exit 0
+ fi
+ case "${release}" in
+ ubuntu | debian | armbian)
+ apt-get update && apt-get install -yqq --no-install-recommends ca-certificates
+ ;;
+ centos | almalinux | rocky | oracle)
+ yum -y update && yum -y install ca-certificates
+ ;;
+ fedora)
+ dnf -y update && dnf -y install ca-certificates
+ ;;
+ arch | manjaro | parch)
+ pacman -Sy --noconfirm ca-certificates
+ ;;
+ *)
+ echo -e "${red}不支持的操作系统。请检查脚本并手动安装必要的软件包。${plain}\n"
+ exit 1
+ ;;
+ esac
+ echo "net.core.default_qdisc=fq" | tee -a /etc/sysctl.conf
+ echo "net.ipv4.tcp_congestion_control=bbr" | tee -a /etc/sysctl.conf
+ sysctl -p
+ if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
+ echo -e "${green}BBR 启用成功。${plain}"
+ else
+ echo -e "${red}启用 BBR 失败。请检查系统配置。${plain}"
+ fi
+}
+
+install_acme() {
+ cd ~
+ LOGI "正在安装 acme..."
+ curl https://get.acme.sh | sh
+ if [ $? -ne 0 ]; then
+ LOGE "安装 acme 失败"
+ return 1
+ else
+ LOGI "安装 acme 成功"
+ fi
+ return 0
+}
+
+ssl_cert_issue_main() {
+ echo -e "${green}\t1.${plain} 获取 SSL"
+ echo -e "${green}\t2.${plain} 吊销证书"
+ echo -e "${green}\t3.${plain} 强制续签"
+ echo -e "${green}\t4.${plain} 自签名证书"
+ read -p "请选择一个选项: " choice
+ case "$choice" in
+ 1) ssl_cert_issue ;;
+ 2)
+ local domain=""
+ read -p "请输入要吊销证书的域名: " domain
+ ~/.acme.sh/acme.sh --revoke -d ${domain}
+ LOGI "证书已吊销"
+ ;;
+ 3)
+ local domain=""
+ read -p "请输入要强制续签 SSL 证书的域名: " domain
+ ~/.acme.sh/acme.sh --renew -d ${domain} --force ;;
+ 4)
+ generate_self_signed_cert
+ ;;
+ *) echo "无效选择" ;;
+ esac
+}
+
+ssl_cert_issue() {
+ if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
+ echo "未找到 acme.sh,将进行安装"
+ install_acme
+ if [ $? -ne 0 ]; then
+ LOGE "安装 acme 失败,请检查日志"
+ exit 1
+ fi
+ fi
+ case "${release}" in
+ ubuntu | debian | armbian)
+ apt update && apt install socat -y
+ ;;
+ centos | almalinux | rocky | oracle)
+ yum -y update && yum -y install socat
+ ;;
+ fedora)
+ dnf -y update && dnf -y install socat
+ ;;
+ arch | manjaro | parch)
+ pacman -Sy --noconfirm socat
+ ;;
+ *)
+ echo -e "${red}不支持的操作系统。请检查脚本并手动安装必要的软件包。${plain}\n"
+ exit 1
+ ;;
+ esac
+ if [ $? -ne 0 ]; then
+ LOGE "安装 socat 失败,请检查日志"
+ exit 1
+ else
+ LOGI "安装 socat 成功..."
+ fi
+
+ local domain=""
+ read -p "请输入你的域名:" domain
+ LOGD "你的域名是:${domain},正在检查..."
+ local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}')
+
+ if [ ${currentCert} == ${domain} ]; then
+ local certInfo=$(~/.acme.sh/acme.sh --list)
+ LOGE "系统中已存在证书,不能重复签发,当前证书详情:"
+ LOGI "$certInfo"
+ exit 1
+ else
+ LOGI "你的域名已准备好签发证书..."
+ fi
+
+ certPath="/root/cert/${domain}"
+ if [ ! -d "$certPath" ]; then
+ mkdir -p "$certPath"
+ else
+ rm -rf "$certPath"
+ mkdir -p "$certPath"
+ fi
+
+ local WebPort=80
+ read -p "请选择使用的端口,默认使用 80 端口:" WebPort
+ if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
+ LOGE "输入的 ${WebPort} 无效,将使用默认端口"
+ fi
+ LOGI "将使用端口 ${WebPort} 签发证书,请确保该端口已开放..."
+ ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
+ ~/.acme.sh/acme.sh --issue -d ${domain} --standalone --httpport ${WebPort}
+ if [ $? -ne 0 ]; then
+ LOGE "签发证书失败,请检查日志"
+ rm -rf ~/.acme.sh/${domain}
+ exit 1
+ else
+ LOGE "证书签发成功,正在安装证书..."
+ fi
+ ~/.acme.sh/acme.sh --installcert -d ${domain} \
+ --key-file /root/cert/${domain}/privkey.pem \
+ --fullchain-file /root/cert/${domain}/fullchain.pem
+
+ if [ $? -ne 0 ]; then
+ LOGE "安装证书失败,退出"
+ rm -rf ~/.acme.sh/${domain}
+ exit 1
+ else
+ LOGI "安装证书成功,正在启用自动续签..."
+ fi
+
+ ~/.acme.sh/acme.sh --upgrade --auto-upgrade
+ if [ $? -ne 0 ]; then
+ LOGE "自动续签失败,证书详情:"
+ ls -lah cert/*
+ chmod 755 $certPath/*
+ exit 1
+ else
+ LOGI "自动续签成功,证书详情:"
+ ls -lah cert/*
+ chmod 755 $certPath/*
+ fi
+}
+
+ssl_cert_issue_CF() {
+ echo -E ""
+ LOGD "******使用说明******"
+ echo "1) 从 Cloudflare 申请新证书"
+ echo "2) 强制续签已有证书"
+ echo "3) 返回菜单"
+ read -p "请输入你的选择 [1-3]: " choice
+
+ certPath="/root/cert-CF"
+
+ case $choice in
+ 1|2)
+ force_flag=""
+ if [ "$choice" -eq 2 ]; then
+ force_flag="--force"
+ echo "正在强制重新签发 SSL 证书..."
+ else
+ echo "开始签发 SSL 证书..."
+ fi
+
+ LOGD "******使用说明******"
+ LOGI "此 Acme 脚本需要以下数据:"
+ LOGI "1.Cloudflare 注册邮箱"
+ LOGI "2.Cloudflare 全局 API Key"
+ LOGI "3.已通过 Cloudflare 将 DNS 解析到当前服务器的域名"
+ LOGI "4.脚本将申请证书,默认安装路径为 /root/cert"
+ confirm "是否确认?[y/n]" "y"
+ if [ $? -eq 0 ]; then
+ if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
+ echo "未找到 acme.sh。正在安装..."
+ install_acme
+ if [ $? -ne 0 ]; then
+ LOGE "安装 acme 失败,请检查日志"
+ exit 1
+ fi
+ fi
+
+ CF_Domain=""
+ if [ ! -d "$certPath" ]; then
+ mkdir -p $certPath
+ else
+ rm -rf $certPath
+ mkdir -p $certPath
+ fi
+
+ LOGD "请设置域名:"
+ read -p "请在此输入域名: " CF_Domain
+ LOGD "你的域名已设置为:${CF_Domain}"
+
+ CF_GlobalKey=""
+ CF_AccountEmail=""
+ LOGD "请设置 API key:"
+ read -p "请在此输入 key: " CF_GlobalKey
+ LOGD "你的 API key 为:${CF_GlobalKey}"
+
+ LOGD "请设置注册邮箱:"
+ read -p "请在此输入邮箱: " CF_AccountEmail
+ LOGD "你的注册邮箱为:${CF_AccountEmail}"
+
+ ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
+ if [ $? -ne 0 ]; then
+ LOGE "设置默认 CA Let's Encrypt 失败,脚本退出..."
+ exit 1
+ fi
+
+ export CF_Key="${CF_GlobalKey}"
+ export CF_Email="${CF_AccountEmail}"
+
+ ~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} $force_flag --log
+ if [ $? -ne 0 ]; then
+ LOGE "证书签发失败,脚本退出..."
+ exit 1
+ else
+ LOGI "证书签发成功,正在安装..."
+ fi
+
+ mkdir -p ${certPath}/${CF_Domain}
+ if [ $? -ne 0 ]; then
+ LOGE "创建目录失败:${certPath}/${CF_Domain}"
+ exit 1
+ fi
+
+ ~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} \
+ --fullchain-file ${certPath}/${CF_Domain}/fullchain.pem \
+ --key-file ${certPath}/${CF_Domain}/privkey.pem
+
+ if [ $? -ne 0 ]; then
+ LOGE "证书安装失败,脚本退出..."
+ exit 1
+ else
+ LOGI "证书安装成功,正在开启自动更新..."
+ fi
+
+ ~/.acme.sh/acme.sh --upgrade --auto-upgrade
+ if [ $? -ne 0 ]; then
+ LOGE "自动更新设置失败,脚本退出..."
+ exit 1
+ else
+ LOGI "证书已安装,并已开启自动续签。"
+ ls -lah ${certPath}/${CF_Domain}
+ chmod 755 ${certPath}/${CF_Domain}
+ fi
+ fi
+ show_menu
+ ;;
+ 3)
+ echo "正在退出..."
+ show_menu
+ ;;
+ *)
+ echo "无效选择,请重新选择。"
+ show_menu
+ ;;
+ esac
+}
+
+generate_self_signed_cert() {
+ cert_dir="/etc/sing-box"
+ mkdir -p "$cert_dir"
+ LOGI "请选择证书类型:"
+ echo -e "${green}\t1.${plain} Ed25519(推荐)"
+ echo -e "${green}\t2.${plain} RSA 2048"
+ echo -e "${green}\t3.${plain} RSA 4096"
+ echo -e "${green}\t4.${plain} ECDSA prime256v1"
+ echo -e "${green}\t5.${plain} ECDSA secp384r1"
+ read -p "请输入你的选择 [1-5,默认 1]: " cert_type
+ cert_type=${cert_type:-1}
+
+ case "$cert_type" in
+ 1)
+ algo="ed25519"
+ key_opt="-newkey ed25519"
+ ;;
+ 2)
+ algo="rsa"
+ key_opt="-newkey rsa:2048"
+ ;;
+ 3)
+ algo="rsa"
+ key_opt="-newkey rsa:4096"
+ ;;
+ 4)
+ algo="ecdsa"
+ key_opt="-newkey ec -pkeyopt ec_paramgen_curve:prime256v1"
+ ;;
+ 5)
+ algo="ecdsa"
+ key_opt="-newkey ec -pkeyopt ec_paramgen_curve:secp384r1"
+ ;;
+ *)
+ algo="ed25519"
+ key_opt="-newkey ed25519"
+ ;;
+ esac
+
+ LOGI "正在生成自签名证书($algo)..."
+ sudo openssl req -x509 -nodes -days 3650 $key_opt \
+ -keyout "${cert_dir}/self.key" \
+ -out "${cert_dir}/self.crt" \
+ -subj "/CN=myserver"
+ if [[ $? -eq 0 ]]; then
+ sudo chmod 600 "${cert_dir}/self."*
+ LOGI "自签名证书生成成功!"
+ LOGI "证书路径:${cert_dir}/self.crt"
+ LOGI "密钥路径:${cert_dir}/self.key"
+ else
+ LOGE "生成自签名证书失败。"
+ fi
+ before_show_menu
+}
+
+show_usage() {
+ echo -e "S-UI 控制菜单用法"
+ echo -e "------------------------------------------"
+ echo -e "子命令:"
+ echo -e "s-ui - 管理员管理脚本"
+ echo -e "s-ui start - 启动 s-ui"
+ echo -e "s-ui stop - 停止 s-ui"
+ echo -e "s-ui restart - 重启 s-ui"
+ echo -e "s-ui status - 查看 s-ui 当前状态"
+ echo -e "s-ui enable - 启用开机自启"
+ echo -e "s-ui disable - 禁用开机自启"
+ echo -e "s-ui log - 查看 s-ui 日志"
+ echo -e "s-ui update - 更新"
+ echo -e "s-ui install - 安装"
+ echo -e "s-ui uninstall - 卸载"
+ echo -e "s-ui help - 控制菜单用法"
+ echo -e "------------------------------------------"
+}
+
+show_menu() {
+ echo -e "
+ ${green}S-UI 管理脚本 ${plain}
+---------------------------------------------------------------
+ ${green}0.${plain} 退出
+---------------------------------------------------------------
+ ${green}1.${plain} 安装
+ ${green}2.${plain} 更新
+ ${green}3.${plain} 自定义版本
+ ${green}4.${plain} 卸载
+---------------------------------------------------------------
+ ${green}5.${plain} 将管理员账号密码重置为默认值
+ ${green}6.${plain} 设置管理员账号密码
+ ${green}7.${plain} 查看管理员账号密码
+---------------------------------------------------------------
+ ${green}8.${plain} 重置面板设置
+ ${green}9.${plain} 设置面板设置
+ ${green}10.${plain} 查看面板设置
+---------------------------------------------------------------
+ ${green}11.${plain} 启动 S-UI
+ ${green}12.${plain} 停止 S-UI
+ ${green}13.${plain} 重启 S-UI
+ ${green}14.${plain} 查看 S-UI 状态
+ ${green}15.${plain} 查看 S-UI 日志
+ ${green}16.${plain} 启用 S-UI 开机自启
+ ${green}17.${plain} 禁用 S-UI 开机自启
+---------------------------------------------------------------
+ ${green}18.${plain} 启用或禁用 BBR
+ ${green}19.${plain} SSL 证书管理
+ ${green}20.${plain} Cloudflare SSL 证书
+---------------------------------------------------------------
+ "
+ show_status s-ui
+ echo && read -p "请输入你的选择 [0-20]: " num
+
+ case "${num}" in
+ 0)
+ exit 0
+ ;;
+ 1)
+ check_uninstall && install
+ ;;
+ 2)
+ check_install && update
+ ;;
+ 3)
+ check_install && custom_version
+ ;;
+ 4)
+ check_install && uninstall
+ ;;
+ 5)
+ check_install && reset_admin
+ ;;
+ 6)
+ check_install && set_admin
+ ;;
+ 7)
+ check_install && view_admin
+ ;;
+ 8)
+ check_install && reset_setting
+ ;;
+ 9)
+ check_install && set_setting
+ ;;
+ 10)
+ check_install && view_setting
+ ;;
+ 11)
+ check_install && start s-ui
+ ;;
+ 12)
+ check_install && stop s-ui
+ ;;
+ 13)
+ check_install && restart s-ui
+ ;;
+ 14)
+ check_install && status s-ui
+ ;;
+ 15)
+ check_install && show_log s-ui
+ ;;
+ 16)
+ check_install && enable s-ui
+ ;;
+ 17)
+ check_install && disable s-ui
+ ;;
+ 18)
+ bbr_menu
+ ;;
+ 19)
+ ssl_cert_issue_main
+ ;;
+ 20)
+ ssl_cert_issue_CF
+ ;;
+ *)
+ LOGE "请输入正确的数字 [0-20]"
+ ;;
+ esac
+}
+
+if [[ $# > 0 ]]; then
+ case $1 in
+ "start")
+ check_install 0 && start s-ui 0
+ ;;
+ "stop")
+ check_install 0 && stop s-ui 0
+ ;;
+ "restart")
+ check_install 0 && restart s-ui 0
+ ;;
+ "status")
+ check_install 0 && status 0
+ ;;
+ "enable")
+ check_install 0 && enable s-ui 0
+ ;;
+ "disable")
+ check_install 0 && disable s-ui 0
+ ;;
+ "log")
+ check_install 0 && show_log s-ui 0
+ ;;
+ "update")
+ check_install 0 && update 0
+ ;;
+ "install")
+ check_uninstall 0 && install 0
+ ;;
+ "uninstall")
+ check_install 0 && uninstall 0
+ ;;
+ *) show_usage ;;
+ esac
+else
+ show_menu
+fi