Compare commits

...

87 Commits

Author SHA1 Message Date
Alireza Ahmadi 13117843ec v1.3.0-rc.1 2025-07-26 20:18:06 +02:00
Alireza Ahmadi 60b0b3c878 fix dockerhub push 2025-07-26 09:59:26 +02:00
Alireza Ahmadi 825a8d9fd9 fix install script on oracle linux #680 2025-07-26 09:58:19 +02:00
Alireza Ahmadi 58105be433 v1.3.0-rc.0 2025-07-24 20:50:32 +02:00
Alireza Ahmadi 98db6d2445 sing-box v1.12.0-rc.3 2025-07-24 20:46:16 +02:00
Alireza Ahmadi cd3d4e6451 fix add bulk client 2025-07-24 15:11:50 +02:00
Alireza Ahmadi 1e3d1b9ed3 faster docker build 2025-07-20 15:36:53 +02:00
Alireza Ahmadi a794cace54 go 1.24.5 2025-07-19 22:01:26 +02:00
Alireza Ahmadi 6520a8dc9c v1.3.0-beta.5 2025-07-18 21:35:11 +02:00
Alireza Ahmadi 8ccd60cb74 Merge pull request #667 from Shellgate/sing-box-v.1.12
افزودن گواهی خود امضا
2025-07-18 21:25:06 +02:00
Alireza Ahmadi c9d89540d3 sing-box v1.12.0-beta.34 2025-07-18 19:47:45 +02:00
Alireza Ahmadi c2d33d2a1e revert back to normal restart inbounds 2025-07-18 19:47:29 +02:00
Alireza Ahmadi fe4fa9b9e6 fix client links #670 #671 2025-07-18 19:45:55 +02:00
Shellgate 1d23f5a1df Update s-ui.sh 2025-07-13 20:19:09 +03:30
Alireza Ahmadi 349d490a65 v1.3.0-beta.4 2025-07-13 12:32:02 +02:00
Alireza Ahmadi 11326d7cc1 update dependencies 2025-07-13 12:31:10 +02:00
Alireza Ahmadi d2827d013b improve client's inbound changes 2025-07-13 12:29:21 +02:00
Alireza Ahmadi f239574e41 v1.3.0-beta.3 2025-07-08 00:17:24 +02:00
Alireza Ahmadi bc05aed51f update frontend 2025-07-08 00:15:27 +02:00
Alireza Ahmadi ff791d0a27 update packages 2025-07-08 00:15:12 +02:00
Alireza Ahmadi 319e3b1eba use musl gcc for docker #651
Co-authored-by: @elseif
2025-07-08 00:13:14 +02:00
Alireza Ahmadi 12a24ec617 sing-box v1.12.0-beta.24 2025-06-13 01:30:38 +02:00
Alireza Ahmadi 92c742987e fix old link removal on inbound tag change #633 2025-06-13 00:57:45 +02:00
Alireza Ahmadi 4dabe656c9 disk and swap info #341 2025-06-11 03:26:48 +02:00
Alireza Ahmadi 03fff53260 UI screenshots #82 #366 2025-06-11 01:53:40 +02:00
Alireza Ahmadi f65cb2ca06 fix http-opts path in clash sub 2025-06-07 01:44:31 +02:00
Alireza Ahmadi 36938aee41 add migration for anytls config of clients 2025-06-07 01:32:58 +02:00
Alireza Ahmadi d82af6f9bd v1.3.0-beta.2 2025-06-07 00:06:48 +02:00
Alireza Ahmadi 6b785c3404 v1.3.0-beta.1 2025-06-06 02:27:46 +02:00
Alireza Ahmadi df1a271efa migration to 1.3 with singbox 1.12 2025-06-06 02:23:00 +02:00
Alireza Ahmadi bd9bd8590c singbox v1.12.0-beta.21 2025-06-05 22:33:34 +02:00
Alireza Ahmadi d186875ab7 clash - stash subscription #373 2025-06-01 23:50:45 +02:00
Alireza Ahmadi 3f7657c080 adjust subJson 2025-05-31 20:34:40 +02:00
Alireza Ahmadi a5f4c46066 support anytls link #611 2025-05-30 23:05:55 +02:00
Alireza Ahmadi 596dc8a884 v1.3.0-beta.0 2025-05-30 00:21:31 +02:00
Alireza Ahmadi 6c97ad8871 clash api and v2ray api #468 2025-05-29 23:49:09 +02:00
Alireza Ahmadi 5b77dded66 update dependencies 2025-05-29 21:59:58 +02:00
Alireza Ahmadi 73cf4d5b7e new protocol anytls 2025-05-29 21:51:20 +02:00
Alireza Ahmadi 1991091444 small enhancement on managed shadowsocks detection 2025-05-29 21:50:30 +02:00
Alireza Ahmadi f69c74b09c update dependencies 2025-05-29 14:21:25 +02:00
Alireza Ahmadi 118baf12df shadowsocks manageable 2025-05-28 23:00:40 +02:00
Alireza Ahmadi fc410c9a8d update new features service-dns 2025-05-28 23:00:19 +02:00
Alireza Ahmadi d873c86ef8 remove ech pqs 2025-05-27 22:00:58 +02:00
Alireza Ahmadi 855a838599 fix file log writer #506 2025-05-27 22:00:58 +02:00
Alireza Ahmadi 354378e038 fix ss-tls init users #530 2025-05-27 22:00:58 +02:00
Alireza Ahmadi 8b431f4da8 fix reality sid #495 2025-05-27 22:00:58 +02:00
Alireza Ahmadi 0a08e9f834 fix init users and naive #553 2025-05-27 22:00:58 +02:00
Shellgate a10950499b Add force option and some optimization (#505)
Better Cloudflare certs
2025-05-27 21:54:58 +02:00
Alireza Ahmadi bac2580be7 fix create core object's context 2025-02-22 14:18:01 +01:00
Alireza Ahmadi d21deda218 fix outbound context #492 2025-02-22 13:51:12 +01:00
Alireza Ahmadi 6ad2a7af70 only allow shadowsocks multi-user #489 2025-02-22 12:43:11 +01:00
Alireza Ahmadi 59d2c652e6 v1.2.2 2025-02-15 22:42:32 +01:00
Alireza Ahmadi 1c0c5f61c6 warp code enhancement 2025-02-15 22:31:13 +01:00
Alireza Ahmadi 97d3b10e2f Merge pull request #475 from alireza0/dependabot/go_modules/github.com/sagernet/sing-box-1.11.3
Bump github.com/sagernet/sing-box from 1.11.1 to 1.11.3
2025-02-15 22:22:52 +01:00
dependabot[bot] d50695067e Bump github.com/sagernet/sing-box from 1.11.1 to 1.11.3
Bumps [github.com/sagernet/sing-box](https://github.com/sagernet/sing-box) from 1.11.1 to 1.11.3.
- [Release notes](https://github.com/sagernet/sing-box/releases)
- [Changelog](https://github.com/SagerNet/sing-box/blob/v1.11.3/docs/changelog.md)
- [Commits](https://github.com/sagernet/sing-box/compare/v1.11.1...v1.11.3)

---
updated-dependencies:
- dependency-name: github.com/sagernet/sing-box
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-15 21:22:42 +00:00
Alireza Ahmadi 5bfd60176f Merge pull request #470 from alireza0/dependabot/go_modules/github.com/sagernet/sing-0.6.1
Bump github.com/sagernet/sing from 0.6.0 to 0.6.1
2025-02-15 22:21:26 +01:00
Alireza Ahmadi 9dd63f83da cmd show sing-box version #479 2025-02-15 22:02:58 +01:00
Alireza Ahmadi 045f368c27 fix host in v2ray links #474 2025-02-15 21:30:12 +01:00
Alireza Ahmadi a1e9ef00a1 fix vlass flow #474 2025-02-15 21:29:20 +01:00
Alireza Ahmadi 11215b96ae fix fingerprint in links #469 2025-02-15 14:19:35 +01:00
Alireza Ahmadi 1535338e0b fix jsonsub required rules #451 2025-02-15 14:13:54 +01:00
dependabot[bot] f6be2dd12e Bump github.com/sagernet/sing from 0.6.0 to 0.6.1
Bumps [github.com/sagernet/sing](https://github.com/sagernet/sing) from 0.6.0 to 0.6.1.
- [Commits](https://github.com/sagernet/sing/compare/v0.6.0...v0.6.1)

---
updated-dependencies:
- dependency-name: github.com/sagernet/sing
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 17:02:19 +00:00
Alireza Ahmadi 66c3f142a7 v1.2.1 2025-02-10 00:45:03 +01:00
Alireza Ahmadi e197a7081b fix exceptions for outjson #459 2025-02-09 14:27:27 +01:00
Alireza Ahmadi 875c660fb2 fix vless flow in reality links #463 2025-02-09 02:17:41 +01:00
Alireza Ahmadi f233f1c6b6 fix utl, flow and insecure in links #463 #445 2025-02-09 02:16:26 +01:00
Alireza Ahmadi 1c4a927e0d small fixes and typos 2025-02-09 00:56:13 +01:00
Alireza Ahmadi ea6ceac2f2 fix backup empty tables 2025-02-09 00:00:33 +01:00
Alireza Ahmadi 99d3cc5c6d Merge pull request #452 from alireza0/dependabot/go_modules/github.com/sagernet/sing-box-1.11.1
Bump github.com/sagernet/sing-box from 1.11.0 to 1.11.1
2025-02-08 17:46:48 +01:00
dependabot[bot] 95855092fd Bump github.com/sagernet/sing-box from 1.11.0 to 1.11.1
Bumps [github.com/sagernet/sing-box](https://github.com/sagernet/sing-box) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/sagernet/sing-box/releases)
- [Changelog](https://github.com/SagerNet/sing-box/blob/dev-next/docs/changelog.md)
- [Commits](https://github.com/sagernet/sing-box/compare/v1.11.0...v1.11.1)

---
updated-dependencies:
- dependency-name: github.com/sagernet/sing-box
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-08 16:46:22 +00:00
Alireza Ahmadi b1d3cfab1c Merge pull request #456 from alireza0/dependabot/go_modules/github.com/sagernet/sing-dns-0.4.0
Bump github.com/sagernet/sing-dns from 0.4.0-beta.2 to 0.4.0
2025-02-08 17:45:16 +01:00
Alireza Ahmadi 4af00b560f Merge pull request #457 from alireza0/dependabot/go_modules/github.com/sagernet/sing-0.6.0
Bump github.com/sagernet/sing from 0.6.0-beta.12 to 0.6.0
2025-02-08 17:45:06 +01:00
Alireza Ahmadi 1ccbbf14dc fix tls override in json sub 2025-02-08 17:23:49 +01:00
dependabot[bot] 917c2aa734 Bump github.com/sagernet/sing from 0.6.0-beta.12 to 0.6.0
Bumps [github.com/sagernet/sing](https://github.com/sagernet/sing) from 0.6.0-beta.12 to 0.6.0.
- [Commits](https://github.com/sagernet/sing/compare/v0.6.0-beta.12...v0.6.0)

---
updated-dependencies:
- dependency-name: github.com/sagernet/sing
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-06 16:52:56 +00:00
dependabot[bot] 17fe80bd9b Bump github.com/sagernet/sing-dns from 0.4.0-beta.2 to 0.4.0
Bumps [github.com/sagernet/sing-dns](https://github.com/sagernet/sing-dns) from 0.4.0-beta.2 to 0.4.0.
- [Commits](https://github.com/sagernet/sing-dns/compare/v0.4.0-beta.2...v0.4.0)

---
updated-dependencies:
- dependency-name: github.com/sagernet/sing-dns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-06 16:52:52 +00:00
Alireza Ahmadi f0a7481d72 fix empty outJson error 2025-02-06 11:48:41 +01:00
Alireza Ahmadi 4b1654e3eb fix docker pipeline 2025-02-06 11:48:08 +01:00
Alireza Ahmadi 6304f4b263 v1.2.0 2025-02-05 08:57:15 +01:00
Alireza Ahmadi ba225aedc7 Merge pull request #442 from alireza0/dependabot/go_modules/github.com/shirou/gopsutil/v4-4.25.1
Bump github.com/shirou/gopsutil/v4 from 4.24.12 to 4.25.1
2025-02-04 23:34:31 +01:00
Alireza Ahmadi e4e692abdd Merge pull request #444 from alireza0/dependabot/go_modules/github.com/gin-contrib/gzip-1.2.2
Bump github.com/gin-contrib/gzip from 1.0.1 to 1.2.2
2025-02-04 23:34:17 +01:00
dependabot[bot] b3c26a2af2 Bump github.com/gin-contrib/gzip from 1.0.1 to 1.2.2
Bumps [github.com/gin-contrib/gzip](https://github.com/gin-contrib/gzip) from 1.0.1 to 1.2.2.
- [Release notes](https://github.com/gin-contrib/gzip/releases)
- [Changelog](https://github.com/gin-contrib/gzip/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/gin-contrib/gzip/compare/v1.0.1...v1.2.2)

---
updated-dependencies:
- dependency-name: github.com/gin-contrib/gzip
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 22:33:07 +00:00
Alireza Ahmadi bf0df2f625 Merge pull request #443 from alireza0/dependabot/go_modules/github.com/gin-contrib/sessions-1.0.2
Bump github.com/gin-contrib/sessions from 1.0.1 to 1.0.2
2025-02-04 23:31:57 +01:00
Alireza Ahmadi 7af80ae577 Merge pull request #440 from alireza0/dependabot/github_actions/actions/checkout-4.2.2
Bump actions/checkout from 4.1.1 to 4.2.2
2025-02-04 23:31:37 +01:00
dependabot[bot] 1a0c01c092 Bump github.com/gin-contrib/sessions from 1.0.1 to 1.0.2
Bumps [github.com/gin-contrib/sessions](https://github.com/gin-contrib/sessions) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/gin-contrib/sessions/releases)
- [Changelog](https://github.com/gin-contrib/sessions/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/gin-contrib/sessions/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: github.com/gin-contrib/sessions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 22:31:37 +00:00
dependabot[bot] 0e82b0442a Bump github.com/shirou/gopsutil/v4 from 4.24.12 to 4.25.1
Bumps [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil) from 4.24.12 to 4.25.1.
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.24.12...v4.25.1)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 22:31:33 +00:00
dependabot[bot] 11970cb514 Bump actions/checkout from 4.1.1 to 4.2.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.2.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.1...v4.2.2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 22:30:59 +00:00
Alireza Ahmadi 928fc228d4 Merge pull request #439 from alireza0/consolidation
Consolidation
2025-02-04 23:30:21 +01:00
44 changed files with 2321 additions and 746 deletions
+42 -10
View File
@@ -6,15 +6,39 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build: frontend-build:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4.2.2
with:
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies and build frontend
run: |
cd frontend
npm install
npm run build
- name: Upload frontend build artifact
uses: actions/upload-artifact@v4
with:
name: frontend-dist
path: frontend/dist/
build:
needs: frontend-build
runs-on: ubuntu-22.04
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.2.2
- name: Download frontend build artifact
uses: actions/download-artifact@v4
with: with:
submodules: recursive name: frontend-dist
path: frontend_dist
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -26,31 +50,39 @@ jobs:
type=ref,event=branch type=ref,event=branch
type=ref,event=tag type=ref,event=tag
type=pep440,pattern={{version}} type=pep440,pattern={{version}}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
with:
install: true
buildkitd-flags: --debug
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKER_HUB_USERNAME }} username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }} password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Login to GHCR - name: Login to GHCR
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
file: Dockerfile.frontend-artifact
push: true push: true
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386 platforms: linux/amd64, linux/386, linux/arm64/v8, linux/arm/v7, linux/arm/v6
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache,mode=max
+2 -2
View File
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.2.2
with: with:
submodules: recursive submodules: recursive
@@ -92,7 +92,7 @@ jobs:
fi fi
### Build s-ui ### Build s-ui
go build -ldflags="-w -s" -tags "with_quic,with_grpc,with_ech,with_utls,with_reality_server,with_acme,with_gvisor" -o sui main.go go build -ldflags="-w -s" -tags "with_quic,with_grpc,with_utls,with_acme,with_gvisor" -o sui main.go
mkdir s-ui mkdir s-ui
cp sui s-ui/ cp sui s-ui/
+22 -7
View File
@@ -3,23 +3,38 @@ WORKDIR /app
COPY frontend/ ./ COPY frontend/ ./
RUN npm install && npm run build RUN npm install && npm run build
FROM golang:1.23-alpine AS backend-builder FROM golang:1.24-alpine AS backend-builder
WORKDIR /app WORKDIR /app
ARG TARGETARCH ARG TARGETARCH
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
ENV CGO_ENABLED=1 ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
ENV GOARCH=$TARGETARCH ENV GOARCH=$TARGETARCH
RUN apk update && apk --no-cache --update add build-base gcc wget unzip
RUN apk update && apk add --no-cache \
gcc \
musl-dev \
libc-dev \
make \
git \
wget \
unzip \
bash
ENV CC=gcc
COPY . . COPY . .
COPY --from=front-builder /app/dist/ /app/web/html/ COPY --from=front-builder /app/dist/ /app/web/html/
RUN go build -ldflags="-w -s" -tags "with_quic,with_grpc,with_ech,with_utls,with_reality_server,with_acme,with_gvisor" -o sui main.go
RUN go build -ldflags="-w -s" \
-tags "with_quic,with_grpc,with_utls,with_acme,with_gvisor" \
-o sui main.go
FROM --platform=$TARGETPLATFORM alpine FROM --platform=$TARGETPLATFORM alpine
LABEL org.opencontainers.image.authors="alireza7@gmail.com" LABEL org.opencontainers.image.authors="alireza7@gmail.com"
ENV TZ=Asia/Tehran ENV TZ=Asia/Tehran
WORKDIR /app WORKDIR /app
RUN apk add --no-cache --update ca-certificates tzdata RUN apk add --no-cache --update ca-certificates tzdata
COPY --from=backend-builder /app/sui /app/ COPY --from=backend-builder /app/sui /app/
COPY entrypoint.sh /app/ COPY entrypoint.sh /app/
VOLUME [ "s-ui" ] VOLUME [ "s-ui" ]
ENTRYPOINT [ "./entrypoint.sh" ] ENTRYPOINT [ "./entrypoint.sh" ]
+36
View File
@@ -0,0 +1,36 @@
FROM golang:1.24-alpine AS backend-builder
WORKDIR /app
ARG TARGETARCH
ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
ENV GOARCH=$TARGETARCH
RUN apk update && apk add --no-cache \
gcc \
musl-dev \
libc-dev \
make \
git \
wget \
unzip \
bash
ENV CC=gcc
COPY . .
# Copy pre-built frontend files from a known location (provided by workflow artifact)
COPY frontend_dist/ /app/web/html/
RUN go build -ldflags="-w -s" \
-tags "with_quic,with_grpc,with_utls,with_acme,with_gvisor" \
-o sui main.go
FROM --platform=$TARGETPLATFORM alpine
LABEL org.opencontainers.image.authors="alireza7@gmail.com"
ENV TZ=Asia/Tehran
WORKDIR /app
RUN apk add --no-cache --update ca-certificates tzdata
COPY --from=backend-builder /app/sui /app/
COPY entrypoint.sh /app/
VOLUME [ "s-ui" ]
ENTRYPOINT [ "./entrypoint.sh" ]
+6 -1
View File
@@ -3,7 +3,6 @@
![](https://img.shields.io/github/v/release/alireza0/s-ui.svg) ![](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) ![S-UI Docker pull](https://img.shields.io/docker/pulls/alireza7/s-ui.svg)
![S-UI-Singbox Docker pull](https://img.shields.io/docker/pulls/alireza7/s-ui-singbox.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/alireza0/s-ui)](https://goreportcard.com/report/github.com/alireza0/s-ui) [![Go Report Card](https://goreportcard.com/badge/github.com/alireza0/s-ui)](https://goreportcard.com/report/github.com/alireza0/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) [![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) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html)
@@ -28,6 +27,12 @@
| Dark/Light Theme | :heavy_check_mark: | | Dark/Light Theme | :heavy_check_mark: |
| API Interface | :heavy_check_mark: | | API Interface | :heavy_check_mark: |
## Screenshots
!["Main"](https://github.com/alireza0/s-ui-frontend/raw/main/media/main.png)
[Other UI Screenshots](https://github.com/alireza0/s-ui-frontend/blob/main/screenshots.md)
## API Documentation ## API Documentation
[API-Documentation Wiki](https://github.com/alireza0/s-ui/wiki/API-Documentation) [API-Documentation Wiki](https://github.com/alireza0/s-ui/wiki/API-Documentation)
+12
View File
@@ -22,6 +22,7 @@ type ApiService struct {
service.InboundService service.InboundService
service.OutboundService service.OutboundService
service.EndpointService service.EndpointService
service.ServicesService
service.PanelService service.PanelService
service.StatsService service.StatsService
service.ServerService service.ServerService
@@ -81,6 +82,10 @@ func (a *ApiService) getData(c *gin.Context) (interface{}, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
services, err := a.ServicesService.GetAll()
if err != nil {
return "", err
}
subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0]) subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0])
if err != nil { if err != nil {
return "", err return "", err
@@ -91,6 +96,7 @@ func (a *ApiService) getData(c *gin.Context) (interface{}, error) {
data["inbounds"] = inbounds data["inbounds"] = inbounds
data["outbounds"] = outbounds data["outbounds"] = outbounds
data["endpoints"] = endpoints data["endpoints"] = endpoints
data["services"] = services
data["subURI"] = subURI data["subURI"] = subURI
data["onlines"] = onlines data["onlines"] = onlines
} else { } else {
@@ -124,6 +130,12 @@ func (a *ApiService) LoadPartialData(c *gin.Context, objs []string) error {
return err return err
} }
data[obj] = endpoints data[obj] = endpoints
case "services":
services, err := a.ServicesService.GetAll()
if err != nil {
return err
}
data[obj] = services
case "tls": case "tls":
tlsConfigs, err := a.TlsService.GetAll() tlsConfigs, err := a.TlsService.GetAll()
if err != nil { if err != nil {
+1 -1
View File
@@ -11,4 +11,4 @@ mkdir -p web/html
rm -fr web/html/* rm -fr web/html/*
cp -R frontend/dist/* web/html/ cp -R frontend/dist/* web/html/
go build -ldflags "-w -s" -tags "with_quic,with_grpc,with_ech,with_utls,with_reality_server,with_acme,with_gvisor" -o sui main.go go build -ldflags "-w -s" -tags "with_quic,with_grpc,with_utls,with_acme,with_gvisor" -o sui main.go
+11 -1
View File
@@ -4,6 +4,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"os" "os"
"runtime/debug"
"s-ui/cmd/migration" "s-ui/cmd/migration"
"s-ui/config" "s-ui/config"
) )
@@ -52,7 +53,16 @@ func ParseCmd() {
flag.Parse() flag.Parse()
if showVersion { if showVersion {
fmt.Println(config.GetVersion()) fmt.Println("S-UI Panel\t", config.GetVersion())
info, ok := debug.ReadBuildInfo()
if ok {
for _, dep := range info.Deps {
if dep.Path == "github.com/sagernet/sing-box" {
fmt.Println("Sing-Box\t", dep.Version)
break
}
}
}
return return
} }
+154
View File
@@ -0,0 +1,154 @@
package migration
import (
"encoding/json"
"net/url"
"s-ui/database/model"
"strconv"
"strings"
"gorm.io/gorm"
)
func migrate_dns(db *gorm.DB) error {
var configStr string
err := db.Model(model.Setting{}).Select("value").Where("key = ?", "config").First(&configStr).Error
if err != nil {
return err
}
if configStr == "" {
return nil
}
var config map[string]interface{}
err = json.Unmarshal([]byte(configStr), &config)
if err != nil {
return err
}
if dnsConfig, ok := config["dns"].(map[string]interface{}); ok {
if dnsServers, ok := dnsConfig["servers"].([]interface{}); ok {
for index, dnsServer := range dnsServers {
if dnsServer, ok := dnsServer.(map[string]interface{}); ok {
if addr, ok := dnsServer["address"].(string); ok && addr != "" {
switch addr {
case "local":
delete(dnsServer, "address")
dnsServer["type"] = "local"
case "fakeip":
delete(dnsServer, "address")
dnsServer["type"] = "fakeip"
default:
addrParsed, err := url.Parse(addr)
if err != nil {
continue
}
switch addrParsed.Scheme {
case "":
dnsServer["type"] = "udp"
dnsServer["server"] = addr
case "udp", "tcp", "tls", "quic", "https", "h3":
dnsServer["type"] = addrParsed.Scheme
dnsServer["server"] = addrParsed.Host
case "dhcp":
dnsServer["type"] = addrParsed.Scheme
if addrParsed.Host != "auto" && addrParsed.Host != "" {
dnsServer["interface"] = addrParsed.Host
}
case "rcode":
dnsServer["type"] = "predefined"
dnsServer["responses"] = []map[string]string{
{
"rcode": strings.ToUpper(addrParsed.Host),
},
}
}
delete(dnsServer, "address")
if addrParsed.Port() != "" {
port, err := strconv.Atoi(addrParsed.Port())
if err == nil {
dnsServer["server_port"] = port
}
}
if address_resolver, ok := dnsServer["address_resolver"].(string); ok && address_resolver != "" {
delete(dnsServer, "address_resolver")
dnsServer["domain_resolver"] = address_resolver
}
delete(dnsServer, "strategy")
}
dnsServers[index] = dnsServer
}
}
}
dnsConfig["servers"] = dnsServers
}
config["dns"] = dnsConfig
} else {
return nil
}
// save changes
configs, err := json.MarshalIndent(config, "", " ")
if err != nil {
return err
}
return db.Model(model.Setting{}).Where("key = ?", "config").Update("value", string(configs)).Error
}
func remove_outbound_strategy(db *gorm.DB) error {
var outbounds []model.Outbound
err := db.Find(&outbounds).Where("json_extract(options, '$.domain_strategy') IS NOT NULL").Error
if err != nil {
return err
}
for _, outbound := range outbounds {
var restFields map[string]json.RawMessage
if err := json.Unmarshal(outbound.Options, &restFields); err != nil {
return err
}
delete(restFields, "domain_strategy")
outbound.Options, _ = json.MarshalIndent(restFields, "", " ")
db.Save(&outbound)
}
return nil
}
func anytls_user_config(db *gorm.DB) error {
var clients []model.Client
err := db.Model(model.Client{}).Find(&clients).Error
if err != nil {
return err
}
for index, client := range clients {
var configs map[string]json.RawMessage
if err := json.Unmarshal(client.Config, &configs); err != nil {
return err
}
if configs["anytls"] != nil {
continue
}
configs["anytls"] = configs["trojan"]
configJson, err := json.MarshalIndent(configs, "", " ")
if err != nil {
return err
}
clients[index].Config = configJson
db.Save(&clients[index])
}
return nil
}
func to1_3(db *gorm.DB) error {
err := anytls_user_config(db)
if err != nil {
return err
}
err = migrate_dns(db)
if err != nil {
return err
}
err = remove_outbound_strategy(db)
if err != nil {
return err
}
return nil
}
+11 -1
View File
@@ -56,10 +56,20 @@ func MigrateDb() {
log.Fatal("Migration to 1.2 failed: ", err) log.Fatal("Migration to 1.2 failed: ", err)
return return
} }
dbVersion = "1.2"
}
// Before 1.3
if dbVersion[0:3] == "1.2" {
err = to1_3(tx)
if err != nil {
log.Fatal("Migration to 1.3 failed: ", err)
return
}
} }
// Set version // Set version
err = tx.Raw("UPDATE settings SET value = ? WHERE key = ?", currentVersion, "version").Error err = tx.Exec("UPDATE settings SET value = ? WHERE key = ?", currentVersion, "version").Error
if err != nil { if err != nil {
log.Fatal("Update version failed: ", err) log.Fatal("Update version failed: ", err)
return return
+1 -1
View File
@@ -51,7 +51,7 @@ func GetDBFolderPath() string {
if dbFolderPath == "" { if dbFolderPath == "" {
dir, err := filepath.Abs(filepath.Dir(os.Args[0])) dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil { if err != nil {
dbFolderPath = "/usr/local/s-ui/db" return "/usr/local/s-ui/db"
} }
dbFolderPath = dir + "/db" dbFolderPath = dir + "/db"
} }
+1 -1
View File
@@ -1 +1 @@
1.2.0-rc0 1.3.0-rc.1
+176 -49
View File
@@ -12,9 +12,14 @@ import (
"github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/adapter/outbound"
boxService "github.com/sagernet/sing-box/adapter/service"
"github.com/sagernet/sing-box/common/certificate"
"github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/taskmonitor" "github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/dns/transport/local"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/experimental/cachefile" "github.com/sagernet/sing-box/experimental/cachefile"
"github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
@@ -28,21 +33,24 @@ import (
"github.com/sagernet/sing/service/pause" "github.com/sagernet/sing/service/pause"
) )
var _ adapter.Service = (*Box)(nil) var _ adapter.SimpleLifecycle = (*Box)(nil)
type Box struct { type Box struct {
createdAt time.Time createdAt time.Time
logFactory log.Factory logFactory log.Factory
logger log.ContextLogger logger log.ContextLogger
network *route.NetworkManager network *route.NetworkManager
endpoint *endpoint.Manager endpoint *endpoint.Manager
inbound *inbound.Manager inbound *inbound.Manager
outbound *outbound.Manager outbound *outbound.Manager
connection *route.ConnectionManager service *boxService.Manager
router *route.Router dnsTransport *dns.TransportManager
services []adapter.LifecycleService dnsRouter *dns.Router
connTracker *ConnTracker connection *route.ConnectionManager
done chan struct{} router *route.Router
internalService []adapter.LifecycleService
connTracker *ConnTracker
done chan struct{}
} }
type Options struct { type Options struct {
@@ -55,6 +63,8 @@ func Context(
inboundRegistry adapter.InboundRegistry, inboundRegistry adapter.InboundRegistry,
outboundRegistry adapter.OutboundRegistry, outboundRegistry adapter.OutboundRegistry,
endpointRegistry adapter.EndpointRegistry, endpointRegistry adapter.EndpointRegistry,
dnsTransportRegistry adapter.DNSTransportRegistry,
serviceRegistry adapter.ServiceRegistry,
) context.Context { ) context.Context {
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil || if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.InboundRegistry](ctx) == nil { service.FromContext[adapter.InboundRegistry](ctx) == nil {
@@ -71,6 +81,14 @@ func Context(
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry) ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry) ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
} }
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
}
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
}
return ctx return ctx
} }
@@ -86,6 +104,8 @@ func NewBox(options Options) (*Box, error) {
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx) endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
if endpointRegistry == nil { if endpointRegistry == nil {
return nil, common.NewError("missing endpoint registry in context") return nil, common.NewError("missing endpoint registry in context")
@@ -96,13 +116,27 @@ func NewBox(options Options) (*Box, error) {
if outboundRegistry == nil { if outboundRegistry == nil {
return nil, common.NewError("missing outbound registry in context") return nil, common.NewError("missing outbound registry in context")
} }
if dnsTransportRegistry == nil {
return nil, common.NewError("missing DNS transport registry in context")
}
if serviceRegistry == nil {
return nil, common.NewError("missing service registry in context")
}
ctx = pause.WithDefaultManager(ctx) ctx = pause.WithDefaultManager(ctx)
experimentalOptions := sbCommon.PtrValueOrDefault(options.Experimental) experimentalOptions := sbCommon.PtrValueOrDefault(options.Experimental)
var needCacheFile bool var needCacheFile bool
var needClashAPI bool
var needV2RayAPI bool
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled { if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled {
needCacheFile = true needCacheFile = true
} }
if experimentalOptions.ClashAPI != nil {
needClashAPI = true
}
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
platformInterface := service.FromContext[platform.Interface](ctx) platformInterface := service.FromContext[platform.Interface](ctx)
var defaultLogWriter io.Writer var defaultLogWriter io.Writer
if platformInterface != nil { if platformInterface != nil {
@@ -120,13 +154,36 @@ func NewBox(options Options) (*Box, error) {
} }
factory = logFactory factory = logFactory
var internalServices []adapter.LifecycleService
certificateOptions := sbCommon.PtrValueOrDefault(options.Certificate)
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
len(certificateOptions.Certificate) > 0 ||
len(certificateOptions.CertificatePath) > 0 ||
len(certificateOptions.CertificateDirectoryPath) > 0 {
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
if err != nil {
return nil, err
}
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
internalServices = append(internalServices, certificateStore)
}
routeOptions := sbCommon.PtrValueOrDefault(options.Route) routeOptions := sbCommon.PtrValueOrDefault(options.Route)
dnsOptions := sbCommon.PtrValueOrDefault(options.DNS)
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry) endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager) inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final) outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
service.MustRegister[adapter.EndpointManager](ctx, endpointManager) service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
service.MustRegister[adapter.InboundManager](ctx, inboundManager) service.MustRegister[adapter.InboundManager](ctx, inboundManager)
service.MustRegister[adapter.OutboundManager](ctx, outboundManager) service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions) networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
if err != nil { if err != nil {
@@ -135,10 +192,34 @@ func NewBox(options Options) (*Box, error) {
service.MustRegister[adapter.NetworkManager](ctx, networkManager) service.MustRegister[adapter.NetworkManager](ctx, networkManager)
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection")) connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager) service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
router, err := route.NewRouter(ctx, logFactory, routeOptions, sbCommon.PtrValueOrDefault(options.DNS)) router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
service.MustRegister[adapter.Router](ctx, router)
err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet)
if err != nil { if err != nil {
return nil, common.NewError("initialize router", err) return nil, common.NewError("initialize router", err)
} }
for i, transportOptions := range dnsOptions.Servers {
var tag string
if transportOptions.Tag != "" {
tag = transportOptions.Tag
} else {
tag = F.ToString(i)
}
err = dnsTransportManager.Create(
ctx,
logFactory.NewLogger(F.ToString("dns/", transportOptions.Type, "[", tag, "]")),
tag,
transportOptions.Type,
transportOptions.Options,
)
if err != nil {
return nil, common.NewError("initialize DNS server[", i, "]", err)
}
}
err = dnsRouter.Initialize(dnsOptions.Rules)
if err != nil {
return nil, common.NewError("initialize dns router", err)
}
for i, endpointOptions := range options.Endpoints { for i, endpointOptions := range options.Endpoints {
var tag string var tag string
if endpointOptions.Tag != "" { if endpointOptions.Tag != "" {
@@ -146,7 +227,8 @@ func NewBox(options Options) (*Box, error) {
} else { } else {
tag = F.ToString(i) tag = F.ToString(i)
} }
err = endpointManager.Create(ctx, err = endpointManager.Create(
ctx,
router, router,
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")), logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
tag, tag,
@@ -164,7 +246,8 @@ func NewBox(options Options) (*Box, error) {
} else { } else {
tag = F.ToString(i) tag = F.ToString(i)
} }
err = inboundManager.Create(ctx, err = inboundManager.Create(
ctx,
router, router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
tag, tag,
@@ -201,6 +284,24 @@ func NewBox(options Options) (*Box, error) {
return nil, common.NewError("initialize outbound["+F.ToString(i)+"] "+tag, err) return nil, common.NewError("initialize outbound["+F.ToString(i)+"] "+tag, err)
} }
} }
for i, serviceOptions := range options.Services {
var tag string
if serviceOptions.Tag != "" {
tag = serviceOptions.Tag
} else {
tag = F.ToString(i)
}
err = serviceManager.Create(
ctx,
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
tag,
serviceOptions.Type,
serviceOptions.Options,
)
if err != nil {
return nil, common.NewError("initialize service["+F.ToString(i)+"]"+tag, err)
}
}
outboundManager.Initialize(sbCommon.Must1( outboundManager.Initialize(sbCommon.Must1(
direct.NewOutbound( direct.NewOutbound(
ctx, ctx,
@@ -210,6 +311,13 @@ func NewBox(options Options) (*Box, error) {
option.DirectOutboundOptions{}, option.DirectOutboundOptions{},
), ),
)) ))
dnsTransportManager.Initialize(sbCommon.Must1(
local.NewTransport(
ctx,
logFactory.NewLogger("dns/local"),
"local",
option.LocalDNSServerOptions{},
)))
if platformInterface != nil { if platformInterface != nil {
err = platformInterface.Initialize(networkManager) err = platformInterface.Initialize(networkManager)
if err != nil { if err != nil {
@@ -219,18 +327,38 @@ func NewBox(options Options) (*Box, error) {
if connTracker == nil { if connTracker == nil {
connTracker = NewConnTracker() connTracker = NewConnTracker()
} }
router.SetTracker(connTracker) router.AppendTracker(connTracker)
var services []adapter.LifecycleService
if needCacheFile { if needCacheFile {
cacheFile := cachefile.New(ctx, sbCommon.PtrValueOrDefault(experimentalOptions.CacheFile)) cacheFile := cachefile.New(ctx, sbCommon.PtrValueOrDefault(experimentalOptions.CacheFile))
service.MustRegister[adapter.CacheFile](ctx, cacheFile) service.MustRegister[adapter.CacheFile](ctx, cacheFile)
services = append(services, cacheFile) internalServices = append(internalServices, cacheFile)
}
if needClashAPI {
clashAPIOptions := sbCommon.PtrValueOrDefault(experimentalOptions.ClashAPI)
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions)
if err != nil {
return nil, common.NewError(err, "create clash-server")
}
router.AppendTracker(clashServer)
service.MustRegister[adapter.ClashServer](ctx, clashServer)
internalServices = append(internalServices, clashServer)
}
if needV2RayAPI {
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), sbCommon.PtrValueOrDefault(experimentalOptions.V2RayAPI))
if err != nil {
return nil, common.NewError(err, "create v2ray-server")
}
if v2rayServer.StatsService() != nil {
router.AppendTracker(v2rayServer.StatsService())
internalServices = append(internalServices, v2rayServer)
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
}
} }
ntpOptions := sbCommon.PtrValueOrDefault(options.NTP) ntpOptions := sbCommon.PtrValueOrDefault(options.NTP)
if ntpOptions.Enabled { if ntpOptions.Enabled {
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions) ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain())
if err != nil { if err != nil {
return nil, common.NewError(err, "create NTP service") return nil, common.NewError(err, "create NTP service")
} }
@@ -243,21 +371,24 @@ func NewBox(options Options) (*Box, error) {
WriteToSystem: ntpOptions.WriteToSystem, WriteToSystem: ntpOptions.WriteToSystem,
}) })
service.MustRegister[ntp.TimeService](ctx, timeService) service.MustRegister[ntp.TimeService](ctx, timeService)
services = append(services, adapter.NewLifecycleService(timeService, "ntp service")) internalServices = append(internalServices, adapter.NewLifecycleService(timeService, "ntp service"))
} }
return &Box{ return &Box{
network: networkManager, network: networkManager,
endpoint: endpointManager, endpoint: endpointManager,
inbound: inboundManager, inbound: inboundManager,
outbound: outboundManager, outbound: outboundManager,
connection: connectionManager, dnsTransport: dnsTransportManager,
router: router, service: serviceManager,
createdAt: createdAt, dnsRouter: dnsRouter,
logFactory: logFactory, connection: connectionManager,
logger: logFactory.Logger(), router: router,
services: services, createdAt: createdAt,
connTracker: connTracker, logFactory: logFactory,
done: make(chan struct{}), logger: logFactory.Logger(),
internalService: internalServices,
connTracker: connTracker,
done: make(chan struct{}),
}, nil }, nil
} }
@@ -305,15 +436,15 @@ func (s *Box) preStart() error {
if err != nil { if err != nil {
return common.NewError(err, "start logger") return common.NewError(err, "start logger")
} }
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint) err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router) err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
if err != nil { if err != nil {
return err return err
} }
@@ -325,31 +456,27 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.StartNamed(adapter.StartStateStart, s.services) err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
if err != nil { if err != nil {
return err return err
} }
err = s.inbound.Start(adapter.StartStateStart) err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateStart, s.endpoint) err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint) err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService)
if err != nil { if err != nil {
return err return err
} }
err = adapter.StartNamed(adapter.StartStatePostStart, s.services) err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint) err = adapter.StartNamed(adapter.StartStateStarted, s.internalService)
if err != nil {
return err
}
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
if err != nil { if err != nil {
return err return err
} }
@@ -364,9 +491,9 @@ func (s *Box) Close() error {
close(s.done) close(s.done)
} }
err := sbCommon.Close( err := sbCommon.Close(
s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.network, s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
) )
for _, lifecycleService := range s.services { for _, lifecycleService := range s.internalService {
err1 := lifecycleService.Close() err1 := lifecycleService.Close()
if err1 != nil { if err1 != nil {
s.logger.Debug(lifecycleService.Name(), " close error: ", err1) s.logger.Debug(lifecycleService.Name(), " close error: ", err1)
+44 -6
View File
@@ -4,6 +4,7 @@ import (
"s-ui/logger" "s-ui/logger"
"s-ui/util/common" "s-ui/util/common"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
) )
@@ -13,13 +14,13 @@ func (c *Core) AddInbound(config []byte) error {
} }
var err error var err error
var inbound_config option.Inbound var inbound_config option.Inbound
err = inbound_config.UnmarshalJSONContext(globalCtx, config) err = inbound_config.UnmarshalJSONContext(c.GetCtx(), config)
if err != nil { if err != nil {
return err return err
} }
err = inbound_manager.Create( err = inbound_manager.Create(
globalCtx, c.GetCtx(),
router, router,
factory.NewLogger("inbound/"+inbound_config.Type+"["+inbound_config.Tag+"]"), factory.NewLogger("inbound/"+inbound_config.Type+"["+inbound_config.Tag+"]"),
inbound_config.Tag, inbound_config.Tag,
@@ -47,13 +48,17 @@ func (c *Core) AddOutbound(config []byte) error {
var err error var err error
var outbound_config option.Outbound var outbound_config option.Outbound
err = outbound_config.UnmarshalJSONContext(globalCtx, config) err = outbound_config.UnmarshalJSONContext(c.GetCtx(), config)
if err != nil { if err != nil {
return err return err
} }
outboundCtx := adapter.WithContext(c.GetCtx(), &adapter.InboundContext{
Outbound: outbound_config.Tag,
})
err = outbound_manager.Create( err = outbound_manager.Create(
globalCtx, outboundCtx,
router, router,
factory.NewLogger("outbound/"+outbound_config.Type+"["+outbound_config.Tag+"]"), factory.NewLogger("outbound/"+outbound_config.Type+"["+outbound_config.Tag+"]"),
outbound_config.Tag, outbound_config.Tag,
@@ -81,13 +86,13 @@ func (c *Core) AddEndpoint(config []byte) error {
var err error var err error
var endpoint_config option.Endpoint var endpoint_config option.Endpoint
err = endpoint_config.UnmarshalJSONContext(globalCtx, config) err = endpoint_config.UnmarshalJSONContext(c.GetCtx(), config)
if err != nil { if err != nil {
return err return err
} }
err = endpoint_manager.Create( err = endpoint_manager.Create(
globalCtx, c.GetCtx(),
router, router,
factory.NewLogger("endpoint/"+endpoint_config.Type+"["+endpoint_config.Tag+"]"), factory.NewLogger("endpoint/"+endpoint_config.Type+"["+endpoint_config.Tag+"]"),
endpoint_config.Tag, endpoint_config.Tag,
@@ -107,3 +112,36 @@ func (c *Core) RemoveEndpoint(tag string) error {
logger.Info("remove endpoint: ", tag) logger.Info("remove endpoint: ", tag)
return endpoint_manager.Remove(tag) return endpoint_manager.Remove(tag)
} }
func (c *Core) AddService(config []byte) error {
if !c.isRunning {
return common.NewError("sing-box is not running")
}
var err error
var srv_config option.Service
err = srv_config.UnmarshalJSONContext(c.GetCtx(), config)
if err != nil {
return err
}
err = service_manager.Create(
c.GetCtx(),
factory.NewLogger("service/"+srv_config.Type+"["+srv_config.Tag+"]"),
srv_config.Tag,
srv_config.Type,
srv_config.Options)
if err != nil {
return err
}
return nil
}
func (c *Core) RemoveService(tag string) error {
if !c.isRunning {
return common.NewError("sing-box is not running")
}
logger.Info("remove service: ", tag)
return service_manager.Remove(tag)
}
+5
View File
@@ -5,6 +5,7 @@ import (
"io" "io"
"os" "os"
suiLog "s-ui/logger" suiLog "s-ui/logger"
"time"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
@@ -177,6 +178,10 @@ func (l *observableLogger) Log(ctx context.Context, level log.Level, args []any)
default: default:
suiLog.Debug(l.tag, msg) suiLog.Debug(l.tag, msg)
} }
if (l.filePath != "" || l.writer != os.Stderr) && l.writer != nil {
message := l.formatter.Format(ctx, level, l.tag, msg, time.Now())
l.writer.Write([]byte(message))
}
} }
func (l *observableLogger) Trace(args ...any) { func (l *observableLogger) Trace(args ...any) {
+3 -1
View File
@@ -19,6 +19,7 @@ var (
globalCtx context.Context globalCtx context.Context
inbound_manager adapter.InboundManager inbound_manager adapter.InboundManager
outbound_manager adapter.OutboundManager outbound_manager adapter.OutboundManager
service_manager adapter.ServiceManager
endpoint_manager adapter.EndpointManager endpoint_manager adapter.EndpointManager
router adapter.Router router adapter.Router
connTracker *ConnTracker connTracker *ConnTracker
@@ -32,7 +33,7 @@ type Core struct {
func NewCore() *Core { func NewCore() *Core {
globalCtx = context.Background() globalCtx = context.Background()
globalCtx = sb.Context(globalCtx, inboundRegistry(), outboundRegistry(), EndpointRegistry()) globalCtx = sb.Context(globalCtx, InboundRegistry(), OutboundRegistry(), EndpointRegistry(), DNSTransportRegistry(), ServiceRegistry())
return &Core{ return &Core{
isRunning: false, isRunning: false,
instance: nil, instance: nil,
@@ -70,6 +71,7 @@ func (c *Core) Start(sbConfig []byte) error {
globalCtx = service.ContextWith(globalCtx, c) globalCtx = service.ContextWith(globalCtx, c)
inbound_manager = service.FromContext[adapter.InboundManager](globalCtx) inbound_manager = service.FromContext[adapter.InboundManager](globalCtx)
outbound_manager = service.FromContext[adapter.OutboundManager](globalCtx) outbound_manager = service.FromContext[adapter.OutboundManager](globalCtx)
service_manager = service.FromContext[adapter.ServiceManager](globalCtx)
endpoint_manager = service.FromContext[adapter.EndpointManager](globalCtx) endpoint_manager = service.FromContext[adapter.EndpointManager](globalCtx)
router = service.FromContext[adapter.Router](globalCtx) router = service.FromContext[adapter.Router](globalCtx)
+70 -4
View File
@@ -4,9 +4,18 @@ import (
"github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/adapter/service"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/dns/transport"
"github.com/sagernet/sing-box/dns/transport/dhcp"
"github.com/sagernet/sing-box/dns/transport/fakeip"
"github.com/sagernet/sing-box/dns/transport/hosts"
"github.com/sagernet/sing-box/dns/transport/local"
"github.com/sagernet/sing-box/dns/transport/quic"
"github.com/sagernet/sing-box/protocol/anytls"
"github.com/sagernet/sing-box/protocol/block" "github.com/sagernet/sing-box/protocol/block"
"github.com/sagernet/sing-box/protocol/direct" "github.com/sagernet/sing-box/protocol/direct"
"github.com/sagernet/sing-box/protocol/dns" protocolDNS "github.com/sagernet/sing-box/protocol/dns"
"github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing-box/protocol/group"
"github.com/sagernet/sing-box/protocol/http" "github.com/sagernet/sing-box/protocol/http"
"github.com/sagernet/sing-box/protocol/hysteria" "github.com/sagernet/sing-box/protocol/hysteria"
@@ -19,6 +28,7 @@ import (
"github.com/sagernet/sing-box/protocol/shadowtls" "github.com/sagernet/sing-box/protocol/shadowtls"
"github.com/sagernet/sing-box/protocol/socks" "github.com/sagernet/sing-box/protocol/socks"
"github.com/sagernet/sing-box/protocol/ssh" "github.com/sagernet/sing-box/protocol/ssh"
"github.com/sagernet/sing-box/protocol/tailscale"
"github.com/sagernet/sing-box/protocol/tor" "github.com/sagernet/sing-box/protocol/tor"
"github.com/sagernet/sing-box/protocol/trojan" "github.com/sagernet/sing-box/protocol/trojan"
"github.com/sagernet/sing-box/protocol/tuic" "github.com/sagernet/sing-box/protocol/tuic"
@@ -26,11 +36,14 @@ import (
"github.com/sagernet/sing-box/protocol/vless" "github.com/sagernet/sing-box/protocol/vless"
"github.com/sagernet/sing-box/protocol/vmess" "github.com/sagernet/sing-box/protocol/vmess"
"github.com/sagernet/sing-box/protocol/wireguard" "github.com/sagernet/sing-box/protocol/wireguard"
"github.com/sagernet/sing-box/service/derp"
"github.com/sagernet/sing-box/service/resolved"
"github.com/sagernet/sing-box/service/ssmapi"
_ "github.com/sagernet/sing-box/transport/v2rayquic" _ "github.com/sagernet/sing-box/transport/v2rayquic"
_ "github.com/sagernet/sing-dns/quic" _ "github.com/sagernet/sing-dns/quic"
) )
func inboundRegistry() *inbound.Registry { func InboundRegistry() *inbound.Registry {
registry := inbound.NewRegistry() registry := inbound.NewRegistry()
tun.RegisterInbound(registry) tun.RegisterInbound(registry)
@@ -48,6 +61,7 @@ func inboundRegistry() *inbound.Registry {
naive.RegisterInbound(registry) naive.RegisterInbound(registry)
shadowtls.RegisterInbound(registry) shadowtls.RegisterInbound(registry)
vless.RegisterInbound(registry) vless.RegisterInbound(registry)
anytls.RegisterInbound(registry)
hysteria.RegisterInbound(registry) hysteria.RegisterInbound(registry)
tuic.RegisterInbound(registry) tuic.RegisterInbound(registry)
@@ -56,13 +70,13 @@ func inboundRegistry() *inbound.Registry {
return registry return registry
} }
func outboundRegistry() *outbound.Registry { func OutboundRegistry() *outbound.Registry {
registry := outbound.NewRegistry() registry := outbound.NewRegistry()
direct.RegisterOutbound(registry) direct.RegisterOutbound(registry)
block.RegisterOutbound(registry) block.RegisterOutbound(registry)
dns.RegisterOutbound(registry) protocolDNS.RegisterOutbound(registry)
group.RegisterSelector(registry) group.RegisterSelector(registry)
group.RegisterURLTest(registry) group.RegisterURLTest(registry)
@@ -76,6 +90,7 @@ func outboundRegistry() *outbound.Registry {
ssh.RegisterOutbound(registry) ssh.RegisterOutbound(registry)
shadowtls.RegisterOutbound(registry) shadowtls.RegisterOutbound(registry)
vless.RegisterOutbound(registry) vless.RegisterOutbound(registry)
anytls.RegisterOutbound(registry)
hysteria.RegisterOutbound(registry) hysteria.RegisterOutbound(registry)
tuic.RegisterOutbound(registry) tuic.RegisterOutbound(registry)
@@ -89,6 +104,57 @@ func EndpointRegistry() *endpoint.Registry {
registry := endpoint.NewRegistry() registry := endpoint.NewRegistry()
wireguard.RegisterEndpoint(registry) wireguard.RegisterEndpoint(registry)
registerTailscaleEndpoint(registry)
return registry
}
func DNSTransportRegistry() *dns.TransportRegistry {
registry := dns.NewTransportRegistry()
transport.RegisterTCP(registry)
transport.RegisterUDP(registry)
transport.RegisterTLS(registry)
transport.RegisterHTTPS(registry)
hosts.RegisterTransport(registry)
local.RegisterTransport(registry)
fakeip.RegisterTransport(registry)
registerQUICTransports(registry)
registerDHCPTransport(registry)
registerTailscaleTransport(registry)
return registry
}
func registerTailscaleEndpoint(registry *endpoint.Registry) {
tailscale.RegisterEndpoint(registry)
}
func registerTailscaleTransport(registry *dns.TransportRegistry) {
tailscale.RegistryTransport(registry)
}
func registerDERPService(registry *service.Registry) {
derp.Register(registry)
}
func registerQUICTransports(registry *dns.TransportRegistry) {
quic.RegisterTransport(registry)
quic.RegisterHTTP3Transport(registry)
}
func registerDHCPTransport(registry *dns.TransportRegistry) {
dhcp.RegisterTransport(registry)
}
func ServiceRegistry() *service.Registry {
registry := service.NewRegistry()
resolved.RegisterService(registry)
ssmapi.RegisterService(registry)
registerDERPService(registry)
return registry return registry
} }
+9 -1
View File
@@ -1,12 +1,14 @@
package cronjob package cronjob
import ( import (
"s-ui/database"
"s-ui/logger" "s-ui/logger"
"s-ui/service" "s-ui/service"
) )
type DepleteJob struct { type DepleteJob struct {
service.ClientService service.ClientService
service.InboundService
} }
func NewDepleteJob() *DepleteJob { func NewDepleteJob() *DepleteJob {
@@ -14,9 +16,15 @@ func NewDepleteJob() *DepleteJob {
} }
func (s *DepleteJob) Run() { func (s *DepleteJob) Run() {
err := s.ClientService.DepleteClients() inboundIds, err := s.ClientService.DepleteClients()
if err != nil { if err != nil {
logger.Warning("Disable depleted users failed: ", err) logger.Warning("Disable depleted users failed: ", err)
return return
} }
if len(inboundIds) > 0 {
err := s.InboundService.RestartInbounds(database.GetDB(), inboundIds)
if err != nil {
logger.Error("unable to restart inbounds: ", err)
}
}
} }
+27 -6
View File
@@ -40,6 +40,7 @@ func GetDb(exclude string) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer os.Remove(dbPath)
err = backupDb.AutoMigrate( err = backupDb.AutoMigrate(
&model.Setting{}, &model.Setting{},
@@ -69,29 +70,50 @@ func GetDb(exclude string) ([]byte, error) {
// Perform scans and handle errors // Perform scans and handle errors
if err := db.Model(&model.Setting{}).Scan(&settings).Error; err != nil { if err := db.Model(&model.Setting{}).Scan(&settings).Error; err != nil {
return nil, err return nil, err
} else if len(settings) > 0 {
if err := backupDb.Save(settings).Error; err != nil {
return nil, err
}
} }
if err := db.Model(&model.Tls{}).Scan(&tls).Error; err != nil { if err := db.Model(&model.Tls{}).Scan(&tls).Error; err != nil {
return nil, err return nil, err
} else if len(tls) > 0 {
if err := backupDb.Save(tls).Error; err != nil {
return nil, err
}
} }
if err := db.Model(&model.Inbound{}).Scan(&inbound).Error; err != nil { if err := db.Model(&model.Inbound{}).Scan(&inbound).Error; err != nil {
return nil, err return nil, err
} else if len(inbound) > 0 {
if err := backupDb.Save(inbound).Error; err != nil {
return nil, err
}
} }
if err := db.Model(&model.Outbound{}).Scan(&outbound).Error; err != nil { if err := db.Model(&model.Outbound{}).Scan(&outbound).Error; err != nil {
return nil, err return nil, err
} else if len(outbound) > 0 {
if err := backupDb.Save(outbound).Error; err != nil {
return nil, err
}
} }
if err := db.Model(&model.Endpoint{}).Scan(&endpoint).Error; err != nil { if err := db.Model(&model.Endpoint{}).Scan(&endpoint).Error; err != nil {
return nil, err return nil, err
} else if len(endpoint) > 0 {
if err := backupDb.Save(endpoint).Error; err != nil {
return nil, err
}
} }
if err := db.Model(&model.User{}).Scan(&users).Error; err != nil { if err := db.Model(&model.User{}).Scan(&users).Error; err != nil {
return nil, err return nil, err
} else if len(users) > 0 {
if err := backupDb.Save(users).Error; err != nil {
return nil, err
}
} }
if err := db.Model(&model.Client{}).Scan(&clients).Error; err != nil { if err := db.Model(&model.Client{}).Scan(&clients).Error; err != nil {
return nil, err return nil, err
} } else if len(clients) > 0 {
if err := backupDb.Save(clients).Error; err != nil {
// Save each model
for _, mdl := range []interface{}{settings, tls, inbound, outbound, endpoint, users, clients} {
if err := backupDb.Save(mdl).Error; err != nil {
return nil, err return nil, err
} }
} }
@@ -132,7 +154,6 @@ func GetDb(exclude string) ([]byte, error) {
return nil, err return nil, err
} }
defer file.Close() defer file.Close()
defer os.Remove(dbPath)
// Read the file contents // Read the file contents
fileContents, err := io.ReadAll(file) fileContents, err := io.ReadAll(file)
+1 -1
View File
@@ -67,7 +67,6 @@ func InitDB(dbPath string) error {
db.Migrator().CreateTable(&model.Outbound{}) db.Migrator().CreateTable(&model.Outbound{})
defaultOutbound := []model.Outbound{ defaultOutbound := []model.Outbound{
{Type: "direct", Tag: "direct", Options: json.RawMessage(`{}`)}, {Type: "direct", Tag: "direct", Options: json.RawMessage(`{}`)},
{Type: "dns", Tag: "dns-out", Options: json.RawMessage(`{}`)},
} }
db.Create(&defaultOutbound) db.Create(&defaultOutbound)
} }
@@ -77,6 +76,7 @@ func InitDB(dbPath string) error {
&model.Tls{}, &model.Tls{},
&model.Inbound{}, &model.Inbound{},
&model.Outbound{}, &model.Outbound{},
&model.Service{},
&model.Endpoint{}, &model.Endpoint{},
&model.User{}, &model.User{},
&model.Tokens{}, &model.Tokens{},
+90
View File
@@ -0,0 +1,90 @@
package model
import (
"encoding/json"
)
type Service struct {
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
Type string `json:"type" form:"type"`
Tag string `json:"tag" form:"tag" gorm:"unique"`
// Foreign key to tls table
TlsId uint `json:"tls_id" form:"tls_id"`
Tls *Tls `json:"tls" form:"tls" gorm:"foreignKey:TlsId;references:Id"`
Options json.RawMessage `json:"-" form:"-"`
}
func (i *Service) UnmarshalJSON(data []byte) error {
var err error
var raw map[string]interface{}
if err = json.Unmarshal(data, &raw); err != nil {
return err
}
// Extract fixed fields and store the rest in Options
if val, exists := raw["id"].(float64); exists {
i.Id = uint(val)
}
delete(raw, "id")
i.Type, _ = raw["type"].(string)
delete(raw, "type")
i.Tag, _ = raw["tag"].(string)
delete(raw, "tag")
// TlsId
if val, exists := raw["tls_id"].(float64); exists {
i.TlsId = uint(val)
}
delete(raw, "tls_id")
delete(raw, "tls")
// Remaining fields
i.Options, err = json.MarshalIndent(raw, "", " ")
return err
}
// MarshalJSON customizes marshalling
func (i Service) MarshalJSON() ([]byte, error) {
// Combine fixed fields and dynamic fields into one map
combined := make(map[string]interface{})
combined["type"] = i.Type
combined["tag"] = i.Tag
if i.Tls != nil {
combined["tls"] = i.Tls.Server
}
if i.Options != nil {
var restFields map[string]json.RawMessage
if err := json.Unmarshal(i.Options, &restFields); err != nil {
return nil, err
}
for k, v := range restFields {
combined[k] = v
}
}
return json.Marshal(combined)
}
func (i Service) MarshalFull() (*map[string]interface{}, error) {
combined := make(map[string]interface{})
combined["id"] = i.Id
combined["type"] = i.Type
combined["tag"] = i.Tag
combined["tls_id"] = i.TlsId
if i.Options != nil {
var restFields map[string]interface{}
if err := json.Unmarshal(i.Options, &restFields); err != nil {
return nil, err
}
for k, v := range restFields {
combined[k] = v
}
}
return &combined, nil
}
+114 -81
View File
@@ -1,126 +1,159 @@
module s-ui module s-ui
go 1.23.2 go 1.24.5
require ( require (
github.com/gin-contrib/gzip v1.0.1 github.com/gin-contrib/gzip v1.2.3
github.com/gin-gonic/gin v1.10.0 github.com/gin-contrib/sessions v1.0.4
github.com/gin-gonic/gin v1.10.1
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/sagernet/sing v0.6.0-beta.12 github.com/sagernet/sing v0.7.0-beta.1.0.20250722151551-64142925accb
github.com/sagernet/sing-box v1.11.0 github.com/sagernet/sing-box v1.12.0-rc.3
github.com/sagernet/sing-dns v0.4.0-beta.2 github.com/sagernet/sing-dns v0.4.6
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 github.com/shirou/gopsutil/v4 v4.25.6
gorm.io/driver/sqlite v1.5.7 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
gorm.io/gorm v1.25.12 gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.30.0
) )
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/ajg/form v1.5.1 // indirect github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect github.com/akutz/memconn v0.1.0 // indirect
github.com/bytedance/sonic v1.12.3 // indirect github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/caddyserver/certmagic v0.20.0 // indirect github.com/anytls/sing-anytls v0.0.8 // indirect
github.com/cloudflare/circl v1.3.7 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect github.com/bytedance/sonic v1.13.3 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/caddyserver/certmagic v0.23.0 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/coder/websocket v1.8.13 // indirect
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
github.com/cretz/bine v0.2.0 // indirect github.com/cretz/bine v0.2.0 // indirect
github.com/ebitengine/purego v0.8.1 // indirect github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
github.com/ebitengine/purego v0.8.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.6 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gin-contrib/sessions v1.0.1 github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gaissmai/bart v0.11.1 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-chi/chi/v5 v5.2.2 // indirect
github.com/go-chi/render v1.0.3 // indirect github.com/go-chi/render v1.0.3 // indirect
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.1 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/pool v0.2.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/gofrs/uuid/v5 v5.3.0 // indirect github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/gofrs/uuid/v5 v5.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.4.0 // indirect github.com/gorilla/sessions v1.4.0 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect github.com/hashicorp/yamux v0.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/illarion/gonotify/v2 v2.0.3 // indirect
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/jsimonetti/rtnetlink v1.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.7 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/libdns/alidns v1.0.3 // indirect github.com/libdns/alidns v1.0.5-libdns.v1.beta1 // indirect
github.com/libdns/cloudflare v0.1.1 // indirect github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 // indirect
github.com/libdns/libdns v0.2.2 // indirect; indiresct github.com/libdns/libdns v1.1.0 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/mattn/go-sqlite3 v1.14.28 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa // indirect github.com/mdlayher/sdnotify v1.0.0 // indirect
github.com/mholt/acmez v1.2.0 // indirect github.com/mdlayher/socket v0.5.1 // indirect
github.com/miekg/dns v1.1.62 // indirect github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect
github.com/metacubex/utls v1.8.0 // indirect
github.com/mholt/acmez/v3 v3.1.2 // indirect
github.com/miekg/dns v1.1.67 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.10.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/prometheus-community/pro-bing v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/qpack v0.5.1 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect
github.com/sagernet/cors v1.2.1 // indirect github.com/sagernet/cors v1.2.1 // indirect
github.com/sagernet/fswatch v0.1.1 // indirect github.com/sagernet/fswatch v0.1.1 // indirect
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff // indirect github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/quic-go v0.48.2-beta.1 // indirect github.com/sagernet/quic-go v0.52.0-beta.1 // indirect
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/sing-mux v0.3.2 // indirect
github.com/sagernet/sing-mux v0.3.0-alpha.1 // indirect github.com/sagernet/sing-quic v0.5.0-beta.3 // indirect
github.com/sagernet/sing-quic v0.4.0-beta.4 // indirect github.com/sagernet/sing-shadowsocks v0.2.8 // indirect
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect github.com/sagernet/sing-shadowsocks2 v0.2.1 // indirect
github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect github.com/sagernet/sing-tun v0.6.10-0.20250721014417-ebbe32588cfb // indirect
github.com/sagernet/sing-tun v0.6.0-beta.8 // indirect github.com/sagernet/sing-vmess v0.2.4 // indirect
github.com/sagernet/sing-vmess v0.2.0-beta.2 // indirect github.com/sagernet/smux v1.5.34-mod.2 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/tailscale v1.80.3-mod.5 // indirect
github.com/sagernet/utls v1.6.7 // indirect github.com/sagernet/wireguard-go v0.0.1-beta.7 // indirect
github.com/sagernet/wireguard-go v0.0.1-beta.5 // indirect
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
github.com/shirou/gopsutil/v4 v4.24.12 github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
github.com/tklauser/numcpus v0.9.0 // indirect github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/ugorji/go/codec v1.3.0 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect github.com/zeebo/blake3 v0.2.4 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.11.0 // indirect golang.org/x/arch v0.18.0 // indirect
golang.org/x/crypto v0.31.0 // indirect golang.org/x/crypto v0.40.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/mod v0.20.0 // indirect golang.org/x/mod v0.26.0 // indirect
golang.org/x/net v0.31.0 // indirect golang.org/x/net v0.42.0 // indirect
golang.org/x/sync v0.10.0 // indirect golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/term v0.33.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.24.0 // indirect golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.34.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/grpc v1.67.1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
google.golang.org/protobuf v1.35.1 // indirect google.golang.org/grpc v1.73.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect google.golang.org/protobuf v1.36.6 // indirect
lukechampine.com/blake3 v1.3.0 // indirect lukechampine.com/blake3 v1.4.1 // indirect
) )
+268 -177
View File
@@ -1,45 +1,75 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anytls/sing-anytls v0.0.8 h1:1u/fnH1HoeeMV5mX7/eUOjLBvPdkd1UJRmXiRi6Vymc=
github.com/anytls/sing-anytls v0.0.8/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI= github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc=
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM= github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/gzip v1.2.3 h1:dAhT722RuEG330ce2agAs75z7yB+NKvX/ZM1r8w0u2U=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/gzip v1.2.3/go.mod h1:ad72i4Bzmaypk8M762gNXa2wkxxjbz0icRNnuLJ9a/c=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@@ -49,253 +79,314 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M=
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A=
github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE=
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU=
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBeJssZ1YBCMZ5Lzu1pX4vhftDvU10WUVb1uXKtM=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= github.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ=
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= github.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g=
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8=
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60=
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4= github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw= github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac=
github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff h1:mlohw3360Wg1BNGook/UHnISXhUx4Gd/3tVLs5T0nSs= github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38=
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw= github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.48.2-beta.1 h1:W0plrLWa1XtOWDTdX3CJwxmQuxkya12nN5BRGZ87kEg= github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.7.0-beta.1.0.20250722151551-64142925accb h1:9DU5JA9Cow/bUfdP1v1pYMbAkFiW17UbI4b/iEPjVnc=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.7.0-beta.1.0.20250722151551-64142925accb/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.6.0-beta.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s= github.com/sagernet/sing-box v1.12.0-rc.3 h1:2II6wtPSAZZtE7+1EvdoEo1M0+De8483sqwMd8evaos=
github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-box v1.12.0-rc.3/go.mod h1:9HHuLZi2GS3xaDT9oh9hpXsaoEB71HN8YM03lctYB10=
github.com/sagernet/sing-box v1.11.0-rc.1 h1:iJbMP+dcKOEbvjv0rBXkFndmJQCPAACrYmanYPPuIgk= github.com/sagernet/sing-dns v0.4.6 h1:mjZC0o6d5sQ1sraoOBbK3G3apCbuL8wWYwu2RNu5rbM=
github.com/sagernet/sing-box v1.11.0-rc.1/go.mod h1:DmL1WKyrfaAEu5z88CtUeQBfELaEdUyQzLS5nzmRg8o= github.com/sagernet/sing-dns v0.4.6/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8=
github.com/sagernet/sing-box v1.11.0 h1:70DvEMhFMtDn4YQdj3aoOsRdvUznLGQREnORAt6UmYo= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
github.com/sagernet/sing-box v1.11.0/go.mod h1:DmL1WKyrfaAEu5z88CtUeQBfELaEdUyQzLS5nzmRg8o= github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY= github.com/sagernet/sing-quic v0.5.0-beta.3 h1:X/acRNsqQNfDlmwE7SorHfaZiny5e67hqIzM/592ric=
github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8= github.com/sagernet/sing-quic v0.5.0-beta.3/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
github.com/sagernet/sing-quic v0.4.0-beta.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM= github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= github.com/sagernet/sing-tun v0.6.10-0.20250721014417-ebbe32588cfb h1:cvHEzjk3sVy80UA9PFKX15MzSP0g1uKwUspOm2ds3no=
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-tun v0.6.10-0.20250721014417-ebbe32588cfb/go.mod h1:AHJuRrLbNRJuivuFZ2VhXwDj4ViYp14szG5EkkKAqRQ=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0= github.com/sagernet/sing-vmess v0.2.4 h1:wSg/SdxThELAvoRIN2yCZgu5xsmP1FWPBrP2ab2wq3A=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA= github.com/sagernet/sing-vmess v0.2.4/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
github.com/sagernet/sing-tun v0.6.0-beta.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs= github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A=
github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk= github.com/sagernet/tailscale v1.80.3-mod.5/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc=
github.com/sagernet/wireguard-go v0.0.1-beta.5/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4= github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o= github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw=
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14=
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
+1 -1
View File
@@ -75,7 +75,7 @@ elif [[ "${release}" == "rocky" ]]; then
if [[ ${os_version} -lt 9 ]]; then if [[ ${os_version} -lt 9 ]]; then
echo -e "${red} Please use Rocky Linux 9 or higher ${plain}\n" && exit 1 echo -e "${red} Please use Rocky Linux 9 or higher ${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "oracle" ]]; then elif [[ "${release}" == "ol" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1 echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
fi fi
+171 -106
View File
@@ -1,4 +1,3 @@
#!/bin/bash #!/bin/bash
red='\033[0;31m' red='\033[0;31m'
@@ -6,7 +5,6 @@ green='\033[0;32m'
yellow='\033[0;33m' yellow='\033[0;33m'
plain='\033[0m' plain='\033[0m'
#Add some basic function here
function LOGD() { function LOGD() {
echo -e "${yellow}[DEG] $* ${plain}" echo -e "${yellow}[DEG] $* ${plain}"
} }
@@ -18,10 +16,9 @@ function LOGE() {
function LOGI() { function LOGI() {
echo -e "${green}[INF] $* ${plain}" echo -e "${green}[INF] $* ${plain}"
} }
# check root
[[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1 [[ $EUID -ne 0 ]] && LOGE "ERROR: You must be root to run this script! \n" && exit 1
# Check OS and set release variable
if [[ -f /etc/os-release ]]; then if [[ -f /etc/os-release ]]; then
source /etc/os-release source /etc/os-release
release=$ID release=$ID
@@ -35,7 +32,6 @@ fi
echo "The OS release is: $release" echo "The OS release is: $release"
os_version="" os_version=""
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1) os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
@@ -54,8 +50,8 @@ elif [[ "${release}" == "centos" ]]; then
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1 echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "ubuntu" ]]; then elif [[ "${release}" == "ubuntu" ]]; then
if [[ ${os_version} -lt 20 ]]; then if [[ ${os_version} -lt 22 ]]; then
echo -e "${red} Please use Ubuntu 20 or higher version!${plain}\n" && exit 1 echo -e "${red} Please use Ubuntu 22 or higher version!${plain}\n" && exit 1
fi fi
elif [[ "${release}" == "fedora" ]]; then elif [[ "${release}" == "fedora" ]]; then
if [[ ${os_version} -lt 36 ]]; then if [[ ${os_version} -lt 36 ]]; then
@@ -80,7 +76,7 @@ elif [[ "${release}" == "oracle" ]]; then
else else
echo -e "${red}Your operating system is not supported by this script.${plain}\n" echo -e "${red}Your operating system is not supported by this script.${plain}\n"
echo "Please ensure you are using one of the following supported operating systems:" echo "Please ensure you are using one of the following supported operating systems:"
echo "- Ubuntu 20.04+" echo "- Ubuntu 22.04+"
echo "- Debian 11+" echo "- Debian 11+"
echo "- CentOS 8+" echo "- CentOS 8+"
echo "- Fedora 36+" echo "- Fedora 36+"
@@ -93,7 +89,6 @@ else
echo "- Oracle Linux 8+" echo "- Oracle Linux 8+"
echo "- OpenSUSE Tumbleweed" echo "- OpenSUSE Tumbleweed"
exit 1 exit 1
fi fi
confirm() { confirm() {
@@ -164,7 +159,6 @@ custom_version() {
download_link="https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh" download_link="https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh"
# Use the entered panel version in the download link
install_command="bash <(curl -Ls $download_link) $panel_version" install_command="bash <(curl -Ls $download_link) $panel_version"
echo "Downloading and installing panel version $panel_version..." echo "Downloading and installing panel version $panel_version..."
@@ -232,13 +226,11 @@ set_setting() {
echo -e "Enter the ${yellow}panel path${plain} (leave blank for existing/default value):" echo -e "Enter the ${yellow}panel path${plain} (leave blank for existing/default value):"
read config_path read config_path
# Sub configuration
echo -e "Enter the ${yellow}subscription port${plain} (leave blank for existing/default value):" echo -e "Enter the ${yellow}subscription port${plain} (leave blank for existing/default value):"
read config_subPort read config_subPort
echo -e "Enter the ${yellow}subscription path${plain} (leave blank for existing/default value):" echo -e "Enter the ${yellow}subscription path${plain} (leave blank for existing/default value):"
read config_subPath read config_subPath
# Set configs
echo -e "${yellow}Initializing, please wait...${plain}" echo -e "${yellow}Initializing, please wait...${plain}"
params="" params=""
[ -z "$config_port" ] || params="$params -port $config_port" [ -z "$config_port" ] || params="$params -port $config_port"
@@ -373,7 +365,6 @@ update_shell() {
fi fi
} }
# 0: running, 1: not running, 2: not installed
check_status() { check_status() {
if [[ ! -f "/etc/systemd/system/$1.service" ]]; then if [[ ! -f "/etc/systemd/system/$1.service" ]]; then
return 2 return 2
@@ -487,20 +478,13 @@ bbr_menu() {
} }
disable_bbr() { 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 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}" echo -e "${yellow}BBR is not currently enabled.${plain}"
exit 0 exit 0
fi fi
# Replace BBR with CUBIC configurations
sed -i 's/net.core.default_qdisc=fq/net.core.default_qdisc=pfifo_fast/' /etc/sysctl.conf 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 sed -i 's/net.ipv4.tcp_congestion_control=bbr/net.ipv4.tcp_congestion_control=cubic/' /etc/sysctl.conf
# Apply changes
sysctl -p sysctl -p
# Verify that BBR is replaced with CUBIC
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "cubic" ]]; then if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "cubic" ]]; then
echo -e "${green}BBR has been replaced with CUBIC successfully.${plain}" echo -e "${green}BBR has been replaced with CUBIC successfully.${plain}"
else else
@@ -513,8 +497,6 @@ enable_bbr() {
echo -e "${green}BBR is already enabled!${plain}" echo -e "${green}BBR is already enabled!${plain}"
exit 0 exit 0
fi fi
# Check the OS and install necessary packages
case "${release}" in case "${release}" in
ubuntu | debian | armbian) ubuntu | debian | armbian)
apt-get update && apt-get install -yqq --no-install-recommends ca-certificates apt-get update && apt-get install -yqq --no-install-recommends ca-certificates
@@ -533,15 +515,9 @@ enable_bbr() {
exit 1 exit 1
;; ;;
esac esac
# Enable BBR
echo "net.core.default_qdisc=fq" | tee -a /etc/sysctl.conf echo "net.core.default_qdisc=fq" | tee -a /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" | tee -a /etc/sysctl.conf echo "net.ipv4.tcp_congestion_control=bbr" | tee -a /etc/sysctl.conf
# Apply changes
sysctl -p sysctl -p
# Verify that BBR is enabled
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "bbr" ]]; then
echo -e "${green}BBR has been enabled successfully.${plain}" echo -e "${green}BBR has been enabled successfully.${plain}"
else else
@@ -566,6 +542,7 @@ ssl_cert_issue_main() {
echo -e "${green}\t1.${plain} Get SSL" echo -e "${green}\t1.${plain} Get SSL"
echo -e "${green}\t2.${plain} Revoke" echo -e "${green}\t2.${plain} Revoke"
echo -e "${green}\t3.${plain} Force Renew" echo -e "${green}\t3.${plain} Force Renew"
echo -e "${green}\t4.${plain} Self-signed Certificate"
read -p "Choose an option: " choice read -p "Choose an option: " choice
case "$choice" in case "$choice" in
1) ssl_cert_issue ;; 1) ssl_cert_issue ;;
@@ -579,12 +556,14 @@ ssl_cert_issue_main() {
local domain="" local domain=""
read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain
~/.acme.sh/acme.sh --renew -d ${domain} --force ;; ~/.acme.sh/acme.sh --renew -d ${domain} --force ;;
4)
generate_self_signed_cert
;;
*) echo "Invalid choice" ;; *) echo "Invalid choice" ;;
esac esac
} }
ssl_cert_issue() { ssl_cert_issue() {
# check for acme.sh first
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
echo "acme.sh could not be found. we will install it" echo "acme.sh could not be found. we will install it"
install_acme install_acme
@@ -593,7 +572,6 @@ ssl_cert_issue() {
exit 1 exit 1
fi fi
fi fi
# install socat second
case "${release}" in case "${release}" in
ubuntu | debian | armbian) ubuntu | debian | armbian)
apt update && apt install socat -y apt update && apt install socat -y
@@ -619,11 +597,9 @@ ssl_cert_issue() {
LOGI "install socat succeed..." LOGI "install socat succeed..."
fi fi
# get the domain here,and we need verify it
local domain="" local domain=""
read -p "Please enter your domain name:" domain read -p "Please enter your domain name:" domain
LOGD "your domain is:${domain},check it..." LOGD "your domain is:${domain},check it..."
# here we need to judge whether there exists cert already
local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}') local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}')
if [ ${currentCert} == ${domain} ]; then if [ ${currentCert} == ${domain} ]; then
@@ -635,7 +611,6 @@ ssl_cert_issue() {
LOGI "your domain is ready for issuing cert now..." LOGI "your domain is ready for issuing cert now..."
fi fi
# create a directory for install cert
certPath="/root/cert/${domain}" certPath="/root/cert/${domain}"
if [ ! -d "$certPath" ]; then if [ ! -d "$certPath" ]; then
mkdir -p "$certPath" mkdir -p "$certPath"
@@ -644,15 +619,12 @@ ssl_cert_issue() {
mkdir -p "$certPath" mkdir -p "$certPath"
fi fi
# get needed port here
local WebPort=80 local WebPort=80
read -p "please choose which port do you use,default will be 80 port:" WebPort read -p "please choose which port do you use,default will be 80 port:" WebPort
if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
LOGE "your input ${WebPort} is invalid,will use default port" LOGE "your input ${WebPort} is invalid,will use default port"
fi fi
LOGI "will use port:${WebPort} to issue certs,please make sure this port is open..." LOGI "will use port:${WebPort} to issue certs,please make sure this port is open..."
# NOTE:This should be handled by user
# open the port and kill the occupied progress
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
~/.acme.sh/acme.sh --issue -d ${domain} --standalone --httpport ${WebPort} ~/.acme.sh/acme.sh --issue -d ${domain} --standalone --httpport ${WebPort}
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@@ -662,7 +634,6 @@ ssl_cert_issue() {
else else
LOGE "issue certs succeed,installing certs..." LOGE "issue certs succeed,installing certs..."
fi fi
# install cert
~/.acme.sh/acme.sh --installcert -d ${domain} \ ~/.acme.sh/acme.sh --installcert -d ${domain} \
--key-file /root/cert/${domain}/privkey.pem \ --key-file /root/cert/${domain}/privkey.pem \
--fullchain-file /root/cert/${domain}/fullchain.pem --fullchain-file /root/cert/${domain}/fullchain.pem
@@ -691,78 +662,172 @@ ssl_cert_issue() {
ssl_cert_issue_CF() { ssl_cert_issue_CF() {
echo -E "" echo -E ""
LOGD "******Instructions for use******" LOGD "******Instructions for use******"
LOGI "This Acme script requires the following data:" echo "1) New certificate from Cloudflare"
LOGI "1.Cloudflare Registered e-mail" echo "2) Force renew existing Certificates"
LOGI "2.Cloudflare Global API Key" echo "3) Back to Menu"
LOGI "3.The domain name that has been resolved dns to the current server by Cloudflare" read -p "Enter your choice [1-3]: " choice
LOGI "4.The script applies for a certificate. The default installation path is /root/cert "
confirm "Confirmed?[y/n]" "y" certPath="/root/cert-CF"
if [ $? -eq 0 ]; then
# check for acme.sh first case $choice in
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then 1|2)
echo "acme.sh could not be found. we will install it" force_flag=""
install_acme if [ "$choice" -eq 2 ]; then
if [ $? -ne 0 ]; then force_flag="--force"
LOGE "install acme failed, please check logs" echo "Forcing SSL certificate reissuance..."
exit 1 else
echo "Starting SSL certificate issuance..."
fi fi
fi
CF_Domain="" LOGD "******Instructions for use******"
CF_GlobalKey="" LOGI "This Acme script requires the following data:"
CF_AccountEmail="" LOGI "1.Cloudflare Registered e-mail"
certPath=/root/cert LOGI "2.Cloudflare Global API Key"
if [ ! -d "$certPath" ]; then LOGI "3.The domain name that has been resolved DNS to the current server by Cloudflare"
mkdir $certPath LOGI "4.The script applies for a certificate. The default installation path is /root/cert "
else confirm "Confirmed?[y/n]" "y"
rm -rf $certPath if [ $? -eq 0 ]; then
mkdir $certPath if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
fi echo "acme.sh could not be found. Installing..."
LOGD "Please set a domain name:" install_acme
read -p "Input your domain here:" CF_Domain if [ $? -ne 0 ]; then
LOGD "Your domain name is set to:${CF_Domain}" LOGE "Install acme failed, please check logs"
LOGD "Please set the API key:" exit 1
read -p "Input your key here:" CF_GlobalKey fi
LOGD "Your API key is:${CF_GlobalKey}" fi
LOGD "Please set up registered email:"
read -p "Input your email here:" CF_AccountEmail CF_Domain=""
LOGD "Your registered email address is:${CF_AccountEmail}" if [ ! -d "$certPath" ]; then
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt mkdir -p $certPath
if [ $? -ne 0 ]; then else
LOGE "Default CA, Lets'Encrypt fail, script exiting..." rm -rf $certPath
exit 1 mkdir -p $certPath
fi fi
export CF_Key="${CF_GlobalKey}"
export CF_Email=${CF_AccountEmail} LOGD "Please set a domain name:"
~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} --log read -p "Input your domain here: " CF_Domain
if [ $? -ne 0 ]; then LOGD "Your domain name is set to: ${CF_Domain}"
LOGE "Certificate issuance failed, script exiting..."
exit 1 CF_GlobalKey=""
else CF_AccountEmail=""
LOGI "Certificate issued Successfully, Installing..." LOGD "Please set the API key:"
fi read -p "Input your key here: " CF_GlobalKey
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} --ca-file /root/cert/ca.cer \ LOGD "Your API key is: ${CF_GlobalKey}"
--cert-file /root/cert/${CF_Domain}.cer --key-file /root/cert/${CF_Domain}.key \
--fullchain-file /root/cert/fullchain.cer LOGD "Please set up registered email:"
if [ $? -ne 0 ]; then read -p "Input your email here: " CF_AccountEmail
LOGE "Certificate installation failed, script exiting..." LOGD "Your registered email address is: ${CF_AccountEmail}"
exit 1
else ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
LOGI "Certificate installed Successfully,Turning on automatic updates..." if [ $? -ne 0 ]; then
fi LOGE "Default CA, Let's Encrypt failed, script exiting..."
~/.acme.sh/acme.sh --upgrade --auto-upgrade exit 1
if [ $? -ne 0 ]; then fi
LOGE "Auto update setup Failed, script exiting..."
ls -lah cert export CF_Key="${CF_GlobalKey}"
chmod 755 $certPath export CF_Email="${CF_AccountEmail}"
exit 1
else ~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} $force_flag --log
LOGI "The certificate is installed and auto-renewal is turned on, Specific information is as follows" if [ $? -ne 0 ]; then
ls -lah cert LOGE "Certificate issuance failed, script exiting..."
chmod 755 $certPath exit 1
fi 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 else
show_menu LOGE "Failed to generate self-signed certificate."
fi fi
before_show_menu
} }
show_usage() { show_usage() {
+61 -43
View File
@@ -1,6 +1,7 @@
package service package service
import ( import (
"bytes"
"encoding/json" "encoding/json"
"s-ui/database" "s-ui/database"
"s-ui/database/model" "s-ui/database/model"
@@ -13,9 +14,7 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
type ClientService struct { type ClientService struct{}
InboundService
}
func (s *ClientService) Get(id string) (*[]model.Client, error) { func (s *ClientService) Get(id string) (*[]model.Client, error) {
if id == "" { if id == "" {
@@ -56,13 +55,21 @@ func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, host
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = json.Unmarshal(client.Inbounds, &inboundIds) err = s.updateLinksWithFixedInbounds(tx, []*model.Client{&client}, hostname)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = s.updateLinksWithFixedInbounds(tx, []*model.Client{&client}, inboundIds, hostname) if act == "edit" {
if err != nil { // Find changed inbounds
return nil, err inboundIds, err = s.findInboundsChanges(tx, client)
if err != nil {
return nil, err
}
} else {
err = json.Unmarshal(client.Inbounds, &inboundIds)
if err != nil {
return nil, err
}
} }
err = tx.Save(&client).Error err = tx.Save(&client).Error
if err != nil { if err != nil {
@@ -78,7 +85,7 @@ func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, host
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = s.updateLinksWithFixedInbounds(tx, clients, inboundIds, hostname) err = s.updateLinksWithFixedInbounds(tx, clients, hostname)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -112,13 +119,19 @@ func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, host
return inboundIds, nil return inboundIds, nil
} }
func (s *ClientService) updateLinksWithFixedInbounds(tx *gorm.DB, clients []*model.Client, inbounIds []uint, hostname string) error { func (s *ClientService) updateLinksWithFixedInbounds(tx *gorm.DB, clients []*model.Client, hostname string) error {
var err error var err error
var inbounds []model.Inbound var inbounds []model.Inbound
var inboundIds []uint
err = json.Unmarshal(clients[0].Inbounds, &inboundIds)
if err != nil {
return err
}
// Zero inbounds means removing local links only // Zero inbounds means removing local links only
if len(inbounIds) > 0 { if len(inboundIds) > 0 {
err = tx.Model(model.Inbound{}).Preload("Tls").Where("id in ? and type in ?", inbounIds, util.InboundTypeWithLink).Find(&inbounds).Error err = tx.Model(model.Inbound{}).Preload("Tls").Where("id in ? and type in ?", inboundIds, util.InboundTypeWithLink).Find(&inbounds).Error
if err != nil { if err != nil {
return err return err
} }
@@ -142,7 +155,7 @@ func (s *ClientService) updateLinksWithFixedInbounds(tx *gorm.DB, clients []*mod
} }
} }
// Add no local links // Add non local links
for _, clientLink := range clientLinks { for _, clientLink := range clientLinks {
if clientLink["type"] != "local" { if clientLink["type"] != "local" {
newClientLinks = append(newClientLinks, clientLink) newClientLinks = append(newClientLinks, clientLink)
@@ -248,13 +261,9 @@ func (s *ClientService) UpdateClientsOnInboundDelete(tx *gorm.DB, id uint, tag s
return nil return nil
} }
func (s *ClientService) UpdateLinksByInboundChange(tx *gorm.DB, inbounIds []uint, hostname string) error { func (s *ClientService) UpdateLinksByInboundChange(tx *gorm.DB, inbounds *[]model.Inbound, hostname string, oldTag string) error {
var inbounds []model.Inbound var err error
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ? and type in ?", inbounIds, util.InboundTypeWithLink).Find(&inbounds).Error for _, inbound := range *inbounds {
if err != nil && database.IsNotFound(err) {
return err
}
for _, inbound := range inbounds {
var clients []model.Client var clients []model.Client
err = tx.Table("clients"). err = tx.Table("clients").
Where("EXISTS (SELECT 1 FROM json_each(clients.inbounds) WHERE json_each.value = ?)", inbound.Id). Where("EXISTS (SELECT 1 FROM json_each(clients.inbounds) WHERE json_each.value = ?)", inbound.Id).
@@ -274,7 +283,7 @@ func (s *ClientService) UpdateLinksByInboundChange(tx *gorm.DB, inbounIds []uint
}) })
} }
for _, clientLink := range clientLinks { for _, clientLink := range clientLinks {
if clientLink["remark"] != inbound.Tag { if clientLink["remark"] != inbound.Tag && clientLink["remark"] != oldTag {
newClientLinks = append(newClientLinks, clientLink) newClientLinks = append(newClientLinks, clientLink)
} }
} }
@@ -292,7 +301,7 @@ func (s *ClientService) UpdateLinksByInboundChange(tx *gorm.DB, inbounIds []uint
return nil return nil
} }
func (s *ClientService) DepleteClients() error { func (s *ClientService) DepleteClients() ([]uint, error) {
var err error var err error
var clients []model.Client var clients []model.Client
var changes []model.Changes var changes []model.Changes
@@ -306,12 +315,6 @@ func (s *ClientService) DepleteClients() error {
defer func() { defer func() {
if err == nil { if err == nil {
tx.Commit() tx.Commit()
if len(inboundIds) > 0 && corePtr.IsRunning() {
err1 := s.InboundService.RestartInbounds(db, inboundIds)
if err1 != nil {
logger.Error("unable to restart inbounds: ", err1)
}
}
} else { } else {
tx.Rollback() tx.Rollback()
} }
@@ -319,7 +322,7 @@ func (s *ClientService) DepleteClients() error {
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Scan(&clients).Error err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Scan(&clients).Error
if err != nil { if err != nil {
return err return nil, err
} }
dt := time.Now().Unix() dt := time.Now().Unix()
@@ -328,7 +331,8 @@ func (s *ClientService) DepleteClients() error {
users = append(users, client.Name) users = append(users, client.Name)
var userInbounds []uint var userInbounds []uint
json.Unmarshal(client.Inbounds, &userInbounds) json.Unmarshal(client.Inbounds, &userInbounds)
inboundIds = s.uniqueAppendInboundIds(inboundIds, userInbounds) // Find changed inbounds
inboundIds = common.UnionUintArray(inboundIds, userInbounds)
changes = append(changes, model.Changes{ changes = append(changes, model.Changes{
DateTime: dt, DateTime: dt,
Actor: "DepleteJob", Actor: "DepleteJob",
@@ -342,30 +346,44 @@ func (s *ClientService) DepleteClients() error {
if len(changes) > 0 { if len(changes) > 0 {
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Update("enable", false).Error err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Update("enable", false).Error
if err != nil { if err != nil {
return err return nil, err
} }
err = tx.Model(model.Changes{}).Create(&changes).Error err = tx.Model(model.Changes{}).Create(&changes).Error
if err != nil { if err != nil {
return err return nil, err
} }
LastUpdate = dt LastUpdate = dt
} }
return nil return inboundIds, nil
} }
// avoid duplicate inboundIds func (s *ClientService) findInboundsChanges(tx *gorm.DB, client model.Client) ([]uint, error) {
func (s *ClientService) uniqueAppendInboundIds(a []uint, b []uint) []uint { var err error
m := make(map[uint]bool) var oldClient model.Client
for _, v := range a { var oldInboundIds, newInboundIds []uint
m[v] = true err = tx.Model(model.Client{}).Where("id = ?", client.Id).First(&oldClient).Error
if err != nil {
return nil, err
} }
for _, v := range b { err = json.Unmarshal(oldClient.Inbounds, &oldInboundIds)
m[v] = true if err != nil {
return nil, err
} }
var res []uint err = json.Unmarshal(client.Inbounds, &newInboundIds)
for k := range m { if err != nil {
res = append(res, k) return nil, err
} }
return res
// Check client.Config changes
if !bytes.Equal(oldClient.Config, client.Config) ||
oldClient.Name != client.Name ||
oldClient.Enable != client.Enable {
return common.UnionUintArray(oldInboundIds, newInboundIds), nil
}
// Check client.Inbounds changes
diffInbounds := common.DiffUintArray(oldInboundIds, newInboundIds)
return diffInbounds, nil
} }
+21 -54
View File
@@ -22,6 +22,7 @@ type ConfigService struct {
SettingService SettingService
InboundService InboundService
OutboundService OutboundService
ServicesService
EndpointService EndpointService
} }
@@ -31,6 +32,7 @@ type SingBoxConfig struct {
Ntp json.RawMessage `json:"ntp"` Ntp json.RawMessage `json:"ntp"`
Inbounds []json.RawMessage `json:"inbounds"` Inbounds []json.RawMessage `json:"inbounds"`
Outbounds []json.RawMessage `json:"outbounds"` Outbounds []json.RawMessage `json:"outbounds"`
Services []json.RawMessage `json:"services"`
Endpoints []json.RawMessage `json:"endpoints"` Endpoints []json.RawMessage `json:"endpoints"`
Route json.RawMessage `json:"route"` Route json.RawMessage `json:"route"`
Experimental json.RawMessage `json:"experimental"` Experimental json.RawMessage `json:"experimental"`
@@ -63,6 +65,10 @@ func (s *ConfigService) GetConfig(data string) (*SingBoxConfig, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
singboxConfig.Services, err = s.ServicesService.GetAllConfig(database.GetDB())
if err != nil {
return nil, err
}
singboxConfig.Endpoints, err = s.EndpointService.GetAllConfig(database.GetDB()) singboxConfig.Endpoints, err = s.EndpointService.GetAllConfig(database.GetDB())
if err != nil { if err != nil {
return nil, err return nil, err
@@ -118,8 +124,6 @@ func (s *ConfigService) StopCore() error {
func (s *ConfigService) Save(obj string, act string, data json.RawMessage, initUsers string, loginUser string, hostname string) ([]string, error) { func (s *ConfigService) Save(obj string, act string, data json.RawMessage, initUsers string, loginUser string, hostname string) ([]string, error) {
var err error var err error
var inboundIds []uint
var inboundId uint
var objs []string = []string{obj} var objs []string = []string{obj}
db := database.GetDB() db := database.GetDB()
@@ -127,12 +131,6 @@ func (s *ConfigService) Save(obj string, act string, data json.RawMessage, initU
defer func() { defer func() {
if err == nil { if err == nil {
tx.Commit() tx.Commit()
if len(inboundIds) > 0 && corePtr.IsRunning() {
err1 := s.InboundService.RestartInbounds(db, inboundIds)
if err1 != nil {
logger.Error("unable to restart inbounds: ", err1)
}
}
// Try to start core if it is not running // Try to start core if it is not running
if !corePtr.IsRunning() { if !corePtr.IsRunning() {
s.StartCore("") s.StartCore("")
@@ -144,14 +142,24 @@ func (s *ConfigService) Save(obj string, act string, data json.RawMessage, initU
switch obj { switch obj {
case "clients": case "clients":
inboundIds, err = s.ClientService.Save(tx, act, data, hostname) inboundIds, err := s.ClientService.Save(tx, act, data, hostname)
objs = append(objs, "inbounds") if err == nil && len(inboundIds) > 0 {
objs = append(objs, "inbounds")
err = s.InboundService.RestartInbounds(tx, inboundIds)
if err != nil {
return nil, common.NewErrorf("failed to update users for inbounds: %v", err)
}
}
case "tls": case "tls":
inboundIds, err = s.TlsService.Save(tx, act, data) err = s.TlsService.Save(tx, act, data, hostname)
objs = append(objs, "clients", "inbounds")
case "inbounds": case "inbounds":
inboundId, err = s.InboundService.Save(tx, act, data, initUsers, hostname) err = s.InboundService.Save(tx, act, data, initUsers, hostname)
objs = append(objs, "clients")
case "outbounds": case "outbounds":
err = s.OutboundService.Save(tx, act, data) err = s.OutboundService.Save(tx, act, data)
case "services":
err = s.ServicesService.Save(tx, act, data)
case "endpoints": case "endpoints":
err = s.EndpointService.Save(tx, act, data) err = s.EndpointService.Save(tx, act, data)
case "config": case "config":
@@ -180,49 +188,8 @@ func (s *ConfigService) Save(obj string, act string, data json.RawMessage, initU
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Commit changes so far
tx.Commit()
LastUpdate = time.Now().Unix() LastUpdate = time.Now().Unix()
tx = db.Begin()
// Update side changes
// Update client links
if obj == "tls" && len(inboundIds) > 0 {
err = s.ClientService.UpdateLinksByInboundChange(tx, inboundIds, hostname)
if err != nil {
return nil, err
}
objs = append(objs, "clients")
}
if obj == "inbounds" {
switch act {
case "new":
err = s.ClientService.UpdateClientsOnInboundAdd(tx, initUsers, inboundId, hostname)
case "edit":
err = s.ClientService.UpdateLinksByInboundChange(tx, []uint{inboundId}, hostname)
case "del":
var tag string
err = json.Unmarshal(data, &tag)
if err != nil {
return nil, err
}
err = s.ClientService.UpdateClientsOnInboundDelete(tx, inboundId, tag)
}
if err != nil {
return nil, err
}
objs = append(objs, "clients")
}
// Update out_json of inbounds when tls is changed
if obj == "tls" && len(inboundIds) > 0 {
err = s.InboundService.UpdateOutJsons(tx, inboundIds, hostname)
if err != nil {
return nil, common.NewError("unable to update out_json of inbounds: ", err.Error())
}
objs = append(objs, "inbounds")
}
return objs, nil return objs, nil
} }
+110 -97
View File
@@ -2,6 +2,7 @@ package service
import ( import (
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"s-ui/database" "s-ui/database"
"s-ui/database/model" "s-ui/database/model"
@@ -12,7 +13,9 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
type InboundService struct{} type InboundService struct {
ClientService
}
func (s *InboundService) Get(ids string) (*[]map[string]interface{}, error) { func (s *InboundService) Get(ids string) (*[]map[string]interface{}, error) {
if ids == "" { if ids == "" {
@@ -49,6 +52,7 @@ func (s *InboundService) GetAll() (*[]map[string]interface{}, error) {
var data []map[string]interface{} var data []map[string]interface{}
for _, inbound := range inbounds { for _, inbound := range inbounds {
var shadowtls_version uint var shadowtls_version uint
ss_managed := false
inbData := map[string]interface{}{ inbData := map[string]interface{}{
"id": inbound.Id, "id": inbound.Id,
"type": inbound.Type, "type": inbound.Type,
@@ -65,20 +69,19 @@ func (s *InboundService) GetAll() (*[]map[string]interface{}, error) {
if inbound.Type == "shadowtls" { if inbound.Type == "shadowtls" {
json.Unmarshal(restFields["version"], &shadowtls_version) json.Unmarshal(restFields["version"], &shadowtls_version)
} }
} if inbound.Type == "shadowsocks" {
switch inbound.Type { json.Unmarshal(restFields["managed"], &ss_managed)
case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless":
if inbound.Type == "shadowtls" && shadowtls_version < 3 {
break
} }
}
if s.hasUser(inbound.Type) &&
!(inbound.Type == "shadowtls" && shadowtls_version < 3) &&
!(inbound.Type == "shadowsocks" && ss_managed) {
users := []string{} users := []string{}
err = db.Raw("SELECT clients.name FROM clients, json_each(clients.inbounds) as je WHERE je.value = ?", inbound.Id).Scan(&users).Error err = db.Raw("SELECT clients.name FROM clients, json_each(clients.inbounds) as je WHERE je.value = ?", inbound.Id).Scan(&users).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(users) > 0 || inbound.Type != "shadowsocks" { inbData["users"] = users
inbData["users"] = users
}
} }
data = append(data, inbData) data = append(data, inbData)
@@ -96,51 +99,41 @@ func (s *InboundService) FromIds(ids []uint) ([]*model.Inbound, error) {
return inbounds, nil return inbounds, nil
} }
func (s *InboundService) Save(tx *gorm.DB, act string, data json.RawMessage, initUserIds string, hostname string) (uint, error) { func (s *InboundService) Save(tx *gorm.DB, act string, data json.RawMessage, initUserIds string, hostname string) error {
var err error var err error
var id uint
switch act { switch act {
case "new", "edit": case "new", "edit":
var inbound model.Inbound var inbound model.Inbound
err = inbound.UnmarshalJSON(data) err = inbound.UnmarshalJSON(data)
if err != nil { if err != nil {
return 0, err return err
} }
if inbound.TlsId > 0 { if inbound.TlsId > 0 {
err = tx.Model(model.Tls{}).Where("id = ?", inbound.TlsId).Find(&inbound.Tls).Error err = tx.Model(model.Tls{}).Where("id = ?", inbound.TlsId).Find(&inbound.Tls).Error
if err != nil { if err != nil {
return 0, err return err
}
}
var oldTag string
if act == "edit" {
err = tx.Model(model.Inbound{}).Select("tag").Where("id = ?", inbound.Id).Find(&oldTag).Error
if err != nil {
return err
} }
} }
err = util.FillOutJson(&inbound, hostname)
if err != nil {
return 0, err
}
err = tx.Save(&inbound).Error
if err != nil {
return 0, err
}
id = inbound.Id
if corePtr.IsRunning() { if corePtr.IsRunning() {
if act == "edit" { if act == "edit" {
var oldTag string
err = tx.Model(model.Inbound{}).Select("tag").Where("id = ?", inbound.Id).Find(&oldTag).Error
if err != nil {
return 0, err
}
err = corePtr.RemoveInbound(oldTag) err = corePtr.RemoveInbound(oldTag)
if err != nil && err != os.ErrInvalid { if err != nil && err != os.ErrInvalid {
return 0, err return err
} }
} }
inboundConfig, err := inbound.MarshalJSON() inboundConfig, err := inbound.MarshalJSON()
if err != nil { if err != nil {
return 0, err return err
} }
if act == "edit" { if act == "edit" {
@@ -149,38 +142,62 @@ func (s *InboundService) Save(tx *gorm.DB, act string, data json.RawMessage, ini
inboundConfig, err = s.initUsers(tx, inboundConfig, initUserIds, inbound.Type) inboundConfig, err = s.initUsers(tx, inboundConfig, initUserIds, inbound.Type)
} }
if err != nil { if err != nil {
return 0, err return err
} }
err = corePtr.AddInbound(inboundConfig) err = corePtr.AddInbound(inboundConfig)
if err != nil { if err != nil {
return 0, err return err
} }
} }
err = util.FillOutJson(&inbound, hostname)
if err != nil {
return err
}
err = tx.Save(&inbound).Error
if err != nil {
return err
}
switch act {
case "new":
err = s.ClientService.UpdateClientsOnInboundAdd(tx, initUserIds, inbound.Id, hostname)
case "edit":
err = s.ClientService.UpdateLinksByInboundChange(tx, &[]model.Inbound{inbound}, hostname, oldTag)
}
if err != nil {
return err
}
case "del": case "del":
var tag string var tag string
err = json.Unmarshal(data, &tag) err = json.Unmarshal(data, &tag)
if err != nil { if err != nil {
return 0, err return err
} }
if corePtr.IsRunning() { if corePtr.IsRunning() {
err = corePtr.RemoveInbound(tag) err = corePtr.RemoveInbound(tag)
if err != nil && err != os.ErrInvalid { if err != nil && err != os.ErrInvalid {
return 0, err return err
} }
} }
var id uint
err = tx.Model(model.Inbound{}).Select("id").Where("tag = ?", tag).Scan(&id).Error err = tx.Model(model.Inbound{}).Select("id").Where("tag = ?", tag).Scan(&id).Error
if err != nil { if err != nil {
return 0, err return err
}
err = s.ClientService.UpdateClientsOnInboundDelete(tx, id, tag)
if err != nil {
return err
} }
err = tx.Where("tag = ?", tag).Delete(model.Inbound{}).Error err = tx.Where("tag = ?", tag).Delete(model.Inbound{}).Error
if err != nil { if err != nil {
return 0, err return err
} }
default: default:
return 0, common.NewErrorf("unknown action: %s", act) return common.NewErrorf("unknown action: %s", act)
} }
return id, nil return nil
} }
func (s *InboundService) UpdateOutJsons(tx *gorm.DB, inboundIds []uint, hostname string) error { func (s *InboundService) UpdateOutJsons(tx *gorm.DB, inboundIds []uint, hostname string) error {
@@ -224,11 +241,49 @@ func (s *InboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
return inboundsJson, nil return inboundsJson, nil
} }
func (s *InboundService) addUsers(db *gorm.DB, inboundJson []byte, inboundId uint, inboundType string) ([]byte, error) { func (s *InboundService) hasUser(inboundType string) bool {
switch inboundType { switch inboundType {
case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless": case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless", "anytls":
break return true
default: }
return false
}
func (s *InboundService) fetchUsers(db *gorm.DB, inboundType string, condition string, inbound map[string]interface{}) ([]json.RawMessage, error) {
if inboundType == "shadowtls" {
version, _ := inbound["version"].(float64)
if int(version) < 3 {
return nil, nil
}
}
if inboundType == "shadowsocks" {
method, _ := inbound["method"].(string)
if method == "2022-blake3-aes-128-gcm" {
inboundType = "shadowsocks16"
}
}
var users []string
err := db.Raw(
fmt.Sprintf(`SELECT json_extract(clients.config, "$.%s")
FROM clients WHERE enable = true AND %s`,
inboundType, condition)).Scan(&users).Error
if err != nil {
return nil, err
}
var usersJson []json.RawMessage
for _, user := range users {
if inboundType == "vless" && inbound["tls"] == nil {
user = strings.Replace(user, "xtls-rprx-vision", "", -1)
}
usersJson = append(usersJson, json.RawMessage(user))
}
return usersJson, nil
}
func (s *InboundService) addUsers(db *gorm.DB, inboundJson []byte, inboundId uint, inboundType string) ([]byte, error) {
if !s.hasUser(inboundType) {
return inboundJson, nil return inboundJson, nil
} }
@@ -238,34 +293,11 @@ func (s *InboundService) addUsers(db *gorm.DB, inboundJson []byte, inboundId uin
return nil, err return nil, err
} }
if inboundType == "shadowtls" { condition := fmt.Sprintf("%d IN (SELECT json_each.value FROM json_each(clients.inbounds))", inboundId)
version, _ := inbound["version"].(float64) inbound["users"], err = s.fetchUsers(db, inboundType, condition, inbound)
if int(version) < 3 {
return inboundJson, nil
}
}
if inboundType == "shadowsocks" {
method, _ := inbound["method"].(string)
if method == "2022-blake3-aes-128-gcm" {
inboundType = "shadowsocks16"
}
}
var users []string
err = db.Raw(`SELECT json_extract(clients.config, ?)
FROM clients, json_each(clients.inbounds) as je
WHERE clients.enable = true AND je.value = ?;`,
"$."+inboundType, inboundId).Scan(&users).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
var usersJson []json.RawMessage
for _, user := range users {
usersJson = append(usersJson, json.RawMessage(user))
}
if len(usersJson) > 0 || inboundType != "shadowsocks" {
inbound["users"] = usersJson
}
return json.Marshal(inbound) return json.Marshal(inbound)
} }
@@ -275,10 +307,8 @@ func (s *InboundService) initUsers(db *gorm.DB, inboundJson []byte, clientIds st
if len(ClientIds) == 0 { if len(ClientIds) == 0 {
return inboundJson, nil return inboundJson, nil
} }
switch inboundType {
case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless": if !s.hasUser(inboundType) {
break
default:
return inboundJson, nil return inboundJson, nil
} }
@@ -288,39 +318,19 @@ func (s *InboundService) initUsers(db *gorm.DB, inboundJson []byte, clientIds st
return nil, err return nil, err
} }
if inboundType == "shadowtls" { condition := fmt.Sprintf("id IN (%s)", strings.Join(ClientIds, ","))
version, _ := inbound["version"].(float64) inbound["users"], err = s.fetchUsers(db, inboundType, condition, inbound)
if int(version) < 3 {
return inboundJson, nil
}
}
if inboundType == "shadowsocks" {
method, _ := inbound["method"].(string)
if method == "2022-blake3-aes-128-gcm" {
inboundType = "shadowsocks16"
}
}
var users []string
err = db.Raw(`SELECT json_extract(clients.config, ?)
FROM clients
WHERE enable = true AND id in ?`,
"$."+inboundType, ClientIds).Scan(&users).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
var usersJson []json.RawMessage
for _, user := range users {
usersJson = append(usersJson, json.RawMessage(user))
}
if len(usersJson) > 0 || inboundType != "shadowsocks" {
inbound["users"] = usersJson
}
return json.Marshal(inbound) return json.Marshal(inbound)
} }
func (s *InboundService) RestartInbounds(tx *gorm.DB, ids []uint) error { func (s *InboundService) RestartInbounds(tx *gorm.DB, ids []uint) error {
if !corePtr.IsRunning() {
return nil
}
var inbounds []*model.Inbound var inbounds []*model.Inbound
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ?", ids).Find(&inbounds).Error err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ?", ids).Find(&inbounds).Error
if err != nil { if err != nil {
@@ -336,6 +346,9 @@ func (s *InboundService) RestartInbounds(tx *gorm.DB, ids []uint) error {
return err return err
} }
inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type) inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type)
if err != nil {
return err
}
err = corePtr.AddInbound(inboundConfig) err = corePtr.AddInbound(inboundConfig)
if err != nil { if err != nil {
return err return err
+53 -7
View File
@@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
"github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/disk"
"github.com/shirou/gopsutil/v4/host" "github.com/shirou/gopsutil/v4/host"
"github.com/shirou/gopsutil/v4/mem" "github.com/shirou/gopsutil/v4/mem"
"github.com/shirou/gopsutil/v4/net" "github.com/shirou/gopsutil/v4/net"
@@ -29,6 +30,12 @@ func (s *ServerService) GetStatus(request string) *map[string]interface{} {
status["cpu"] = s.GetCpuPercent() status["cpu"] = s.GetCpuPercent()
case "mem": case "mem":
status["mem"] = s.GetMemInfo() status["mem"] = s.GetMemInfo()
case "dsk":
status["dsk"] = s.GetDiskInfo()
case "dio":
status["dio"] = s.GetDiskIO()
case "swp":
status["swp"] = s.GetSwapInfo()
case "net": case "net":
status["net"] = s.GetNetInfo() status["net"] = s.GetNetInfo()
case "sys": case "sys":
@@ -73,6 +80,49 @@ func (s *ServerService) GetMemInfo() map[string]interface{} {
return info return info
} }
func (s *ServerService) GetDiskInfo() map[string]interface{} {
info := make(map[string]interface{}, 0)
diskInfo, err := disk.Usage("/")
if err != nil {
logger.Warning("get disk usage failed:", err)
} else {
info["current"] = diskInfo.Used
info["total"] = diskInfo.Total
}
return info
}
func (s *ServerService) GetDiskIO() map[string]interface{} {
info := make(map[string]interface{}, 0)
ioStats, err := disk.IOCounters()
if err != nil {
logger.Warning("get disk io counters failed:", err)
} else if len(ioStats) > 0 {
infoR, infoW := uint64(0), uint64(0)
for _, ioStat := range ioStats {
infoR += ioStat.ReadBytes
infoW += ioStat.WriteBytes
}
info["read"] = infoR
info["write"] = infoW
} else {
logger.Warning("can not find disk io counters")
}
return info
}
func (s *ServerService) GetSwapInfo() map[string]interface{} {
info := make(map[string]interface{}, 0)
swapInfo, err := mem.SwapMemory()
if err != nil {
logger.Warning("get swap memory failed:", err)
} else {
info["current"] = swapInfo.Used
info["total"] = swapInfo.Total
}
return info
}
func (s *ServerService) GetNetInfo() map[string]interface{} { func (s *ServerService) GetNetInfo() map[string]interface{} {
info := make(map[string]interface{}, 0) info := make(map[string]interface{}, 0)
ioStats, err := net.IOCounters(false) ioStats, err := net.IOCounters(false)
@@ -172,12 +222,8 @@ func (s *ServerService) GenKeypair(keyType string, options string) []string {
return []string{"Failed to generate keypair"} return []string{"Failed to generate keypair"}
} }
func (s *ServerService) generateECHKeyPair(options string) []string { func (s *ServerService) generateECHKeyPair(serverName string) []string {
parts := strings.Split(options, ",") configPem, keyPem, err := tls.ECHKeygenDefault(serverName)
if len(parts) != 2 {
return []string{"Failed to generate ECH keypair: ", "invalid options"}
}
configPem, keyPem, err := tls.ECHKeygenDefault(parts[0], parts[1] == "true")
if err != nil { if err != nil {
return []string{"Failed to generate ECH keypair: ", err.Error()} return []string{"Failed to generate ECH keypair: ", err.Error()}
} }
@@ -185,7 +231,7 @@ func (s *ServerService) generateECHKeyPair(options string) []string {
} }
func (s *ServerService) generateTLSKeyPair(serverName string) []string { func (s *ServerService) generateTLSKeyPair(serverName string) []string {
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, 12, 0)) privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, 12, 0))
if err != nil { if err != nil {
return []string{"Failed to generate TLS keypair: ", err.Error()} return []string{"Failed to generate TLS keypair: ", err.Error()}
} }
+152
View File
@@ -0,0 +1,152 @@
package service
import (
"encoding/json"
"os"
"s-ui/database"
"s-ui/database/model"
"s-ui/util/common"
"gorm.io/gorm"
)
type ServicesService struct{}
func (s *ServicesService) GetAll() (*[]map[string]interface{}, error) {
db := database.GetDB()
services := []model.Service{}
err := db.Model(model.Service{}).Scan(&services).Error
if err != nil {
return nil, err
}
var data []map[string]interface{}
for _, srv := range services {
srvData := map[string]interface{}{
"id": srv.Id,
"type": srv.Type,
"tag": srv.Tag,
"tls_id": srv.TlsId,
}
if srv.Options != nil {
var restFields map[string]json.RawMessage
if err := json.Unmarshal(srv.Options, &restFields); err != nil {
return nil, err
}
for k, v := range restFields {
srvData[k] = v
}
}
data = append(data, srvData)
}
return &data, nil
}
func (s *ServicesService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
var servicesJson []json.RawMessage
var services []*model.Service
err := db.Model(model.Service{}).Preload("Tls").Find(&services).Error
if err != nil {
return nil, err
}
for _, srv := range services {
srvJson, err := srv.MarshalJSON()
if err != nil {
return nil, err
}
servicesJson = append(servicesJson, srvJson)
}
return servicesJson, nil
}
func (s *ServicesService) Save(tx *gorm.DB, act string, data json.RawMessage) error {
var err error
switch act {
case "new", "edit":
var srv model.Service
err = srv.UnmarshalJSON(data)
if err != nil {
return err
}
if srv.TlsId > 0 {
err = tx.Model(model.Tls{}).Where("id = ?", srv.TlsId).Find(&srv.Tls).Error
if err != nil {
return err
}
}
if corePtr.IsRunning() {
configData, err := srv.MarshalJSON()
if err != nil {
return err
}
if act == "edit" {
var oldTag string
err = tx.Model(model.Service{}).Select("tag").Where("id = ?", srv.Id).Find(&oldTag).Error
if err != nil {
return err
}
err = corePtr.RemoveService(oldTag)
if err != nil && err != os.ErrInvalid {
return err
}
}
err = corePtr.AddService(configData)
if err != nil {
return err
}
}
err = tx.Save(&srv).Error
if err != nil {
return err
}
case "del":
var tag string
err = json.Unmarshal(data, &tag)
if err != nil {
return err
}
if corePtr.IsRunning() {
err = corePtr.RemoveService(tag)
if err != nil && err != os.ErrInvalid {
return err
}
}
err = tx.Where("tag = ?", tag).Delete(model.Service{}).Error
if err != nil {
return err
}
default:
return common.NewErrorf("unknown action: %s", act)
}
return nil
}
func (s *ServicesService) RestartServices(tx *gorm.DB, ids []uint) error {
if !corePtr.IsRunning() {
return nil
}
var services []*model.Service
err := tx.Model(model.Service{}).Preload("Tls").Where("id in ?", ids).Find(&services).Error
if err != nil {
return err
}
for _, srv := range services {
err = corePtr.RemoveService(srv.Tag)
if err != nil && err != os.ErrInvalid {
return err
}
srvConfig, err := srv.MarshalJSON()
if err != nil {
return err
}
err = corePtr.AddService(srvConfig)
if err != nil {
return err
}
}
return nil
}
+6 -1
View File
@@ -56,6 +56,7 @@ var defaultValueMap = map[string]string{
"subShowInfo": "false", "subShowInfo": "false",
"subURI": "", "subURI": "",
"subJsonExt": "", "subJsonExt": "",
"subClashExt": "",
"config": defaultConfig, "config": defaultConfig,
"version": config.GetVersion(), "version": config.GetVersion(),
} }
@@ -358,7 +359,7 @@ func (s *SettingService) Save(tx *gorm.DB, data json.RawMessage) error {
return err return err
} }
for key, obj := range settings { for key, obj := range settings {
// Secure file existance check // Secure file existence check
if obj != "" && (key == "webCertFile" || if obj != "" && (key == "webCertFile" ||
key == "webKeyFile" || key == "webKeyFile" ||
key == "subCertFile" || key == "subCertFile" ||
@@ -392,6 +393,10 @@ func (s *SettingService) GetSubJsonExt() (string, error) {
return s.getString("subJsonExt") return s.getString("subJsonExt")
} }
func (s *SettingService) GetSubClashExt() (string, error) {
return s.getString("subClashExt")
}
func (s *SettingService) fileExists(path string) error { func (s *SettingService) fileExists(path string) error {
_, err := os.Stat(path) _, err := os.Stat(path)
return err return err
+50 -14
View File
@@ -11,6 +11,7 @@ import (
type TlsService struct { type TlsService struct {
InboundService InboundService
ServicesService
} }
func (s *TlsService) GetAll() ([]model.Tls, error) { func (s *TlsService) GetAll() ([]model.Tls, error) {
@@ -24,45 +25,80 @@ func (s *TlsService) GetAll() ([]model.Tls, error) {
return tlsConfig, nil return tlsConfig, nil
} }
func (s *TlsService) Save(tx *gorm.DB, action string, data json.RawMessage) ([]uint, error) { func (s *TlsService) Save(tx *gorm.DB, action string, data json.RawMessage, hostname string) error {
var err error var err error
var inboundIds []uint
switch action { switch action {
case "new", "edit": case "new", "edit":
var tls model.Tls var tls model.Tls
err = json.Unmarshal(data, &tls) err = json.Unmarshal(data, &tls)
if err != nil { if err != nil {
return nil, err return err
} }
err = tx.Save(&tls).Error err = tx.Save(&tls).Error
if err != nil { if err != nil {
return nil, err return err
} }
err = tx.Model(model.Inbound{}).Select("id").Where("tls_id = ?", tls.Id).Scan(&inboundIds).Error if action == "edit" {
if err != nil { var inbounds []model.Inbound
return nil, err err = tx.Model(model.Inbound{}).Preload("Tls").Where("tls_id = ?", tls.Id).Find(&inbounds).Error
if err != nil {
return err
}
if len(inbounds) > 0 {
err = s.ClientService.UpdateLinksByInboundChange(tx, &inbounds, hostname, "")
if err != nil {
return err
}
var inboundIds []uint
for _, inbound := range inbounds {
inboundIds = append(inboundIds, inbound.Id)
}
err = s.InboundService.UpdateOutJsons(tx, inboundIds, hostname)
if err != nil {
return common.NewError("unable to update out_json of inbounds: ", err.Error())
}
err = s.InboundService.RestartInbounds(tx, inboundIds)
if err != nil {
return err
}
}
var serviceIds []uint
err = tx.Model(model.Service{}).Where("tls_id = ?", tls.Id).Scan(&serviceIds).Error
if err != nil {
return err
}
if len(serviceIds) > 0 {
err = s.ServicesService.RestartServices(tx, serviceIds)
if err != nil {
return err
}
}
} }
return inboundIds, nil
case "del": case "del":
var id uint var id uint
err = json.Unmarshal(data, &id) err = json.Unmarshal(data, &id)
if err != nil { if err != nil {
return nil, err return err
} }
var inboundCount int64 var inboundCount int64
err = tx.Model(model.Inbound{}).Where("tls_id = ?", id).Count(&inboundCount).Error err = tx.Model(model.Inbound{}).Where("tls_id = ?", id).Count(&inboundCount).Error
if err != nil { if err != nil {
return nil, err return err
} }
if inboundCount > 0 { var serviceCount int64
return nil, common.NewError("tls in use") err = tx.Model(model.Service{}).Where("tls_id = ?", id).Count(&serviceCount).Error
if err != nil {
return err
}
if inboundCount > 0 || serviceCount > 0 {
return common.NewError("tls in use")
} }
err = tx.Where("id = ?", id).Delete(model.Tls{}).Error err = tx.Where("id = ?", id).Delete(model.Tls{}).Error
if err != nil { if err != nil {
return nil, err return err
} }
} }
return nil, nil return nil
} }
+17 -23
View File
@@ -19,24 +19,18 @@ import (
type WarpService struct{} type WarpService struct{}
func (s *WarpService) getWarpInfo(ep *model.Endpoint) ([]byte, error) { func (s *WarpService) getWarpInfo(deviceId string, accessToken string) ([]byte, error) {
var warpData map[string]string url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", deviceId)
err := json.Unmarshal(ep.Ext, &warpData)
if err != nil {
return nil, err
}
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", warpData["device_id"])
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Header.Set("Authorization", "Bearer "+warpData["access_token"]) req.Header.Set("Authorization", "Bearer "+accessToken)
client := &http.Client{} client := &http.Client{}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil || resp.StatusCode != 200 {
return nil, err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
@@ -69,7 +63,7 @@ func (s *WarpService) RegisterWarp(ep *model.Endpoint) error {
client := &http.Client{} client := &http.Client{}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil || resp.StatusCode != 200 {
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
@@ -94,18 +88,7 @@ func (s *WarpService) RegisterWarp(ep *model.Endpoint) error {
return err return err
} }
warpData := map[string]string{ warpInfo, err := s.getWarpInfo(deviceId, token)
"access_token": token,
"device_id": deviceId,
"license_key": license,
}
ep.Ext, err = json.MarshalIndent(warpData, "", " ")
if err != nil {
return err
}
warpInfo, err := s.getWarpInfo(ep)
if err != nil { if err != nil {
return err return err
} }
@@ -142,6 +125,17 @@ func (s *WarpService) RegisterWarp(ep *model.Endpoint) error {
}, },
} }
warpData := map[string]interface{}{
"access_token": token,
"device_id": deviceId,
"license_key": license,
}
ep.Ext, err = json.MarshalIndent(warpData, "", " ")
if err != nil {
return err
}
var epOptions map[string]interface{} var epOptions map[string]interface{}
err = json.Unmarshal(ep.Options, &epOptions) err = json.Unmarshal(ep.Options, &epOptions)
if err != nil { if err != nil {
+332
View File
@@ -0,0 +1,332 @@
package sub
import (
"s-ui/logger"
"s-ui/service"
"s-ui/util"
"strings"
"gopkg.in/yaml.v3"
)
type ClashService struct {
service.SettingService
JsonService
LinkService
}
const basicClashConfig = `mixed-port: 7890
allow-lan: false
mode: rule
log-level: info
external-controller: 127.0.0.1:9090
tun:
enable: true
stack: system
auto-route: true
auto-detect-interface: true
dns-hijack:
- any:53
dns:
enable: true
ipv6: false
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
default-nameserver:
- 8.8.8.8
- 1.1.1.1
nameserver:
- https://doh.pub/dns-query
- https://1.0.0.1/dns-query
fallback:
- tcp://9.9.9.9:53
fake-ip-filter:
- "*.lan"
- localhost
- "*.local"
rules:
- GEOIP,Private,DIRECT
- MATCH,Proxy
`
const ProxyGroups = `- name: Proxy
type: select
proxies: []
- name: Auto
type: url-test
proxies: []
url: http://www.gstatic.com/generate_204
interval: 300
tolerance: 50
`
func (s *ClashService) GetClash(subId string) (*string, []string, error) {
client, inDatas, err := s.getData(subId)
if err != nil {
return nil, nil, err
}
outbounds, outTags, err := s.getOutbounds(client.Config, inDatas)
if err != nil {
return nil, nil, err
}
links := s.LinkService.GetLinks(&client.Links, "external", "")
for index, link := range links {
json, tag, err := util.GetOutbound(link, index)
if err == nil && len(tag) > 0 {
*outbounds = append(*outbounds, *json)
*outTags = append(*outTags, tag)
}
}
othersStr, err := s.getClashConfig()
if err != nil || len(othersStr) == 0 {
othersStr = basicClashConfig
}
result, err := s.ConvertToClashMeta(outbounds)
resultStr := othersStr + "\n" + string(result)
updateInterval, _ := s.SettingService.GetSubUpdates()
headers := util.GetHeaders(client, updateInterval)
return &resultStr, headers, nil
}
func (s *ClashService) getClashConfig() (string, error) {
subClashExt, err := s.SettingService.GetSubClashExt()
if err != nil {
return "", err
}
return subClashExt, nil
}
func (s *ClashService) ConvertToClashMeta(outbounds *[]map[string]interface{}) ([]byte, error) {
var proxies []interface{}
proxyTags := make([]string, 0)
for _, obMap := range *outbounds {
t, _ := obMap["type"].(string)
if t == "selector" || t == "urltest" || t == "direct" {
continue
}
proxy := make(map[string]interface{})
proxy["name"] = obMap["tag"]
proxy["type"] = t
proxy["server"] = obMap["server"]
proxy["port"] = obMap["server_port"]
switch t {
case "vmess", "vless", "tuic":
proxy["uuid"] = obMap["uuid"]
if t == "vmess" {
proxy["alterId"] = obMap["alter_id"]
proxy["cipher"] = "auto"
}
if t == "vless" {
if flow, ok := obMap["flow"].(string); ok {
proxy["flow"] = flow
}
}
case "trojan":
proxy["password"] = obMap["password"]
case "socks", "http":
if t == "socks" {
proxy["type"] = "socks5"
}
proxy["username"] = obMap["username"]
proxy["password"] = obMap["password"]
case "hysteria", "hysteria2":
if _, ok := obMap["up_mbps"].(float64); ok {
proxy["up"] = obMap["up_mbps"]
} else {
proxy["up"] = 1000
}
if _, ok := obMap["down_mbps"].(float64); ok {
proxy["down"] = obMap["down_mbps"]
} else {
proxy["down"] = 1000
}
if t == "hysteria" {
proxy["auth-str"] = obMap["auth_str"]
if obfs, ok := obMap["obfs"].(string); ok {
proxy["obfs"] = obfs
}
} else {
proxy["password"] = obMap["password"]
if obfs, ok := obMap["obfs"].(map[string]interface{}); ok {
proxy["obfs"] = obfs["type"]
proxy["obfs-password"] = obfs["password"]
}
if ports, ok := obMap["server_ports"].([]string); ok {
proxy["ports"] = strings.ReplaceAll(strings.Join(ports, ","), ":", "-")
}
}
case "anytls":
proxy["password"] = obMap["password"]
if tls, ok := obMap["tls"].(map[string]interface{}); ok {
proxy["sni"] = tls["server_name"]
proxy["skip-cert-verify"] = tls["insecure"]
}
default:
continue
}
// TLS params
tls, isTls := obMap["tls"].(map[string]interface{})
if isTls {
tlsEnabled, ok := tls["enabled"].(bool)
if ok && !tlsEnabled {
isTls = false
}
}
if isTls {
// ignore ech outbounds
if _, ok := tls["ech"].(interface{}); ok {
continue
}
proxy["tls"] = tls["enabled"]
// ALPN if exists
if alpn, ok := tls["alpn"].([]interface{}); ok {
proxy["alpn"] = alpn
}
// Add reality if exists
if reality, ok := tls["reality"].(map[string]interface{}); ok && reality["enabled"].(bool) {
reality_opts := make(map[string]interface{})
if pbk, ok := reality["public_key"].(string); ok {
reality_opts["public-key"] = pbk
}
if sid, ok := reality["short_id"].(string); ok {
reality_opts["short-id"] = sid
}
proxy["reality-opts"] = reality_opts
}
if utls, ok := tls["utls"].(map[string]interface{}); ok {
if enabled, ok := utls["enabled"].(bool); ok && enabled {
if fp, ok := utls["fingerprint"].(string); ok {
proxy["client-fingerprint"] = fp
}
}
}
if sni, ok := tls["server_name"].(string); ok {
if t == "http" {
proxy["sni"] = sni
} else {
proxy["servername"] = sni
}
}
if insecure, ok := tls["insecure"].(bool); ok && insecure {
proxy["skip-cert-verify"] = insecure
}
}
// Transport if exist
if transport, ok := obMap["transport"].(map[string]interface{}); ok {
tt, _ := transport["type"].(string)
switch tt {
case "http":
httpOpts := make(map[string]interface{})
if path, ok := transport["path"].([]interface{}); ok {
httpOpts["path"] = path[0]
} else if path, ok := transport["path"].(string); ok {
httpOpts["path"] = path
}
if host, ok := transport["host"].([]interface{}); ok {
httpOpts["host"] = host[0]
}
if isTls {
proxy["network"] = "h2"
proxy["h2-opts"] = httpOpts
} else {
proxy["network"] = "http"
proxy["http-opts"] = map[string]interface{}{"path": []interface{}{httpOpts["path"]}, "host": httpOpts["host"]}
}
case "ws", "httpupgrade":
proxy["network"] = "ws"
wsOpts := make(map[string]interface{})
if path, ok := transport["path"].(string); ok {
wsOpts["path"] = path
}
if headers, ok := transport["headers"].([]interface{}); ok {
wsOpts["headers"] = headers
}
if ed, ok := transport["early_data_header_name"].(string); ok {
wsOpts["early-data-header-name"] = ed
}
if tt == "httpupgrade" {
wsOpts["v2ray-http-upgrade"] = true
}
proxy["ws-opts"] = wsOpts
case "grpc":
proxy["network"] = "grpc"
grpcOpts := make(map[string]interface{})
if service_name, ok := transport["service_name"].(string); ok {
grpcOpts["grpc-service-name"] = service_name
}
proxy["grpc-opts"] = grpcOpts
}
}
// Multiplex
if mux, ok := obMap["multiplex"].(map[string]interface{}); ok {
if enabled, ok := mux["enabled"].(bool); ok && enabled {
smux := make(map[string]interface{})
smux["enabled"] = true
if protocol, ok := mux["protocol"].(string); ok {
smux["protocol"] = protocol
}
if _, ok := mux["max_connections"].(float64); ok {
smux["max-connections"] = mux["max_connections"]
}
if _, ok := mux["min_streams"].(float64); ok {
smux["min-streams"] = mux["min_streams"]
}
if _, ok := mux["max_streams"].(float64); ok {
smux["max-streams"] = mux["max_streams"]
}
if _, ok := mux["padding"].(bool); ok {
smux["padding"] = mux["padding"]
}
if brutal, ok := mux["brutal"].(map[string]interface{}); ok {
if enabled, ok := brutal["enabled"].(bool); ok && enabled {
brutalOpts := make(map[string]interface{})
brutalOpts["enabled"] = true
if _, ok := brutal["up_mbps"].(float64); ok {
brutalOpts["up"] = brutal["up_mbps"]
}
if _, ok := brutal["down_mbps"].(float64); ok {
brutalOpts["down"] = brutal["down_mbps"]
}
smux["brutal-opts"] = brutalOpts
}
}
proxy["smux"] = smux
}
}
proxies = append(proxies, proxy)
proxyTags = append(proxyTags, obMap["tag"].(string))
}
var proxyGroups []map[string]interface{}
err := yaml.Unmarshal([]byte(ProxyGroups), &proxyGroups)
if err != nil {
logger.Error(err.Error())
}
proxyGroups[1]["proxies"] = proxyTags
proxyGroups[0]["proxies"] = append([]string{proxyGroups[1]["name"].(string)}, proxyTags...)
output := map[string]interface{}{
"proxies": proxies,
"proxy-groups": proxyGroups,
}
return yaml.Marshal(output)
}
+34 -15
View File
@@ -46,17 +46,17 @@ type JsonService struct {
LinkService LinkService
} }
func (j *JsonService) GetJson(subId string, format string) (*string, error) { func (j *JsonService) GetJson(subId string, format string) (*string, []string, error) {
var jsonConfig map[string]interface{} var jsonConfig map[string]interface{}
client, inDatas, err := j.getData(subId) client, inDatas, err := j.getData(subId)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
outbounds, outTags, err := j.getOutbounds(client.Config, inDatas) outbounds, outTags, err := j.getOutbounds(client.Config, inDatas)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
links := j.LinkService.GetLinks(&client.Links, "external", "") links := j.LinkService.GetLinks(&client.Links, "external", "")
@@ -72,7 +72,7 @@ func (j *JsonService) GetJson(subId string, format string) (*string, error) {
err = json.Unmarshal([]byte(defaultJson), &jsonConfig) err = json.Unmarshal([]byte(defaultJson), &jsonConfig)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
jsonConfig["outbounds"] = outbounds jsonConfig["outbounds"] = outbounds
@@ -82,7 +82,11 @@ func (j *JsonService) GetJson(subId string, format string) (*string, error) {
result, _ := json.MarshalIndent(jsonConfig, "", " ") result, _ := json.MarshalIndent(jsonConfig, "", " ")
resultStr := string(result) resultStr := string(result)
return &resultStr, nil
updateInterval, _ := j.SettingService.GetSubUpdates()
headers := util.GetHeaders(client, updateInterval)
return &resultStr, headers, nil
} }
func (j *JsonService) getData(subId string) (*model.Client, []*model.Inbound, error) { func (j *JsonService) getData(subId string) (*model.Client, []*model.Inbound, error) {
@@ -98,7 +102,7 @@ func (j *JsonService) getData(subId string) (*model.Client, []*model.Inbound, er
return nil, nil, err return nil, nil, err
} }
var inbounds []*model.Inbound var inbounds []*model.Inbound
err = db.Model(model.Inbound{}).Where("id in ?", clientInbounds).Find(&inbounds).Error err = db.Model(model.Inbound{}).Preload("Tls").Where("id in ?", clientInbounds).Find(&inbounds).Error
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -126,9 +130,10 @@ func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inbounds []*mod
protocol, _ := outbound["type"].(string) protocol, _ := outbound["type"].(string)
config, _ := configs[protocol].(map[string]interface{}) config, _ := configs[protocol].(map[string]interface{})
for key, value := range config { for key, value := range config {
if key != "alterId" && key != "name" { if key == "name" || key == "alterId" || (key == "flow" && inData.TlsId == 0) {
outbound[key] = value continue
} }
outbound[key] = value
} }
var addrs []map[string]interface{} var addrs []map[string]interface{}
@@ -159,13 +164,16 @@ func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inbounds []*mod
newOut["server_port"] = int(port) newOut["server_port"] = int(port)
// Override TLS // Override TLS
outTls, _ := newOut["tls"].(map[string]interface{})
if addrTls, ok := addr["tls"].(map[string]interface{}); ok { if addrTls, ok := addr["tls"].(map[string]interface{}); ok {
outTls, _ := newOut["tls"].(map[string]interface{})
if outTls == nil {
outTls = make(map[string]interface{})
}
for key, value := range addrTls { for key, value := range addrTls {
outTls[key] = value outTls[key] = value
} }
newOut["tls"] = outTls
} }
newOut["tls"] = outTls
remark, _ := addr["remark"].(string) remark, _ := addr["remark"].(string)
newTag := fmt.Sprintf("%d.%s%s", index+1, tag, remark) newTag := fmt.Sprintf("%d.%s%s", index+1, tag, remark)
@@ -207,20 +215,27 @@ func (j *JsonService) addDefaultOutbounds(outbounds *[]map[string]interface{}, o
} }
func (j *JsonService) addOthers(jsonConfig *map[string]interface{}) error { func (j *JsonService) addOthers(jsonConfig *map[string]interface{}) error {
rules := []interface{}{ rules_start := []interface{}{
map[string]interface{}{ map[string]interface{}{
"clash_mode": "Direct", "action": "sniff",
"outbound": "direct",
}, },
map[string]interface{}{
"clash_mode": "Direct",
"action": "route",
"outbound": "direct",
},
}
rules_end := []interface{}{
map[string]interface{}{ map[string]interface{}{
"clash_mode": "Global", "clash_mode": "Global",
"action": "route",
"outbound": "proxy", "outbound": "proxy",
}, },
} }
route := map[string]interface{}{ route := map[string]interface{}{
"auto_detect_interface": true, "auto_detect_interface": true,
"final": "proxy", "final": "proxy",
"rules": rules, "rules": rules_start,
} }
othersStr, err := j.SettingService.GetSubJsonExt() othersStr, err := j.SettingService.GetSubJsonExt()
@@ -252,7 +267,11 @@ func (j *JsonService) addOthers(jsonConfig *map[string]interface{}) error {
route["rule_set"] = othersJson["rule_set"] route["rule_set"] = othersJson["rule_set"]
} }
if settingRules, ok := othersJson["rules"].([]interface{}); ok { if settingRules, ok := othersJson["rules"].([]interface{}); ok {
route["rules"] = append(rules, settingRules...) rules := append(rules_start, settingRules...)
route["rules"] = append(rules, rules_end...)
}
if defaultDomainResolver, ok := othersJson["default_domain_resolver"].(string); ok {
route["default_domain_resolver"] = defaultDomainResolver
} }
(*jsonConfig)["route"] = route (*jsonConfig)["route"] = route
+19 -12
View File
@@ -11,6 +11,7 @@ type SubHandler struct {
service.SettingService service.SettingService
SubService SubService
JsonService JsonService
ClashService
} }
func NewSubHandler(g *gin.RouterGroup) { func NewSubHandler(g *gin.RouterGroup) {
@@ -23,29 +24,35 @@ func (s *SubHandler) initRouter(g *gin.RouterGroup) {
} }
func (s *SubHandler) subs(c *gin.Context) { func (s *SubHandler) subs(c *gin.Context) {
var headers []string
var result *string
var err error
subId := c.Param("subid") subId := c.Param("subid")
format, isFormat := c.GetQuery("format") format, isFormat := c.GetQuery("format")
if isFormat { if isFormat {
result, err := s.JsonService.GetJson(subId, format) switch format {
case "json":
result, headers, err = s.JsonService.GetJson(subId, format)
case "clash":
result, headers, err = s.ClashService.GetClash(subId)
}
if err != nil || result == nil { if err != nil || result == nil {
logger.Error(err) logger.Error(err)
c.String(400, "Error!") c.String(400, "Error!")
} else { return
c.String(200, *result)
} }
} else { } else {
result, headers, err := s.SubService.GetSubs(subId) result, headers, err = s.SubService.GetSubs(subId)
if err != nil || result == nil { if err != nil || result == nil {
logger.Error(err) logger.Error(err)
c.String(400, "Error!") c.String(400, "Error!")
} else { return
// Add headers
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
c.Writer.Header().Set("Profile-Title", headers[2])
c.String(200, *result)
} }
} }
// Add headers
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
c.Writer.Header().Set("Profile-Title", headers[2])
c.String(200, *result)
} }
+2 -4
View File
@@ -6,6 +6,7 @@ import (
"s-ui/database" "s-ui/database"
"s-ui/database/model" "s-ui/database/model"
"s-ui/service" "s-ui/service"
"s-ui/util"
"strings" "strings"
"time" "time"
) )
@@ -34,11 +35,8 @@ func (s *SubService) GetSubs(subId string) (*string, []string, error) {
linksArray := s.LinkService.GetLinks(&client.Links, "all", clientInfo) linksArray := s.LinkService.GetLinks(&client.Links, "all", clientInfo)
result := strings.Join(linksArray, "\n") result := strings.Join(linksArray, "\n")
var headers []string
updateInterval, _ := s.SettingService.GetSubUpdates() updateInterval, _ := s.SettingService.GetSubUpdates()
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", client.Up, client.Down, client.Volume, client.Expiry)) headers := util.GetHeaders(client, updateInterval)
headers = append(headers, fmt.Sprintf("%d", updateInterval))
headers = append(headers, subId)
subEncode, _ := s.SettingService.GetSubEncode() subEncode, _ := s.SettingService.GetSubEncode()
if subEncode { if subEncode {
+44
View File
@@ -0,0 +1,44 @@
package common
func UnionUintArray(a []uint, b []uint) []uint {
m := make(map[uint]bool)
for _, v := range a {
m[v] = true
}
for _, v := range b {
m[v] = true
}
var res []uint
for k := range m {
res = append(res, k)
}
return res
}
// Find different elements in two slices
// Returns elements in 'a' that are not in 'b' and elements in 'b' that are not in 'a'
func DiffUintArray(a []uint, b []uint) []uint {
different := []uint{}
set := make(map[uint]bool)
for _, item := range a {
set[item] = true
}
for _, item := range b {
if !set[item] {
different = append(different, item)
}
}
set = make(map[uint]bool)
for _, item := range b {
set[item] = true
}
for _, item := range a {
if !set[item] {
different = append(different, item)
}
}
return different
}
+57 -12
View File
@@ -10,7 +10,7 @@ import (
"strings" "strings"
) )
var InboundTypeWithLink = []string{"shadowsocks", "naive", "hysteria", "hysteria2", "tuic", "vless", "trojan", "vmess"} var InboundTypeWithLink = []string{"shadowsocks", "naive", "hysteria", "hysteria2", "anytls", "tuic", "vless", "trojan", "vmess"}
func LinkGenerator(clientConfig json.RawMessage, i *model.Inbound, hostname string) []string { func LinkGenerator(clientConfig json.RawMessage, i *model.Inbound, hostname string) []string {
inbound, err := i.MarshalFull() inbound, err := i.MarshalFull()
@@ -73,6 +73,8 @@ func LinkGenerator(clientConfig json.RawMessage, i *model.Inbound, hostname stri
return tuicLink(userConfig["tuic"], *inbound, Addrs) return tuicLink(userConfig["tuic"], *inbound, Addrs)
case "vless": case "vless":
return vlessLink(userConfig["vless"], *inbound, Addrs) return vlessLink(userConfig["vless"], *inbound, Addrs)
case "anytls":
return anytlsLink(userConfig["anytls"], Addrs)
case "trojan": case "trojan":
return trojanLink(userConfig["trojan"], *inbound, Addrs) return trojanLink(userConfig["trojan"], *inbound, Addrs)
case "vmess": case "vmess":
@@ -95,7 +97,7 @@ func prepareTls(t *model.Tls) map[string]interface{} {
reality := v.(map[string]interface{}) reality := v.(map[string]interface{})
clientReality := oTls["reality"].(map[string]interface{}) clientReality := oTls["reality"].(map[string]interface{})
clientReality["enabled"] = reality["enabled"] clientReality["enabled"] = reality["enabled"]
if short_ids, hasSIds := reality["short_ids"].([]interface{}); hasSIds && len(short_ids) > 0 { if short_ids, hasSIds := reality["short_id"].([]interface{}); hasSIds && len(short_ids) > 0 {
clientReality["short_id"] = short_ids[common.RandomInt(len(short_ids))] clientReality["short_id"] = short_ids[common.RandomInt(len(short_ids))]
} }
oTls["reality"] = clientReality oTls["reality"] = clientReality
@@ -159,7 +161,7 @@ func naiveLink(
params["alpn"] = strings.Join(alpnList, ",") params["alpn"] = strings.Join(alpnList, ",")
} }
if insecure, ok := tls["insecure"].(bool); ok && insecure { if insecure, ok := tls["insecure"].(bool); ok && insecure {
params["allowInsecure"] = "1" params["insecure"] = "1"
} }
} }
if tfo, ok := inbound["tcp_fast_open"].(bool); ok && tfo { if tfo, ok := inbound["tcp_fast_open"].(bool); ok && tfo {
@@ -206,7 +208,7 @@ func hysteriaLink(
params["alpn"] = strings.Join(alpnList, ",") params["alpn"] = strings.Join(alpnList, ",")
} }
if insecure, ok := tls["insecure"].(bool); ok && insecure { if insecure, ok := tls["insecure"].(bool); ok && insecure {
params["allowInsecure"] = "1" params["insecure"] = "1"
} }
} }
if obfs, ok := inbound["obfs"].(string); ok { if obfs, ok := inbound["obfs"].(string); ok {
@@ -255,7 +257,7 @@ func hysteria2Link(
params["alpn"] = strings.Join(alpnList, ",") params["alpn"] = strings.Join(alpnList, ",")
} }
if insecure, ok := tls["insecure"].(bool); ok && insecure { if insecure, ok := tls["insecure"].(bool); ok && insecure {
params["allowInsecure"] = "1" params["insecure"] = "1"
} }
} }
if obfs, ok := inbound["obfs"].(map[string]interface{}); ok { if obfs, ok := inbound["obfs"].(map[string]interface{}); ok {
@@ -280,6 +282,40 @@ func hysteria2Link(
return links return links
} }
func anytlsLink(
userConfig map[string]interface{},
addrs []map[string]interface{}) []string {
password, _ := userConfig["password"].(string)
baseUri := fmt.Sprintf("%s%s@", "anytls://", password)
var links []string
for _, addr := range addrs {
params := map[string]string{}
if tls, ok := addr["tls"].(map[string]interface{}); ok {
if sni, ok := tls["server_name"].(string); ok {
params["sni"] = sni
}
if alpn, ok := tls["alpn"].([]interface{}); ok {
alpnList := make([]string, len(alpn))
for i, v := range alpn {
alpnList[i] = v.(string)
}
params["alpn"] = strings.Join(alpnList, ",")
}
if insecure, ok := tls["insecure"].(bool); ok && insecure {
params["insecure"] = "1"
}
}
port, _ := addr["server_port"].(float64)
uri := fmt.Sprintf("%s%s:%d", baseUri, addr["server"].(string), uint(port))
links = append(links, addParams(uri, params, addr["remark"].(string)))
}
return links
}
func tuicLink( func tuicLink(
userConfig map[string]interface{}, userConfig map[string]interface{},
inbound map[string]interface{}, inbound map[string]interface{},
@@ -304,10 +340,10 @@ func tuicLink(
params["alpn"] = strings.Join(alpnList, ",") params["alpn"] = strings.Join(alpnList, ",")
} }
if insecure, ok := tls["insecure"].(bool); ok && insecure { if insecure, ok := tls["insecure"].(bool); ok && insecure {
params["allowInsecure"] = "1" params["insecure"] = "1"
} }
if disableSni, ok := tls["disable_sni"].(bool); ok && disableSni { if disableSni, ok := tls["disable_sni"].(bool); ok && disableSni {
params["disableSni"] = "1" params["disable_sni"] = "1"
} }
} }
if congestionControl, ok := inbound["congestion_control"].(string); ok { if congestionControl, ok := inbound["congestion_control"].(string); ok {
@@ -347,9 +383,12 @@ func vlessLink(
if insecure, ok := tls["insecure"].(bool); ok && insecure { if insecure, ok := tls["insecure"].(bool); ok && insecure {
params["allowInsecure"] = "1" params["allowInsecure"] = "1"
} }
if flow, ok := userConfig["flow"].(string); ok { }
params["flow"] = flow if flow, ok := userConfig["flow"].(string); ok {
} params["flow"] = flow
}
if utls, ok := tls["utls"].(map[string]interface{}); ok {
params["fp"], _ = utls["fingerprint"].(string)
} }
if sni, ok := tls["server_name"].(string); ok { if sni, ok := tls["server_name"].(string); ok {
params["sni"] = sni params["sni"] = sni
@@ -396,6 +435,9 @@ func trojanLink(
params["allowInsecure"] = "1" params["allowInsecure"] = "1"
} }
} }
if utls, ok := tls["utls"].(map[string]interface{}); ok {
params["fp"], _ = utls["fingerprint"].(string)
}
if sni, ok := tls["server_name"].(string); ok { if sni, ok := tls["server_name"].(string); ok {
params["sni"] = sni params["sni"] = sni
} }
@@ -459,6 +501,9 @@ func vmessLink(
if sni, ok := tls["server_name"].(string); ok { if sni, ok := tls["server_name"].(string); ok {
obj["sni"] = sni obj["sni"] = sni
} }
if utls, ok := tls["utls"].(map[string]interface{}); ok {
obj["fp"], _ = utls["fingerprint"].(string)
}
} else { } else {
obj["tls"] = "none" obj["tls"] = "none"
} }
@@ -513,7 +558,7 @@ func getTransportParams(t interface{}) map[string]string {
} }
if headers, ok := trasport["headers"].(map[string]interface{}); ok { if headers, ok := trasport["headers"].(map[string]interface{}); ok {
if host, ok := headers["Host"].(string); ok { if host, ok := headers["Host"].(string); ok {
params["peer"] = host params["host"] = host
} }
} }
case "grpc": case "grpc":
@@ -522,7 +567,7 @@ func getTransportParams(t interface{}) map[string]string {
} }
case "httpupgrade": case "httpupgrade":
if host, ok := trasport["host"].(string); ok { if host, ok := trasport["host"].(string); ok {
params["peer"] = host params["host"] = host
} }
if path, ok := trasport["path"].(string); ok { if path, ok := trasport["path"].(string); ok {
params["path"] = path params["path"] = path
+38
View File
@@ -24,6 +24,8 @@ func GetOutbound(uri string, i int) (*map[string]interface{}, string, error) {
return hy(u, i) return hy(u, i)
case "hy2", "hysteria2": case "hy2", "hysteria2":
return hy2(u, i) return hy2(u, i)
case "anytls":
return anytls(u, i)
case "tuic": case "tuic":
return tuic(u, i) return tuic(u, i)
case "ss", "shadowsocks": case "ss", "shadowsocks":
@@ -293,6 +295,42 @@ func hy2(u *url.URL, i int) (*map[string]interface{}, string, error) {
return &hy2, tag, nil return &hy2, tag, nil
} }
func anytls(u *url.URL, i int) (*map[string]interface{}, string, error) {
query, _ := url.ParseQuery(u.RawQuery)
host, portStr, _ := net.SplitHostPort(u.Host)
port := 443
if len(portStr) > 0 {
port, _ = strconv.Atoi(portStr)
}
tls := map[string]interface{}{
"enabled": true,
"server_name": query.Get("sni"),
}
alpn := query.Get("alpn")
insecure := query.Get("insecure")
if len(alpn) > 0 {
tls["alpn"] = strings.Split(alpn, ",")
}
if insecure == "1" || insecure == "true" {
tls["insecure"] = true
}
tag := u.Fragment
if i > 0 {
tag = fmt.Sprintf("%d.%s", i, u.Fragment)
}
anytls := map[string]interface{}{
"type": "anytls",
"tag": tag,
"server": host,
"server_port": port,
"password": u.User.Username(),
"tls": tls,
}
return &anytls, tag, nil
}
func tuic(u *url.URL, i int) (*map[string]interface{}, string, error) { func tuic(u *url.URL, i int) (*map[string]interface{}, string, error) {
query, _ := url.ParseQuery(u.RawQuery) query, _ := url.ParseQuery(u.RawQuery)
host, portStr, _ := net.SplitHostPort(u.Host) host, portStr, _ := net.SplitHostPort(u.Host)
+28 -1
View File
@@ -8,12 +8,20 @@ import (
// Fill Inbound's out_json // Fill Inbound's out_json
func FillOutJson(i *model.Inbound, hostname string) error { func FillOutJson(i *model.Inbound, hostname string) error {
switch i.Type {
case "direct", "tun", "redirect", "tproxy":
return nil
}
var outJson map[string]interface{} var outJson map[string]interface{}
err := json.Unmarshal(i.OutJson, &outJson) err := json.Unmarshal(i.OutJson, &outJson)
if err != nil { if err != nil {
return err return err
} }
if outJson == nil {
outJson = make(map[string]interface{})
}
if i.TlsId > 0 { if i.TlsId > 0 {
addTls(&outJson, i.Tls) addTls(&outJson, i.Tls)
} else { } else {
@@ -21,6 +29,9 @@ func FillOutJson(i *model.Inbound, hostname string) error {
} }
inbound, err := i.MarshalFull() inbound, err := i.MarshalFull()
if err != nil {
return err
}
outJson["type"] = i.Type outJson["type"] = i.Type
outJson["tag"] = i.Tag outJson["tag"] = i.Tag
@@ -28,7 +39,7 @@ func FillOutJson(i *model.Inbound, hostname string) error {
outJson["server_port"] = (*inbound)["listen_port"] outJson["server_port"] = (*inbound)["listen_port"]
switch i.Type { switch i.Type {
case "http", "socks", "mixed": case "http", "socks", "mixed", "anytls":
case "shadowsocks": case "shadowsocks":
shadowsocksOut(&outJson, *inbound) shadowsocksOut(&outJson, *inbound)
return nil return nil
@@ -128,6 +139,12 @@ func shadowTlsOut(out *map[string]interface{}, inbound map[string]interface{}) {
} }
func hysteriaOut(out *map[string]interface{}, inbound map[string]interface{}) { func hysteriaOut(out *map[string]interface{}, inbound map[string]interface{}) {
delete(*out, "down_mbps")
delete(*out, "up_mbps")
delete(*out, "obfs")
delete(*out, "recv_window_conn")
delete(*out, "disable_mtu_discovery")
if upMbps, ok := inbound["down_mbps"]; ok { if upMbps, ok := inbound["down_mbps"]; ok {
(*out)["up_mbps"] = upMbps (*out)["up_mbps"] = upMbps
} }
@@ -146,6 +163,10 @@ func hysteriaOut(out *map[string]interface{}, inbound map[string]interface{}) {
} }
func hysteria2Out(out *map[string]interface{}, inbound map[string]interface{}) { func hysteria2Out(out *map[string]interface{}, inbound map[string]interface{}) {
delete(*out, "down_mbps")
delete(*out, "up_mbps")
delete(*out, "obfs")
if upMbps, ok := inbound["down_mbps"]; ok { if upMbps, ok := inbound["down_mbps"]; ok {
(*out)["up_mbps"] = upMbps (*out)["up_mbps"] = upMbps
} }
@@ -158,6 +179,8 @@ func hysteria2Out(out *map[string]interface{}, inbound map[string]interface{}) {
} }
func tuicOut(out *map[string]interface{}, inbound map[string]interface{}) { func tuicOut(out *map[string]interface{}, inbound map[string]interface{}) {
delete(*out, "zero_rtt_handshake")
delete(*out, "heartbeat")
if congestionControl, ok := inbound["congestion_control"].(string); ok { if congestionControl, ok := inbound["congestion_control"].(string); ok {
(*out)["congestion_control"] = congestionControl (*out)["congestion_control"] = congestionControl
} else { } else {
@@ -172,18 +195,22 @@ func tuicOut(out *map[string]interface{}, inbound map[string]interface{}) {
} }
func vlessOut(out *map[string]interface{}, inbound map[string]interface{}) { func vlessOut(out *map[string]interface{}, inbound map[string]interface{}) {
delete(*out, "transport")
if transport, ok := inbound["transport"]; ok { if transport, ok := inbound["transport"]; ok {
(*out)["transport"] = transport (*out)["transport"] = transport
} }
} }
func trojanOut(out *map[string]interface{}, inbound map[string]interface{}) { func trojanOut(out *map[string]interface{}, inbound map[string]interface{}) {
delete(*out, "transport")
if transport, ok := inbound["transport"]; ok { if transport, ok := inbound["transport"]; ok {
(*out)["transport"] = transport (*out)["transport"] = transport
} }
} }
func vmessOut(out *map[string]interface{}, inbound map[string]interface{}) { func vmessOut(out *map[string]interface{}, inbound map[string]interface{}) {
(*out)["alter_id"] = 0
delete(*out, "transport")
if transport, ok := inbound["transport"]; ok { if transport, ok := inbound["transport"]; ok {
(*out)["transport"] = transport (*out)["transport"] = transport
} }
+14
View File
@@ -0,0 +1,14 @@
package util
import (
"fmt"
"s-ui/database/model"
)
func GetHeaders(client *model.Client, updateInterval int) []string {
var headers []string
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", client.Up, client.Down, client.Volume, client.Expiry))
headers = append(headers, fmt.Sprintf("%d", updateInterval))
headers = append(headers, client.Name)
return headers
}
+1 -1
View File
@@ -24,7 +24,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
//go:embed html/* //go:embed *
var content embed.FS var content embed.FS
type Server struct { type Server struct {