9 Commits

Author SHA1 Message Date
Kain344 8264f356a7 feat: update build workflow, enhance README, and modify license for version 1.4-01
Build and Release / release (push) Successful in 1m36s
2026-06-22 11:23:22 +07:00
Kain344 7c3d96f1db fix: correct proxy response status and formatting, and add release configuration file
Build and Release / release (push) Successful in 1m35s
2026-06-12 12:36:43 +07:00
Kain344 2e6f54b7e7 feat: implement EmptyUrls response handling and add release version configuration
Build and Release / release (push) Successful in 1m35s
2026-06-12 12:17:41 +07:00
Kain344 48f801e05b feat: add AlwaysIgnoreUrls for URL filtering; update README and release version to 1.3-01
Build and Release / release (push) Successful in 1m43s
2026-06-12 10:31:40 +07:00
Kain344 5aac0d6297 feat: add automated release publishing script and Gitea workflow integration
Build and Release / release (push) Successful in 1m50s
2026-06-11 20:38:50 +07:00
Kain344 d59288e73f feat: add build and publish automation script for component deployment
Build and Release / release (push) Successful in 1m53s
2026-06-10 12:50:18 +07:00
Kain344 9cdd50f230 feat: add release manifest and enforce certificate file verification in setupCertificate
Build and Release / release (push) Successful in 42s
2026-05-26 18:53:28 +07:00
Kain344 7bcd8b43d9 UPDATE: Add '-no-sys' flag to skip certificate installation and system proxy setup; update README with usage examples; increment release version to 1.2-02
Build and Release / release (push) Successful in 43s
2026-05-26 14:27:36 +07:00
Kain344 77d5a09021 UPDATE: Consolidate release upload steps for Windows and macOS in build workflow
Build and Release / release (push) Successful in 41s
2026-05-24 10:25:05 +07:00
22 changed files with 1182 additions and 440 deletions
+48 -64
View File
@@ -1,64 +1,48 @@
name: Build and Release
run-name: ${{ gitea.actor }} build 🚀
on:
push:
branches:
- master
jobs:
release:
runs-on: ubuntu-latest
container:
image: azenkain/go-node:latest
steps:
- uses: actions/checkout@v4
- name: Download Go dependencies
run: go mod download
- name: Build for Windows
run: GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" .
- name: Build for macOS Intel
run: |
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 \
go build -trimpath -ldflags="-s -w" \
-o firefly-go-proxy-macos-amd64 .
- name: Build for macOS Apple Silicon
run: |
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 \
go build -trimpath -ldflags="-s -w" \
-o firefly-go-proxy-macos-arm64 .
- name: Grant execute permissions
run: |
chmod +x ./script/release-uploader
- name: Upload Windows release
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
run: |
script/release-uploader \
-token="$REPO_TOKEN" \
-release-url="https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Proxy/releases" \
-files="firefly-go-proxy.exe"
- name: Upload macOS Intel release
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
run: |
script/release-uploader \
-token="$REPO_TOKEN" \
-release-url="https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Proxy/releases" \
-files="firefly-go-proxy-macos-amd64"
- name: Upload macOS ARM release
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
run: |
script/release-uploader \
-token="$REPO_TOKEN" \
-release-url="https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Proxy/releases" \
-files="firefly-go-proxy-macos-arm64"
name: Build and Release
run-name: ${{ gitea.actor }} build 🚀
on:
push:
branches:
- master
jobs:
release:
runs-on: ubuntu-latest
container:
image: azenkain/go-node:latest
steps:
- uses: actions/checkout@v4
- name: Download Go dependencies
run: go mod download
- name: Build for Windows x64
run: GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o firefly-go-proxy-windows-amd64.exe .
- name: Build for Windows ARM64
run: GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o firefly-go-proxy-windows-arm64.exe .
- name: Build for macOS x64
run: GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o firefly-go-proxy-macos-amd64 .
- name: Build for macOS ARM64
run: GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o firefly-go-proxy-macos-arm64 .
- name: Build for Linux x64
run: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o firefly-go-proxy-linux-amd64 .
- name: Build for Linux ARM64
run: GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o firefly-go-proxy-linux-arm64 .
- name: Grant execute permissions
run: |
chmod +x ./script/publish/publish-script
- name: Publish components to API
run: ./script/publish/publish-script
env:
ENV_ROBOT_TOKEN: ${{ secrets.ENV_ROBOT_TOKEN }}
ENV_GAME_IDS: ${{ secrets.ENV_GAME_IDS }}
ENV_COMPONENT_TYPE: PROXY
ENV_FILES: "firefly-go-proxy-windows-amd64.exe,firefly-go-proxy-windows-arm64.exe,firefly-go-proxy-macos-amd64,firefly-go-proxy-macos-arm64,firefly-go-proxy-linux-amd64,firefly-go-proxy-linux-arm64"
+18 -18
View File
@@ -1,18 +1,18 @@
MIT License
Copyright (c) 2025 Firefly-Shelter
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
MIT License
Copyright (c) 2025 Firefly-Shelter
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
+5 -5
View File
@@ -25,9 +25,9 @@ build_mac_arm64:
build_ico:
@echo Building application icon...
magick logo.jpg -define icon:auto-resize=256,128,64,48,32,16 ./logo.ico
@echo Done!
set_logo:
@echo Embedding application icon...
go-winres simply --icon ./logo.ico
@echo Done!
set_logo:
@echo Embedding application icon...
go-winres simply --icon ./logo.ico
@echo Done!
+74 -67
View File
@@ -1,67 +1,68 @@
# Firefly Go Proxy
A lightweight HTTP/HTTPS proxy server with domain redirection and request blocking capabilities. This tool is designed to help with local development and testing by intercepting and modifying HTTP/HTTPS traffic.
## Features
- HTTP/HTTPS proxy with MITM support
- Domain-based request redirection
- URL pattern blocking
- Automatic certificate management
- Cross-platform support (Windows, macOS, Linux)
# Firefly Go Proxy
A lightweight HTTP/HTTPS proxy server with domain redirection and request blocking capabilities. This tool is designed to help with local development and testing by intercepting and modifying HTTP/HTTPS traffic.
## Features
- HTTP/HTTPS proxy with MITM support
- Domain-based request redirection
- URL pattern blocking
- Automatic certificate management
- Cross-platform support (Windows, macOS, Linux)
- System proxy configuration
- Automatic admin prompt on macOS/Linux for certificate/proxy setup
## Installation
### Prerequisites
- Go 1.22 or higher
- Git
### Building from source
```bash
cd firefly-go-proxy
go build
```
## Usage
### Basic usage
```bash
./firefly-proxy [flags] //linux|macos
./firefly-proxy.exe [flags] //windows
```
### Available Flags
### Prerequisites
- Go 1.22 or higher
- Git
### Building from source
```bash
cd firefly-go-proxy
go build
```
## Usage
### Basic usage
```bash
./firefly-proxy [flags] //linux|macos
./firefly-proxy.exe [flags] //windows
```
### Available Flags
- `-r`: Redirect target host (default: "127.0.0.1:21000")
- `-b`: Comma-separated list of blocked ports
- `-p`: Proxy listen port (default: auto)
- `-e`: Path to an executable to run with admin privileges
- `-no-sys`: Run only the proxy server; skip certificate installation, system proxy setup, and macOS/Linux admin relaunch
### Examples
1. Start proxy with default settings:
```bash
./firefly-proxy //linux|macos
./firefly-proxy.exe //windows
```
2. Redirect traffic to a different host:
```bash
./firefly-proxy -r 192.168.1.100:8080 //linux|macos
./firefly-proxy.exe -r 192.168.1.100:8080 //windows
```
3. Block specific ports:
```bash
./firefly-proxy -b "80,443,8080" //linux|macos
./firefly-proxy.exe -b "80,443,8080" //windows
```
1. Start proxy with default settings:
```bash
./firefly-proxy //linux|macos
./firefly-proxy.exe //windows
```
2. Redirect traffic to a different host:
```bash
./firefly-proxy -r 192.168.1.100:8080 //linux|macos
./firefly-proxy.exe -r 192.168.1.100:8080 //windows
```
3. Block specific ports:
```bash
./firefly-proxy -b "80,443,8080" //linux|macos
./firefly-proxy.exe -b "80,443,8080" //windows
```
4. Run an executable with admin privileges:
```bash
./firefly-proxy -e "/path/to/your/executable" //linux|macos
@@ -76,16 +77,22 @@ go build
On macOS/Linux, if the proxy is not already running as root, it relaunches with an administrator prompt. On Linux, logs from the elevated process are written to `/tmp/firefly-go-proxy.log`; on macOS, elevated process output is discarded.
## How it works
The proxy intercepts HTTP/HTTPS traffic and can:
- Redirect requests based on domain names
- Block specific URLs or patterns
- Handle SSL/TLS connections with custom CA certificates
- Automatically configure system proxy settings
## License
MIT License
6. Start only the proxy server without changing system settings:
```bash
./firefly-proxy -no-sys -p 8888 //linux|macos
./firefly-proxy.exe -no-sys -p 8888 //windows
```
## How it works
The proxy intercepts HTTP/HTTPS traffic and can:
- Redirect requests based on domain names
- Block specific URLs or patterns
- Handle SSL/TLS connections with custom CA certificates
- Automatically configure system proxy settings
## License
MIT License
+36 -36
View File
@@ -1,37 +1,37 @@
package main
import (
"crypto/tls"
"sync"
)
type CertStorage struct {
certs map[string]*tls.Certificate
mtx sync.RWMutex
}
func (cs *CertStorage) Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error) {
cs.mtx.RLock()
cert, ok := cs.certs[hostname]
cs.mtx.RUnlock()
if ok {
return cert, nil
}
cert, err := gen()
if err != nil {
return nil, err
}
cs.mtx.Lock()
cs.certs[hostname] = cert
cs.mtx.Unlock()
return cert, nil
}
func NewCertStorage() *CertStorage {
return &CertStorage{
certs: make(map[string]*tls.Certificate),
}
package main
import (
"crypto/tls"
"sync"
)
type CertStorage struct {
certs map[string]*tls.Certificate
mtx sync.RWMutex
}
func (cs *CertStorage) Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error) {
cs.mtx.RLock()
cert, ok := cs.certs[hostname]
cs.mtx.RUnlock()
if ok {
return cert, nil
}
cert, err := gen()
if err != nil {
return nil, err
}
cs.mtx.Lock()
cs.certs[hostname] = cert
cs.mtx.Unlock()
return cert, nil
}
func NewCertStorage() *CertStorage {
return &CertStorage{
certs: make(map[string]*tls.Certificate),
}
}
+5 -1
View File
@@ -10,13 +10,17 @@ import (
const caCertName = "firefly-go-proxy-ca.crt"
func setupCertificate() (*tls.Certificate, error) {
func setupCertificate(installSystemCA bool) (*tls.Certificate, error) {
if _, err := os.Stat(caCertName); os.IsNotExist(err) {
if err := os.WriteFile(caCertName, goproxy.GoproxyCa.Certificate[0], 0644); err != nil {
return nil, err
}
}
if !installSystemCA {
return &goproxy.GoproxyCa, nil
}
absPath, err := filepath.Abs(caCertName)
if err != nil {
return nil, err
+62 -62
View File
@@ -1,62 +1,62 @@
//go:build linux
// +build linux
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
func installCA(absPath string) error {
// Detect distro
data, err := os.ReadFile("/etc/os-release")
if err != nil {
return fmt.Errorf("cannot detect distro: %v", err)
}
content := string(data)
// Debian/Ubuntu/Kali
if strings.Contains(content, "ID=debian") ||
strings.Contains(content, "ID=ubuntu") ||
strings.Contains(content, "ID=kali") {
destDir := "/usr/local/share/ca-certificates"
if err := os.MkdirAll(destDir, 0755); err != nil {
return fmt.Errorf("failed to create cert dir: %v", err)
}
filename := filepath.Base(absPath)
destPath := filepath.Join(destDir, filename)
inputData, err := os.ReadFile(absPath)
if err != nil {
return fmt.Errorf("failed to read source file: %v", err)
}
if err := os.WriteFile(destPath, inputData, 0644); err != nil {
return fmt.Errorf("failed to write cert file to system: %v", err)
}
fmt.Printf("Updating certificates for Debian/Ubuntu...\n")
cmd := exec.Command("update-ca-certificates")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// Arch / Manjaro
if strings.Contains(content, "ID=arch") ||
strings.Contains(content, "ID=manjaro") {
cmd := exec.Command("trust", "anchor", "--store", absPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
return fmt.Errorf("unsupported Linux distribution")
}
//go:build linux
// +build linux
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
func installCA(absPath string) error {
// Detect distro
data, err := os.ReadFile("/etc/os-release")
if err != nil {
return fmt.Errorf("cannot detect distro: %v", err)
}
content := string(data)
// Debian/Ubuntu/Kali
if strings.Contains(content, "ID=debian") ||
strings.Contains(content, "ID=ubuntu") ||
strings.Contains(content, "ID=kali") {
destDir := "/usr/local/share/ca-certificates"
if err := os.MkdirAll(destDir, 0755); err != nil {
return fmt.Errorf("failed to create cert dir: %v", err)
}
filename := filepath.Base(absPath)
destPath := filepath.Join(destDir, filename)
inputData, err := os.ReadFile(absPath)
if err != nil {
return fmt.Errorf("failed to read source file: %v", err)
}
if err := os.WriteFile(destPath, inputData, 0644); err != nil {
return fmt.Errorf("failed to write cert file to system: %v", err)
}
fmt.Printf("Updating certificates for Debian/Ubuntu...\n")
cmd := exec.Command("update-ca-certificates")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// Arch / Manjaro
if strings.Contains(content, "ID=arch") ||
strings.Contains(content, "ID=manjaro") {
cmd := exec.Command("trust", "anchor", "--store", absPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
return fmt.Errorf("unsupported Linux distribution")
}
+7 -7
View File
@@ -1,7 +1,7 @@
//go:build !windows && !darwin && !linux
package main
func installCA(certPath string) error {
return nil
}
//go:build !windows && !darwin && !linux
package main
func installCA(certPath string) error {
return nil
}
+17 -17
View File
@@ -1,17 +1,17 @@
//go:build windows
// +build windows
package main
import (
"os/exec"
)
func installCA(absPath string) error {
cmd := exec.Command("certutil", "-addstore", "-user", "root", absPath)
if err := cmd.Run(); err != nil {
return err
}
return nil
}
//go:build windows
// +build windows
package main
import (
"os/exec"
)
func installCA(absPath string) error {
cmd := exec.Command("certutil", "-addstore", "-user", "root", absPath)
if err := cmd.Run(); err != nil {
return err
}
return nil
}
+6
View File
@@ -23,6 +23,12 @@ var AlwaysIgnoreDomains = []string{
"autopatchos.starrails.com",
}
var AlwaysIgnoreUrls = []string{}
var EmptyUrls = []string{
"/query_security_file",
}
var BlockUrls = []string{
"/data_abtest_api/config/experiment/list",
"/common/hkrpg_global/announcement/api/getAlertPic",
+17 -17
View File
@@ -1,17 +1,17 @@
package main
import (
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func init() {
output := zerolog.ConsoleWriter{
Out: os.Stdout,
PartsOrder: []string{"level", "message"},
}
log.Logger = zerolog.New(output).With().Logger()
}
package main
import (
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func init() {
output := zerolog.ConsoleWriter{
Out: os.Stdout,
PartsOrder: []string{"level", "message"},
}
log.Logger = zerolog.New(output).With().Logger()
}
+64 -28
View File
@@ -2,11 +2,13 @@ package main
import (
"context"
"crypto/tls"
"flag"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"os/signal"
"strings"
@@ -32,22 +34,36 @@ func rawQueryFromRequestURI(requestURI string) string {
return rawQuery
}
func parseRedirect(r string) (scheme, host string) {
if strings.Contains(r, "://") {
if u, err := url.Parse(r); err == nil && u.Host != "" {
return u.Scheme, u.Host
}
}
return "http", r
}
func main() {
redirectHost := flag.String("r", "127.0.0.1:21000", "redirect target host")
redirectHost := flag.String("r", "127.0.0.1:21000", "redirect target (host:port or full URL)")
blockedStr := flag.String("b", "", "comma separated list of blocked ports")
proxyPort := flag.Int("p", 0, "proxy listen port (default: auto)")
exePath := flag.String("e", "", "path to the executable")
parentPID := flag.Int("parent-pid", 0, "parent process id to watch")
noSys := flag.Bool("no-sys", false, "skip certificate installation and system proxy setup")
flag.Parse()
relaunched, err := relaunchWithAdminIfNeeded()
if err != nil {
zlog.Error().Err(err).Msg("Failed to relaunch with admin privileges")
return
}
if relaunched {
zlog.Info().Msg("Relaunched with admin privileges")
return
redirectScheme, redirectTarget := parseRedirect(*redirectHost)
if !*noSys {
relaunched, err := relaunchWithAdminIfNeeded()
if err != nil {
zlog.Error().Err(err).Msg("Failed to relaunch with admin privileges")
return
}
if relaunched {
zlog.Info().Msg("Relaunched with admin privileges")
return
}
}
blockedPorts := parseBlockedPorts(*blockedStr)
@@ -66,7 +82,7 @@ func main() {
return
}
cert, err := setupCertificate()
cert, err := setupCertificate(!*noSys)
if err != nil {
zlog.Error().Err(err).Msg("Failed setup certificate")
return
@@ -74,29 +90,30 @@ func main() {
addr := ":" + port
proxyAddr := "127.0.0.1"
proxyEndpoint := proxyAddr + ":" + port
proxyEnabled := false
stopProxyRefresh := func() {}
defer func() {
stopProxyRefresh()
if r := recover(); r != nil {
zlog.Error().
Interface("panic", r).
Msg("Unexpected panic")
}
if proxyEnabled {
}()
if !*noSys {
if err := setProxy(true, proxyAddr, port); err != nil {
zlog.Error().Err(err).Msg("Failed to set system proxy")
return
}
stopProxyRefresh := startProxyRefreshLoop(proxyAddr, port)
defer func() {
stopProxyRefresh()
if err := setProxy(false, "", ""); err != nil {
zlog.Error().Err(err).Msg("Failed to reset system proxy")
}
}
}()
if err := setProxy(true, proxyAddr, port); err != nil {
zlog.Error().Err(err).Msg("Failed to set system proxy")
return
}()
} else {
zlog.Info().Msg("System certificate and proxy setup skipped")
}
proxyEnabled = true
stopProxyRefresh = startProxyRefreshLoop(proxyAddr, port)
customCaMitm := &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(cert)}
var customAlwaysMitm goproxy.FuncHttpsHandler = func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
@@ -115,6 +132,7 @@ func main() {
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
DisableCompression: false,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
proxy.CertStore = NewCertStorage()
proxy.OnRequest().HandleConnect(customAlwaysMitm)
@@ -132,6 +150,22 @@ func main() {
return req, nil
}
if matchURL(path, AlwaysIgnoreUrls) {
zlog.Warn().Str("url", req.URL.String()).Msg("PASS URL")
return req, nil
}
if matchURL(path, EmptyUrls) {
full := req.URL.String()
zlog.Warn().Str("url", full).Msg("Empty URL Response")
return req, goproxy.NewResponse(
req,
goproxy.ContentTypeText,
http.StatusNotFound,
"",
)
}
if matchDomain(host, RedirectDomains) {
if matchURL(path, BlockUrls) {
full := req.URL.String()
@@ -140,7 +174,7 @@ func main() {
req,
goproxy.ContentTypeText,
http.StatusNotFound,
`{\n"message": "blocked by proxy",\n,"success": false,\n"retcode": -1\n}`,
`{"message": "blocked by proxy", "success": false, "retcode": -1}`,
)
}
full := req.URL.String()
@@ -151,8 +185,9 @@ func main() {
Str("raw_query", rawQuery).
Msg("Force redirect")
req.URL.Scheme = "http"
req.URL.Host = *redirectHost
req.URL.Scheme = redirectScheme
req.URL.Host = redirectTarget
req.Host = redirectTarget
req.URL.RawQuery = rawQuery
req.RequestURI = ""
zlog.Info().Str("to_url", req.URL.String()).Msg("Force redirected")
@@ -164,8 +199,9 @@ func main() {
Str("from_url", full).
Str("raw_query", rawQuery).
Msg("Redirect domain")
req.URL.Scheme = "http"
req.URL.Host = *redirectHost
req.URL.Scheme = redirectScheme
req.URL.Host = redirectTarget
req.Host = redirectTarget
req.URL.RawQuery = rawQuery
req.RequestURI = ""
zlog.Info().Str("to_url", req.URL.String()).Msg("Redirected domain")
@@ -226,4 +262,4 @@ func main() {
if err := srv.Shutdown(ctx); err != nil {
zlog.Error().Err(err).Msg("Server shutdown error")
}
}
}
+7 -7
View File
@@ -1,7 +1,7 @@
//go:build !windows && !darwin && !linux
package main
func runWithAdmin(exePath string, env []string) error {
return nil
}
//go:build !windows && !darwin && !linux
package main
func runWithAdmin(exePath string, env []string) error {
return nil
}
+15 -15
View File
@@ -1,15 +1,15 @@
//go:build linux
// +build linux
package main
import (
"os"
"os/exec"
)
func runWithAdmin(exePath string, env []string) error {
cmd := exec.Command("pkexec", exePath)
cmd.Env = append(os.Environ(), env...)
return cmd.Start()
}
//go:build linux
// +build linux
package main
import (
"os"
"os/exec"
)
func runWithAdmin(exePath string, env []string) error {
cmd := exec.Command("pkexec", exePath)
cmd.Env = append(os.Environ(), env...)
return cmd.Start()
}
+15 -15
View File
@@ -1,15 +1,15 @@
//go:build windows
// +build windows
package main
import (
"os"
"os/exec"
)
func runWithAdmin(exePath string, env []string) error {
cmd := exec.Command("powershell", "Start-Process", exePath, "-Verb", "runAs")
cmd.Env = append(os.Environ(), env...)
return cmd.Start()
}
//go:build windows
// +build windows
package main
import (
"os"
"os/exec"
)
func runWithAdmin(exePath string, env []string) error {
cmd := exec.Command("powershell", "Start-Process", exePath, "-Verb", "runAs")
cmd.Env = append(os.Environ(), env...)
return cmd.Start()
}
+4 -4
View File
@@ -1,4 +1,4 @@
# Changelog
### UPDATE
- Fix bug in macos
# Changelog
### UPDATE
- Support linux, macos, window
Binary file not shown.
+705
View File
@@ -0,0 +1,705 @@
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
const (
apiRequestTimeout = 45 * time.Second
maxHTTPAttempts = 5
)
// API responses structures
type CommonResponse struct {
Status bool `json:"status"`
Message string `json:"message"`
Data json.RawMessage `json:"data"`
Errors any `json:"errors"`
}
type AuthResponse struct {
AccessToken string `json:"access_token"`
}
type PreSignedResponse struct {
TokenID string `json:"token_id"`
UploadUrl string `json:"upload_url"`
StorageKey string `json:"storage_key"`
SignedHeaders map[string]string `json:"signed_headers"`
}
type MediaResponse struct {
ID string `json:"id"`
StorageKey string `json:"storage_key"`
OriginalName string `json:"original_name"`
MimeType string `json:"mime_type"`
Size int64 `json:"size"`
}
type PreSignedCompleteDto struct {
TokenID string `json:"token_id"`
FileMetadata json.RawMessage `json:"file_metadata,omitempty"`
}
type CreateComponentRequest struct {
Type string `json:"type"`
Platform string `json:"platform"`
Status string `json:"status"`
Version string `json:"version"`
Description *string `json:"description,omitempty"`
Hash *string `json:"hash,omitempty"`
MediaIDs []string `json:"media_ids"`
GameIDs []string `json:"game_ids"`
}
type UpdateComponentRequest struct {
Type *string `json:"type,omitempty"`
Platform *string `json:"platform,omitempty"`
Status *string `json:"status,omitempty"`
Version *string `json:"version,omitempty"`
Description *string `json:"description,omitempty"`
Hash *string `json:"hash,omitempty"`
MediaIDs []string `json:"media_ids,omitempty"`
GameIDs []string `json:"game_ids,omitempty"`
}
func readFile(path string) string {
data, err := os.ReadFile(path)
if err != nil {
panic(fmt.Sprintf("Failed to read %s: %v", path, err))
}
return string(data)
}
func main() {
// Flag definitions (useful for manual testing, but optional in CI/CD)
apiURLFlag := flag.String("api-url", "https://api.punklorde.org", "Base URL of the management API")
gameIdsFlag := flag.String("game-ids", "", "Comma-separated Game IDs (defaults to ENV_GAME_IDS)")
filesFlag := flag.String("files", "", "Comma-separated list of files to upload (defaults to scanning prebuild/)")
cTypeFlag := flag.String("type", "", "Component type: LAUNCHER, PROXY, SERVER (defaults to ENV_COMPONENT_TYPE or PROXY)")
flag.Parse()
// 1. Resolve settings from Env or Flags
apiURL := *apiURLFlag
if envAPI := os.Getenv("ENV_API_URL"); envAPI != "" {
apiURL = envAPI
}
gameIdsStr := *gameIdsFlag
if gameIdsStr == "" {
gameIdsStr = os.Getenv("ENV_GAME_IDS")
}
if gameIdsStr == "" {
fmt.Fprintln(os.Stderr, "Error: game IDs must be specified via -game-ids flag or ENV_GAME_IDS environment variable")
os.Exit(1)
}
gameIDs := splitCommaSeparated(gameIdsStr)
cType := *cTypeFlag
if cType == "" {
cType = os.Getenv("ENV_COMPONENT_TYPE")
}
if cType == "" {
cType = "PROXY" // Default component type
}
// 2. Read release metadata from files
releaseJSON := readFile("script/release.json")
var meta map[string]string
if err := json.Unmarshal([]byte(releaseJSON), &meta); err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse release.json: %v\n", err)
os.Exit(1)
}
version := meta["tag"]
if version == "" {
fmt.Fprintln(os.Stderr, "Error: 'tag' is missing in release.json")
os.Exit(1)
}
var description *string
if bodyBytes, err := os.ReadFile("script/README_Note.md"); err == nil && len(bodyBytes) > 0 {
descStr := string(bodyBytes)
description = &descStr
}
// 3. Read robot token from environment
robotToken := os.Getenv("ENV_ROBOT_TOKEN")
if robotToken == "" {
fmt.Fprintln(os.Stderr, "Error: ENV_ROBOT_TOKEN environment variable is missing")
os.Exit(1)
}
fmt.Println("Refreshing access token using robot token...")
accessToken, err := refreshRobotToken(apiURL, robotToken)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to refresh token: %v\n", err)
os.Exit(1)
}
fmt.Println("Access token successfully obtained.")
// 4. Resolve files to publish
var filePaths []string
filesStr := *filesFlag
if filesStr == "" {
filesStr = os.Getenv("ENV_FILES")
}
if filesStr != "" {
filePaths = splitCommaSeparated(filesStr)
} else {
// Fallback to scanning prebuild/
prebuildDir := "prebuild"
files, err := os.ReadDir(prebuildDir)
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot read prebuild folder: %v\n", err)
os.Exit(1)
}
for _, file := range files {
if !file.IsDir() && filepath.Ext(file.Name()) == ".zip" {
filePaths = append(filePaths, filepath.Join(prebuildDir, file.Name()))
}
}
}
var processedCount int
for _, filePath := range filePaths {
fileName := filepath.Base(filePath)
fmt.Printf("\n--- Processing asset: %s ---\n", fileName)
// Map filename to platform
platform := detectPlatformFromFilename(fileName)
fmt.Printf("Mapped Platform: %s\n", platform)
// Calculate size and SHA256
size, hash, err := getFileInfoAndHash(filePath)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to process file %s: %v\n", fileName, err)
os.Exit(1)
}
fmt.Printf("Size: %d bytes, SHA256: %s\n", size, hash)
// Request presigned URL
contentType := "application/octet-stream"
if filepath.Ext(fileName) == ".zip" {
contentType = "application/zip"
}
fmt.Println("Requesting presigned URL...")
presigned, err := getPresignedURL(apiURL, accessToken, fileName, contentType, size)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get presigned URL: %v\n", err)
os.Exit(1)
}
// Upload file to S3
fmt.Println("Uploading file to storage...")
err = uploadFileToS3(filePath, presigned)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to upload file to storage: %v\n", err)
os.Exit(1)
}
// Confirm upload completion
fmt.Println("Confirming upload completion...")
mediaID, err := completePreSignedUpload(apiURL, accessToken, presigned.TokenID, hash)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to complete upload: %v\n", err)
os.Exit(1)
}
fmt.Printf("Media ID generated: %s\n", mediaID)
fmt.Println("Checking if component already exists...")
existingID, err := findExistingComponent(apiURL, accessToken, cType, platform, version)
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: Failed to check existing component: %v\n", err)
}
var compID string
if existingID != "" {
fmt.Printf("Component already exists with ID: %s. Updating it...\n", existingID)
updateReq := UpdateComponentRequest{
Type: &cType,
Platform: &platform,
Description: description,
Hash: &hash,
MediaIDs: []string{mediaID},
GameIDs: gameIDs,
}
compID, err = updateComponent(apiURL, accessToken, existingID, updateReq)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to update component: %v\n", err)
os.Exit(1)
}
fmt.Printf("SUCCESS! Component updated with ID: %s for Platform: %s\n", compID, platform)
} else {
fmt.Println("Component does not exist. Registering on the API...")
reqBody := CreateComponentRequest{
Type: cType,
Platform: platform,
Status: "ACTIVE",
Version: version,
Description: description,
Hash: &hash,
MediaIDs: []string{mediaID},
GameIDs: gameIDs,
}
compID, err = createComponent(apiURL, accessToken, reqBody)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to register component: %v\n", err)
os.Exit(1)
}
fmt.Printf("SUCCESS! Component created with ID: %s for Platform: %s\n", compID, platform)
}
processedCount++
}
if processedCount == 0 {
fmt.Println("No component files found to publish.")
} else {
fmt.Printf("\nAll %d components successfully published to API.\n", processedCount)
}
}
func detectPlatformFromFilename(name string) string {
nameLower := strings.ToLower(name)
if strings.Contains(nameLower, "mac_arm") || strings.Contains(nameLower, "macos-arm64") {
return "MACOS_ARM64"
}
if strings.Contains(nameLower, "mac_x86") || strings.Contains(nameLower, "macos-amd64") {
return "MACOS_X64"
}
if strings.Contains(nameLower, "win_arm") || strings.Contains(nameLower, "win-arm") || strings.Contains(nameLower, "windows-arm") {
return "WINDOWS_ARM64"
}
if strings.Contains(nameLower, "win_x86") || strings.Contains(nameLower, "win_x64") || strings.Contains(nameLower, "win") {
return "WINDOWS_X64"
}
if strings.Contains(nameLower, "android_arm64") {
return "ANDROID_ARM64"
}
if strings.Contains(nameLower, "linux_x64") || strings.Contains(nameLower, "linux-amd64") || strings.Contains(nameLower, "linux-x64") {
return "LINUX_X64"
}
if strings.Contains(nameLower, "linux_arm64") || strings.Contains(nameLower, "linux-arm64") {
return "LINUX_ARM64"
}
return "WINDOWS_X64" // Fallback default
}
func splitCommaSeparated(s string) []string {
parts := strings.Split(s, ",")
var result []string
for _, p := range parts {
trimmed := strings.TrimSpace(p)
if trimmed != "" {
result = append(result, trimmed)
}
}
return result
}
func getFileInfoAndHash(path string) (int64, string, error) {
file, err := os.Open(path)
if err != nil {
return 0, "", err
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return 0, "", err
}
hasher := sha256.New()
if _, err := io.Copy(hasher, file); err != nil {
return 0, "", err
}
return stat.Size(), hex.EncodeToString(hasher.Sum(nil)), nil
}
func apiEndpoint(apiURL, path string) string {
return strings.TrimRight(apiURL, "/") + path
}
func retryDelay(attempt int) time.Duration {
delay := time.Duration(1<<uint(attempt-1)) * time.Second
if delay > 20*time.Second {
return 20 * time.Second
}
return delay
}
func isRetryableStatus(statusCode int) bool {
return statusCode == http.StatusRequestTimeout ||
statusCode == http.StatusTooEarly ||
statusCode == http.StatusTooManyRequests ||
statusCode >= http.StatusInternalServerError
}
func isAccepted(statusCode int, accepted ...int) bool {
for _, code := range accepted {
if statusCode == code {
return true
}
}
return false
}
func doHTTPWithRetry(label string, client *http.Client, buildRequest func() (*http.Request, error), accepted ...int) ([]byte, int, error) {
var lastErr error
for attempt := 1; attempt <= maxHTTPAttempts; attempt++ {
req, err := buildRequest()
if err != nil {
return nil, 0, err
}
resp, err := client.Do(req)
if err != nil {
lastErr = err
if attempt < maxHTTPAttempts {
delay := retryDelay(attempt)
fmt.Fprintf(os.Stderr, "%s attempt %d/%d failed: %v. Retrying in %s...\n", label, attempt, maxHTTPAttempts, err, delay)
time.Sleep(delay)
continue
}
return nil, 0, err
}
bodyBytes, readErr := io.ReadAll(resp.Body)
_ = resp.Body.Close()
if readErr != nil {
return nil, resp.StatusCode, readErr
}
if isAccepted(resp.StatusCode, accepted...) {
return bodyBytes, resp.StatusCode, nil
}
if !isRetryableStatus(resp.StatusCode) || attempt == maxHTTPAttempts {
return bodyBytes, resp.StatusCode, nil
}
lastErr = fmt.Errorf("status %d: %s", resp.StatusCode, strings.TrimSpace(string(bodyBytes)))
delay := retryDelay(attempt)
fmt.Fprintf(os.Stderr, "%s attempt %d/%d returned %v. Retrying in %s...\n", label, attempt, maxHTTPAttempts, lastErr, delay)
time.Sleep(delay)
}
return nil, 0, lastErr
}
func refreshRobotToken(apiURL, robotToken string) (string, error) {
endpoint := apiEndpoint(apiURL, "/robot-tokens/refresh")
client := &http.Client{Timeout: apiRequestTimeout}
bodyBytes, statusCode, err := doHTTPWithRetry("refresh robot token", client, func() (*http.Request, error) {
req, err := http.NewRequest("POST", endpoint, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+robotToken)
return req, nil
}, http.StatusOK)
if err != nil {
return "", err
}
if statusCode != http.StatusOK {
return "", fmt.Errorf("refresh token failed with status %d: %s", statusCode, string(bodyBytes))
}
var cr CommonResponse
if err := json.Unmarshal(bodyBytes, &cr); err != nil {
return "", fmt.Errorf("failed to parse common response: %w", err)
}
if !cr.Status {
return "", fmt.Errorf("API error: %s", cr.Message)
}
var auth AuthResponse
if err := json.Unmarshal(cr.Data, &auth); err != nil {
return "", fmt.Errorf("failed to parse auth response data: %w", err)
}
return auth.AccessToken, nil
}
func getPresignedURL(apiURL, accessToken, fileName, contentType string, size int64) (*PreSignedResponse, error) {
endpoint, err := url.Parse(apiEndpoint(apiURL, "/media/presigned"))
if err != nil {
return nil, err
}
query := endpoint.Query()
query.Set("fileName", fileName)
query.Set("content_type", contentType)
query.Set("size", strconv.FormatInt(size, 10))
endpoint.RawQuery = query.Encode()
client := &http.Client{Timeout: apiRequestTimeout}
bodyBytes, statusCode, err := doHTTPWithRetry("get presigned URL", client, func() (*http.Request, error) {
req, err := http.NewRequest("GET", endpoint.String(), nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+accessToken)
return req, nil
}, http.StatusOK)
if err != nil {
return nil, err
}
if statusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get presigned URL (status %d): %s", statusCode, string(bodyBytes))
}
var cr CommonResponse
if err := json.Unmarshal(bodyBytes, &cr); err != nil {
return nil, fmt.Errorf("failed to parse common response: %w", err)
}
if !cr.Status {
return nil, fmt.Errorf("API error: %s", cr.Message)
}
var presigned PreSignedResponse
if err := json.Unmarshal(cr.Data, &presigned); err != nil {
return nil, fmt.Errorf("failed to parse presigned data: %w", err)
}
return &presigned, nil
}
func uploadFileToS3(path string, presigned *PreSignedResponse) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return err
}
req, err := http.NewRequest("PUT", presigned.UploadUrl, file)
if err != nil {
return err
}
// Set S3 signature headers
for k, v := range presigned.SignedHeaders {
req.Header.Set(k, v)
}
req.ContentLength = stat.Size()
if req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "application/zip")
}
client := &http.Client{Timeout: 10 * time.Minute}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
bodyBytes, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("S3 upload failed with status %d: %s", resp.StatusCode, string(bodyBytes))
}
return nil
}
func completePreSignedUpload(apiURL, accessToken, tokenID, hash string) (string, error) {
endpoint := apiEndpoint(apiURL, "/media/presigned/complete")
metaJSON, _ := json.Marshal(map[string]string{"sha256": hash})
dto := PreSignedCompleteDto{
TokenID: tokenID,
FileMetadata: json.RawMessage(metaJSON),
}
payload, _ := json.Marshal(dto)
client := &http.Client{Timeout: apiRequestTimeout}
bodyBytes, statusCode, err := doHTTPWithRetry("complete presigned upload", client, func() (*http.Request, error) {
req, err := http.NewRequest("POST", endpoint, bytes.NewReader(payload))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
return req, nil
}, http.StatusOK)
if err != nil {
return "", err
}
if statusCode != http.StatusOK {
return "", fmt.Errorf("complete upload failed (status %d): %s", statusCode, string(bodyBytes))
}
var cr CommonResponse
if err := json.Unmarshal(bodyBytes, &cr); err != nil {
return "", fmt.Errorf("failed to parse common response: %w", err)
}
if !cr.Status {
return "", fmt.Errorf("API error: %s", cr.Message)
}
var media MediaResponse
if err := json.Unmarshal(cr.Data, &media); err != nil {
return "", fmt.Errorf("failed to parse media data: %w", err)
}
return media.ID, nil
}
func createComponent(apiURL, accessToken string, dto CreateComponentRequest) (string, error) {
endpoint := apiEndpoint(apiURL, "/components")
payload, _ := json.Marshal(dto)
client := &http.Client{Timeout: apiRequestTimeout}
bodyBytes, statusCode, err := doHTTPWithRetry("create component", client, func() (*http.Request, error) {
req, err := http.NewRequest("POST", endpoint, bytes.NewReader(payload))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
return req, nil
}, http.StatusCreated, http.StatusOK)
if err != nil {
return "", err
}
if statusCode != http.StatusCreated && statusCode != http.StatusOK {
return "", fmt.Errorf("create component failed (status %d): %s", statusCode, string(bodyBytes))
}
var cr CommonResponse
if err := json.Unmarshal(bodyBytes, &cr); err != nil {
return "", fmt.Errorf("failed to parse common response: %w", err)
}
if !cr.Status {
return "", fmt.Errorf("API error: %s", cr.Message)
}
var component struct {
ID string `json:"id"`
}
if err := json.Unmarshal(cr.Data, &component); err != nil {
return "", fmt.Errorf("failed to parse component data: %w", err)
}
return component.ID, nil
}
func findExistingComponent(apiURL, accessToken, cType, platform, version string) (string, error) {
endpoint, err := url.Parse(apiEndpoint(apiURL, "/components"))
if err != nil {
return "", err
}
query := endpoint.Query()
query.Set("type", cType)
query.Set("platform", platform)
query.Set("search", version)
endpoint.RawQuery = query.Encode()
client := &http.Client{Timeout: apiRequestTimeout}
bodyBytes, statusCode, err := doHTTPWithRetry("search existing component", client, func() (*http.Request, error) {
req, err := http.NewRequest("GET", endpoint.String(), nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+accessToken)
return req, nil
}, http.StatusOK)
if err != nil {
return "", err
}
if statusCode != http.StatusOK {
return "", fmt.Errorf("failed to search components (status %d): %s", statusCode, string(bodyBytes))
}
var pr struct {
Status bool `json:"status"`
Data []struct {
ID string `json:"id"`
ComponentType string `json:"component_type"`
Platform string `json:"platform"`
Version string `json:"version"`
} `json:"data"`
}
if err := json.Unmarshal(bodyBytes, &pr); err != nil {
return "", fmt.Errorf("failed to parse components search response: %w", err)
}
if !pr.Status {
return "", fmt.Errorf("search components returned unsuccessful status")
}
for _, item := range pr.Data {
if item.ComponentType == cType && item.Platform == platform && item.Version == version {
return item.ID, nil
}
}
return "", nil
}
func updateComponent(apiURL, accessToken, id string, dto UpdateComponentRequest) (string, error) {
endpoint := apiEndpoint(apiURL, "/components/"+url.PathEscape(id))
payload, _ := json.Marshal(dto)
client := &http.Client{Timeout: apiRequestTimeout}
bodyBytes, statusCode, err := doHTTPWithRetry("update component", client, func() (*http.Request, error) {
req, err := http.NewRequest("PUT", endpoint, bytes.NewReader(payload))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/json")
return req, nil
}, http.StatusOK)
if err != nil {
return "", err
}
if statusCode != http.StatusOK {
return "", fmt.Errorf("update component failed (status %d): %s", statusCode, string(bodyBytes))
}
var cr CommonResponse
if err := json.Unmarshal(bodyBytes, &cr); err != nil {
return "", fmt.Errorf("failed to parse common response: %w", err)
}
if !cr.Status {
return "", fmt.Errorf("API error: %s", cr.Message)
}
var component struct {
ID string `json:"id"`
}
if err := json.Unmarshal(cr.Data, &component); err != nil {
return "", fmt.Errorf("failed to parse component data: %w", err)
}
return component.ID, nil
}
+4 -4
View File
@@ -1,5 +1,5 @@
{
"tag": "1.2-01",
"title": "PreBuild Version 1.2 - 01"
}
{
"tag": "1.4-01",
"title": "PreBuild Version 1.4 - 01"
}
+6 -6
View File
@@ -1,7 +1,7 @@
//go:build !windows && !darwin && !linux
package main
func setProxy(enable bool, host string, port string) error {
return nil
//go:build !windows && !darwin && !linux
package main
func setProxy(enable bool, host string, port string) error {
return nil
}
+24 -24
View File
@@ -1,24 +1,24 @@
//go:build linux
// +build linux
package main
import "fmt"
func setProxy(enable bool, host string, port string) error {
httpProxy1 := fmt.Sprintf("HTTP_PROXY=http://%s:%s", host, port)
httpProxy2 := fmt.Sprintf("http_proxy=http://%s:%s", host, port)
ENV_CONFIG = append(ENV_CONFIG, httpProxy1, httpProxy2)
httpsProxy1 := fmt.Sprintf("HTTPS_PROXY=http://%s:%s", host, port)
httpsProxy2 := fmt.Sprintf("https_proxy=http://%s:%s", host, port)
ENV_CONFIG = append(ENV_CONFIG, httpsProxy1, httpsProxy2)
if enable {
ENV_CONFIG = make([]string, 0)
}
return nil
}
//go:build linux
// +build linux
package main
import "fmt"
func setProxy(enable bool, host string, port string) error {
httpProxy1 := fmt.Sprintf("HTTP_PROXY=http://%s:%s", host, port)
httpProxy2 := fmt.Sprintf("http_proxy=http://%s:%s", host, port)
ENV_CONFIG = append(ENV_CONFIG, httpProxy1, httpProxy2)
httpsProxy1 := fmt.Sprintf("HTTPS_PROXY=http://%s:%s", host, port)
httpsProxy2 := fmt.Sprintf("https_proxy=http://%s:%s", host, port)
ENV_CONFIG = append(ENV_CONFIG, httpsProxy1, httpsProxy2)
if enable {
ENV_CONFIG = make([]string, 0)
}
return nil
}
+43 -43
View File
@@ -1,43 +1,43 @@
//go:build windows
// +build windows
package main
import (
"fmt"
"syscall"
"golang.org/x/sys/windows/registry"
)
func setProxy(enable bool, host string, port string) error {
k, _, err := registry.CreateKey(
registry.CURRENT_USER,
`Software\Microsoft\Windows\CurrentVersion\Internet Settings`,
registry.SET_VALUE,
)
if err != nil {
return err
}
if enable {
k.SetDWordValue("ProxyEnable", 1)
addr := fmt.Sprintf("%s:%s", host, port)
val := fmt.Sprintf("http=%s;https=%s", addr, addr)
k.SetStringValue("ProxyServer", val)
} else {
k.SetDWordValue("ProxyEnable", 0)
}
k.Close()
d := syscall.NewLazyDLL("wininet.dll")
o := d.NewProc("InternetSetOptionW")
o.Call(0, 39, 0, 0)
o.Call(0, 37, 0, 0)
return nil
}
//go:build windows
// +build windows
package main
import (
"fmt"
"syscall"
"golang.org/x/sys/windows/registry"
)
func setProxy(enable bool, host string, port string) error {
k, _, err := registry.CreateKey(
registry.CURRENT_USER,
`Software\Microsoft\Windows\CurrentVersion\Internet Settings`,
registry.SET_VALUE,
)
if err != nil {
return err
}
if enable {
k.SetDWordValue("ProxyEnable", 1)
addr := fmt.Sprintf("%s:%s", host, port)
val := fmt.Sprintf("http=%s;https=%s", addr, addr)
k.SetStringValue("ProxyServer", val)
} else {
k.SetDWordValue("ProxyEnable", 0)
}
k.Close()
d := syscall.NewLazyDLL("wininet.dll")
o := d.NewProc("InternetSetOptionW")
o.Call(0, 39, 0, 0)
o.Call(0, 37, 0, 0)
return nil
}