From 39d18aabd1f45414f496cd157a46ff6752eeff3e Mon Sep 17 00:00:00 2001 From: admin8800 Date: Sun, 10 May 2026 15:28:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E4=B8=BA=E6=89=8B=E5=8A=A8=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E6=B5=81=E6=B0=B4=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker.yml | 340 +++--- .github/workflows/release.yml | 395 ++++--- .github/workflows/windows.yml | 272 +++-- README.md | 498 +++++---- docker-compose.yml | 7 +- install.sh | 376 +++---- s-ui.sh | 1868 ++++++++++++++++----------------- 7 files changed, 1868 insertions(+), 1888 deletions(-) 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://img.shields.io/github/v/release/alireza0/s-ui.svg) -![S-UI Docker pull](https://img.shields.io/docker/pulls/alireza7/s-ui.svg) -[![Go Report Card](https://goreportcard.com/badge/github.com/admin8800/s-ui)](https://goreportcard.com/report/github.com/admin8800/s-ui) -[![Downloads](https://img.shields.io/github/downloads/alireza0/s-ui/total.svg)](https://img.shields.io/github/downloads/alireza0/s-ui/total.svg) -[![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](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. - -[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/alireza7) - - - Crypto donation button by NOWPayments - - -## 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 - -!["Main"](https://github.com/admin8800/s-ui-frontend/raw/main/media/main.png) - -[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 -[![Stargazers over time](https://starchart.cc/alireza0/s-ui.svg)](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 | 实验性支持 | + +## 截图 + +!["主界面"](https://github.com/admin8800/s-ui-frontend/raw/main/media/main.png) + +## 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