diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..cbdc42f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,42 @@ +name: build + +on: + push: + branches: + - "main" + paths-ignore: + - "*.js" + - "*.md" + pull_request: + branches: + - "main" + paths-ignore: + - "*.js" + - "*.md" + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: "~> v2" + args: release --clean --snapshot + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/upload-artifact@v4 + with: + name: ci_build + path: | + dist/*.tar.gz + dist/*.zip + dist/*.txt diff --git a/.github/workflows/build_docker.yml b/.github/workflows/build_docker.yml deleted file mode 100644 index 9447838..0000000 --- a/.github/workflows/build_docker.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: build_docker - -on: - push: - branches: [ main ] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - build_docker: - name: Build docker - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: xhofe/alist-proxy - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: xhofe - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 95977ba..6e1b27f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,28 +1,36 @@ -name: release +name: goreleaser + on: push: tags: - - '*' + - "v*" + +permissions: + contents: write + packages: write jobs: - goreleaser: + release: + name: Release runs-on: ubuntu-latest steps: - - - name: Checkout - uses: actions/checkout@v4 + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v4 with: fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 + - uses: actions/setup-go@v5 with: - go-version: 1.17 - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 + go-version: stable + - uses: goreleaser/goreleaser-action@v6 with: - version: latest - args: release --rm-dist + distribution: goreleaser + version: "~> v2" + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release_docker.yml b/.github/workflows/release_docker.yml deleted file mode 100644 index 4c2bb64..0000000 --- a/.github/workflows/release_docker.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: release_docker - -on: - push: - tags: - - '*' - -jobs: - release_docker: - name: Release Docker - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: xhofe/alist-proxy - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: xhofe - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x diff --git a/.gitignore b/.gitignore index 89dac75..b5386fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,36 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib +openlist-proxy -# Test binary, build with `go test -c` +# Test binary, built with `go test -c` *.test -# Output of the go coverage tool, specifically when used with LiteIDE +# Code coverage profiles and other test artifacts *.out -.idea -alist-proxy \ No newline at end of file +coverage.* +*.coverprofile +profile.cov + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env + +# Editor/IDE +.idea/ +.vscode/ + +# Added by goreleaser init: +dist/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..f8679c8 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,87 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com + +# The lines below are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/need to use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 2 + +project_name: openlist-proxy + +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + - freebsd + goarch: + - "386" + - amd64 + - arm + - arm64 + - loong64 + - mips + - mips64 + - mips64le + - mipsle + - ppc64 + - ppc64le + - riscv64 + - s390x + goarm: + - "6" + - "7" + ldflags: + - -s -w + - -X main.version={{ .Version }} + flags: + - -trimpath + +archives: + - formats: [tar.gz] + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + formats: [zip] + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + +kos: + - base_image: alpine + repositories: + - "ghcr.io/openlistteam/openlist-proxy" + tags: + - "{{.Version}}" + - "{{ if .IsSnapshot }}{{ .ShortCommit }}{{ else }}latest{{ end }}" + sbom: none + bare: true + preserve_import_paths: false + platforms: + - linux/amd64 + - linux/386 + - linux/arm64 + - linux/arm/v7 + - linux/arm/v6 + - linux/s390x diff --git a/Dockerfile b/Dockerfile index f593488..3c09973 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,14 @@ -FROM alpine:edge as builder +FROM golang:alpine as builder LABEL stage=go-builder WORKDIR /app/ -COPY ./ ./ -RUN apk add --no-cache bash curl gcc git go musl-dev; \ - go build -o /app/bin/alist-proxy -ldflags="-w -s" . +COPY go.mod go.sum ./ +RUN go mod download +COPY *.go ./ +RUN go build -v -o /app/bin/openlist-proxy -ldflags="-w -s" . -FROM alpine:edge -LABEL MAINTAINER="i@nn.ci" +FROM alpine:3 +LABEL MAINTAINER="OpenList" WORKDIR /app/ -COPY --from=builder /app/bin/alist-proxy ./ +COPY --from=builder /app/bin/openlist-proxy ./ EXPOSE 5243 -CMD [ "./alist-proxy" ] +CMD [ "./openlist-proxy" ] diff --git a/README.md b/README.md index cb4cccc..0f4581a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,28 @@ -# alist-proxy +# OpenList-Proxy -- [x] cloudflare workers -- [x] golang +Use another machine to proxy OpenList's traffic. + +- [x] Cloudflare Workers +- [x] Golang + +## Usage + +```shell +Usage of OpenList-Proxy: + -address string + openlist address + -cert string + cert file (default "server.crt") + -help + show help + -https + use https protocol. + -key string + key file (default "server.key") + -port int + the proxy port. (default 5243) + -token string + openlist token + -version + show version and exit +``` diff --git a/go.mod b/go.mod index 920c149..8eaaaac 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ -module github.com/Xhofe/alist-proxy +module github.com/OpenListTeam/OpenList-Proxy -go 1.19 +go 1.24.4 -require github.com/alist-org/alist/v3 v3.0.0-beta.0 // indirect +require github.com/OpenListTeam/OpenList/v4 v4.0.7 diff --git a/go.sum b/go.sum index 6dfa05e..2695da5 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -github.com/alist-org/alist/v3 v3.0.0-beta.0 h1:tTosXvDttekbbkORzVMCzpw6xa6TaclzLEZ0mzzu8qw= -github.com/alist-org/alist/v3 v3.0.0-beta.0/go.mod h1:1TfFPURtBFLrW6mOV28P/4gdbSiAT1f3dp2bGR4o/cM= +github.com/OpenListTeam/OpenList/v4 v4.0.7 h1:/QtuO8VmwlBxNXraPa+hfqPN3F589pSN9hugQHywgNk= +github.com/OpenListTeam/OpenList/v4 v4.0.7/go.mod h1:9tzs5NAkgYbYIb0TmuBfas/yLcukJDq3CjTZo2BGy2M= diff --git a/alist-proxy.go b/openlist-proxy.go similarity index 83% rename from alist-proxy.go rename to openlist-proxy.go index d55be8e..675800b 100644 --- a/alist-proxy.go +++ b/openlist-proxy.go @@ -6,10 +6,11 @@ import ( "flag" "fmt" "io" + "maps" "net/http" "strings" - "github.com/alist-org/alist/v3/pkg/sign" + "github.com/OpenListTeam/OpenList/v4/pkg/sign" ) type Link struct { @@ -27,20 +28,24 @@ var ( port int https bool help bool + showVersion bool certFile, keyFile string address, token string s sign.Sign + version string = "dev" ) func init() { flag.IntVar(&port, "port", 5243, "the proxy port.") flag.BoolVar(&https, "https", false, "use https protocol.") flag.BoolVar(&help, "help", false, "show help") + flag.BoolVar(&showVersion, "version", false, "show version and exit") flag.StringVar(&certFile, "cert", "server.crt", "cert file") flag.StringVar(&keyFile, "key", "server.key", "key file") - flag.StringVar(&address, "address", "", "alist address") - flag.StringVar(&token, "token", "", "alist token") + flag.StringVar(&address, "address", "", "openlist address") + flag.StringVar(&token, "token", "", "openlist token") flag.Parse() + s = sign.NewHMACSign([]byte(token)) } @@ -107,12 +112,8 @@ func downHandle(w http.ResponseWriter, r *http.Request) { return } req2, _ := http.NewRequest(r.Method, resp.Data.Url, nil) - for h, val := range r.Header { - req2.Header[h] = val - } - for h, val := range resp.Data.Header { - req2.Header[h] = val - } + maps.Copy(req2.Header, r.Header) + maps.Copy(req2.Header, resp.Data.Header) res2, err := HttpClient.Do(req2) if err != nil { errorResponse(w, 500, err.Error()) @@ -123,9 +124,7 @@ func downHandle(w http.ResponseWriter, r *http.Request) { }() res2.Header.Del("Access-Control-Allow-Origin") res2.Header.Del("set-cookie") - for h, v := range res2.Header { - w.Header()[h] = v - } + maps.Copy(w.Header(), res2.Header) w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") w.Header().Add("Access-Control-Allow-Headers", "range") @@ -142,18 +141,27 @@ func main() { flag.Usage() return } + + if showVersion { + fmt.Println("Version:", version) + return + } + + fmt.Printf("OpenList-Proxy - %s\n", version) addr := fmt.Sprintf(":%d", port) fmt.Printf("listen and serve: %s\n", addr) - s := http.Server{ + + srv := http.Server{ Addr: addr, Handler: http.HandlerFunc(downHandle), } + if !https { - if err := s.ListenAndServe(); err != nil { + if err := srv.ListenAndServe(); err != nil { fmt.Printf("failed to start: %s\n", err.Error()) } } else { - if err := s.ListenAndServeTLS(certFile, keyFile); err != nil { + if err := srv.ListenAndServeTLS(certFile, keyFile); err != nil { fmt.Printf("failed to start: %s\n", err.Error()) } } diff --git a/alist-proxy.js b/openlist-proxy.js similarity index 59% rename from alist-proxy.js rename to openlist-proxy.js index 38aadbf..320e7f7 100644 --- a/alist-proxy.js +++ b/openlist-proxy.js @@ -1,9 +1,15 @@ -// src/const.ts -var ADDRESS = "YOUR_ADDRESS"; -var TOKEN = "YOUR_TOKEN"; -var WORKER_ADDRESS = "YOUR_WORKER_ADDRESS"; +// src/const.js +const ADDRESS = "YOUR_ADDRESS"; +const TOKEN = "YOUR_TOKEN"; +const WORKER_ADDRESS = "YOUR_WORKER_ADDRESS"; -// src/verify.ts +// src/verify.js +/** + * Verifies a signed string with expiration check. + * @param {string} data - Original data. + * @param {string} _sign - Signed string. + * @returns {Promise} Error message if invalid, empty string if valid. + */ var verify = async (data, _sign) => { const signSlice = _sign.split(":"); if (!signSlice[signSlice.length - 1]) { @@ -22,6 +28,13 @@ var verify = async (data, _sign) => { } return ""; }; + +/** + * Generates an HMAC-SHA256 signature with expiration. + * @param {string} data - The data to sign. + * @param {number} expire - Expiry timestamp (in seconds). + * @returns {Promise} The signed string. + */ var hmacSha256Sign = async (data, expire) => { const key = await crypto.subtle.importKey( "raw", @@ -33,15 +46,26 @@ var hmacSha256Sign = async (data, expire) => { const buf = await crypto.subtle.sign( { name: "HMAC", - hash: "SHA-256" + hash: "SHA-256", }, key, new TextEncoder().encode(`${data}:${expire}`) ); - return btoa(String.fromCharCode(...new Uint8Array(buf))).replace(/\+/g, "-").replace(/\//g, "_") + ":" + expire; + return ( + btoa(String.fromCharCode(...new Uint8Array(buf))) + .replace(/\+/g, "-") + .replace(/\//g, "_") + + ":" + + expire + ); }; -// src/handleDownload.ts +// src/handleDownload.js +/** + * Handles download requests with signature verification and CORS. + * @param {Request} request - The incoming fetch request. + * @returns {Promise} A proper file or error response. + */ async function handleDownload(request) { const origin = request.headers.get("origin") ?? "*"; const url = new URL(request.url); @@ -52,12 +76,12 @@ async function handleDownload(request) { const resp2 = new Response( JSON.stringify({ code: 401, - message: verifyResult + message: verifyResult, }), { headers: { - "content-type": "application/json;charset=UTF-8" - } + "content-type": "application/json;charset=UTF-8", + }, } ); resp2.headers.set("Access-Control-Allow-Origin", origin); @@ -67,11 +91,11 @@ async function handleDownload(request) { method: "POST", headers: { "content-type": "application/json;charset=UTF-8", - Authorization: TOKEN + Authorization: TOKEN, }, body: JSON.stringify({ - path - }) + path, + }), }); let res = await resp.json(); if (res.code !== 200) { @@ -107,32 +131,46 @@ async function handleDownload(request) { return response; } -// src/handleOptions.ts +// src/handleOptions.js +/** + * Handles preflight CORS (OPTIONS) requests. + * @param {Request} request - The incoming OPTIONS request. + * @returns {Response} Response with CORS headers. + */ function handleOptions(request) { const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS", - "Access-Control-Max-Age": "86400" + "Access-Control-Max-Age": "86400", }; let headers = request.headers; - if (headers.get("Origin") !== null && headers.get("Access-Control-Request-Method") !== null) { + if ( + headers.get("Origin") !== null && + headers.get("Access-Control-Request-Method") !== null + ) { let respHeaders = { ...corsHeaders, - "Access-Control-Allow-Headers": request.headers.get("Access-Control-Request-Headers") || "" + "Access-Control-Allow-Headers": + request.headers.get("Access-Control-Request-Headers") || "", }; return new Response(null, { - headers: respHeaders + headers: respHeaders, }); } else { return new Response(null, { headers: { - Allow: "GET, HEAD, POST, OPTIONS" - } + Allow: "GET, HEAD, POST, OPTIONS", + }, }); } } -// src/handleRequest.ts +// src/handleRequest.js +/** + * Main request handler that routes based on HTTP method. + * @param {Request} request - The incoming HTTP request. + * @returns {Promise} A valid response. + */ async function handleRequest(request) { if (request.method === "OPTIONS") { return handleOptions(request); @@ -140,13 +178,17 @@ async function handleRequest(request) { return await handleDownload(request); } -// src/index.ts +// src/index.js +/** + * Cloudflare Worker entry point. + * @param {Request} request - The incoming request. + * @param {any} env - Environment bindings. + * @param {ExecutionContext} ctx - Execution context. + * @returns {Promise} Response from the handler. + */ var src_default = { async fetch(request, env, ctx) { return await handleRequest(request); - } -}; -export { - src_default as default + }, }; -//# sourceMappingURL=index.js.map +export { src_default as default };