new: add cli interface with basic webui

This commit is contained in:
Hiddify
2024-03-09 15:49:09 +01:00
parent f9e6f022c8
commit 3a82650759
27 changed files with 433 additions and 222 deletions

View File

@@ -74,7 +74,7 @@ jobs:
rm -f /*.h
rm ./hiddify-libcore*sources* ||echo "no source"
rm ./hiddify-libcore-macos-a*.dylib || echo "no macos arm and amd"
files=$(ls | grep -E '^(libcore\.(dll|so|dylib|aar)|Libcore.xcframework|HiddifyService(\.exe)?)$')
files=$(ls | grep -E '^(libcore\.(dll|so|dylib|aar)|webui|Libcore.xcframework|HiddifyCli(\.exe)?)$')
echo tar -czvf hiddify-core-${{ matrix.job.target }}.tar.gz $files
tar -czvf hiddify-core-${{ matrix.job.target }}.tar.gz $files

View File

@@ -2,7 +2,7 @@ PRODUCT_NAME=libcore
BASENAME=$(PRODUCT_NAME)
BINDIR=bin
LIBNAME=$(PRODUCT_NAME)
SRVNAME=HiddifyService
CLINAME=HiddifyCli
BRANCH=$(shell git branch --show-current)
VERSION=$(shell git describe --tags || echo "unknown version")
@@ -35,24 +35,36 @@ ios: lib_install
cp Info.plist $(BINDIR)/Libcore.xcframework/
webui:
curl -L -o webui.zip https://github.com/hiddify/Yacd-meta/archive/gh-pages.zip
unzip -d ./ -q webui.zip
rm webui.zip
rm -rf bin/webui
mv Yacd-meta-gh-pages bin/webui
windows-amd64:
curl http://localhost:18020/exit || echo "exited"
env GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc $(GOBUILDLIB) -o $(BINDIR)/$(LIBNAME).dll ./custom
go get github.com/akavel/rsrc
go install github.com/akavel/rsrc
# go get github.com/akavel/rsrc
# go install github.com/akavel/rsrc
cp $(BINDIR)/$(LIBNAME).dll ./$(LIBNAME).dll
$$(go env GOPATH)/bin/rsrc -manifest admin_service/cmd/admin_service.manifest -ico ./assets/hiddify-service.ico -o admin_service/cmd/admin_service.syso
env GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc CGO_LDFLAGS="$(LIBNAME).dll" $(GOBUILDSRV) -o $(BINDIR)/$(SRVNAME).exe ./admin_service/cmd
# $$(go env GOPATH)/bin/rsrc -manifest admin_service/cmd/admin_service.manifest -ico ./assets/hiddify-service.ico -o admin_service/cmd/admin_service.syso
$$(go env GOPATH)/bin/rsrc -ico ./assets/hiddify-cli.ico -o ./cli/bydll/cli.syso
env GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc CGO_LDFLAGS="$(LIBNAME).dll" $(GOBUILDSRV) -o $(BINDIR)/$(CLINAME).exe ./cli/bydll
rm ./$(LIBNAME).dll
make webui
linux-amd64:
env GOOS=linux GOARCH=amd64 $(GOBUILDLIB) -o $(BINDIR)/$(LIBNAME).so ./custom
mkdir lib
cp $(BINDIR)/$(LIBNAME).so ./lib/$(LIBNAME).so
env GOOS=linux GOARCH=amd64 CGO_LDFLAGS="./lib/$(LIBNAME).so" $(GOBUILDSRV) -o $(BINDIR)/$(SRVNAME) ./admin_service/cmd
env GOOS=linux GOARCH=amd64 CGO_LDFLAGS="./lib/$(LIBNAME).so" $(GOBUILDSRV) -o $(BINDIR)/$(CLINAME) ./cli/bydll
rm -rf ./lib
chmod +x $(BINDIR)/$(SRVNAME)
chmod +x $(BINDIR)/$(CLINAME)
make webui
macos-amd64:
env GOOS=darwin GOARCH=amd64 CGO_CFLAGS="-mmacosx-version-min=10.11" CGO_LDFLAGS="-mmacosx-version-min=10.11" CGO_ENABLED=1 go build -trimpath -tags $(TAGS),$(IOS_ADD_TAGS) -buildmode=c-shared -o $(BINDIR)/$(LIBNAME)-amd64.dylib ./custom
@@ -62,9 +74,9 @@ macos-arm64:
macos-universal: macos-amd64 macos-arm64
lipo -create $(BINDIR)/$(LIBNAME)-amd64.dylib $(BINDIR)/$(LIBNAME)-arm64.dylib -output $(BINDIR)/$(LIBNAME).dylib
cp $(BINDIR)/$(LIBNAME).dylib ./$(LIBNAME).dylib
env GOOS=darwin GOARCH=amd64 CGO_CFLAGS="-mmacosx-version-min=10.11" CGO_LDFLAGS="-mmacosx-version-min=10.11" CGO_LDFLAGS="bin/$(LIBNAME).dylib" CGO_ENABLED=1 $(GOBUILDSRV) -o $(BINDIR)/$(SRVNAME) ./admin_service/cmd
env GOOS=darwin GOARCH=amd64 CGO_CFLAGS="-mmacosx-version-min=10.11" CGO_LDFLAGS="-mmacosx-version-min=10.11" CGO_LDFLAGS="bin/$(LIBNAME).dylib" CGO_ENABLED=1 $(GOBUILDSRV) -o $(BINDIR)/$(CLINAME) ./admin_service/cmd
rm ./$(LIBNAME).dylib
chmod +x $(BINDIR)/$(SRVNAME)
chmod +x $(BINDIR)/$(CLINAME)
clean:
rm $(BINDIR)/*
@@ -73,6 +85,7 @@ build_protobuf:
protoc --go_out=. --go-grpc_out=. hiddifyrpc/hiddify.proto
release: # Create a new tag for release.
@echo "previous version was $$(git describe --tags $$(git rev-list --tags --max-count=1))"
@echo "WARNING: This operation will creates version tag and push to github"

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<!-- <assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="HiddifyService.exe"
type="win32"
/> -->
<description>Hiddify Tunnel Service</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

View File

@@ -1,50 +0,0 @@
package main
/*
#include <stdlib.h>
#include <stdint.h>
// Import the function from the DLL
char* AdminServiceStart(const char* arg);
*/
import "C"
import (
"fmt"
"os"
"strings"
"unsafe"
)
func main() {
args := os.Args
if len(args) < 2 {
fmt.Println("Usage: HiddifyService.exe empty/start/stop/uninstall/install")
args = append(args, "")
}
arg := C.CString(args[1])
defer C.free(unsafe.Pointer(arg))
result := C.AdminServiceStart(arg)
defer C.free(unsafe.Pointer(result))
goRes := C.GoString(result)
parts := strings.SplitN(goRes, " ", 2)
var parsedExitCode int
_, err := fmt.Sscanf(parts[0], "%d", &parsedExitCode)
parsedOutMessage := parts[1]
if err != nil {
fmt.Println("Error parsing the string:", err)
return
}
fmt.Printf("%d %s", parsedExitCode, parsedOutMessage)
if parsedExitCode != 0 {
os.Exit(parsedExitCode)
}
}

View File

@@ -48,6 +48,7 @@ func StartService(goArg string) (int, string) {
svcConfig := &service.Config{
Name: "HiddifyTunnelService",
DisplayName: "Hiddify Tunnel Service",
Arguments: []string{"tunnel", "run"},
Description: "This is a bridge for tunnel",
Option: map[string]interface{}{
"RunAtLoad": true,

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -4,15 +4,15 @@ set GOARCH=amd64
set CC=x86_64-w64-mingw32-gcc
set CGO_ENABLED=1
curl http://localhost:18020/exit || echo "Exited"
del bin\libcore.dll bin\HiddifyService.exe
del bin\libcore.dll bin\HiddifyCli.exe
set CGO_LDFLAGS=
go build -trimpath -tags with_gvisor,with_quic,with_wireguard,with_ech,with_utls,with_clash_api,with_grpc -ldflags="-w -s" -buildmode=c-shared -o bin/libcore.dll ./custom
go get github.com/akavel/rsrc
go install github.com/akavel/rsrc
rsrc -manifest admin_service\cmd\admin_service.manifest -ico ..\assets\images\tray_icon_connected.ico -o admin_service\cmd\admin_service.syso
rsrc -ico .\assets\images\hiddify-cli.ico -o cli\bydll\cli.syso
copy bin\libcore.dll .
set CGO_LDFLAGS="libcore.dll"
go build -o bin/HiddifyService.exe ./admin_service/cmd/
go build -o bin/HiddifyCli.exe ./cli/bydll/
del libcore.dll

35
cli/bydll/clibydll.go Normal file
View File

@@ -0,0 +1,35 @@
package main
/*
#include <stdlib.h>
#include <stdint.h>
// Import the function from the DLL
char* parseCli(int argc, char** argv);
*/
import "C"
import (
"fmt"
"os"
"unsafe"
)
func main() {
args := os.Args
// Convert []string to []*C.char
var cArgs []*C.char
for _, arg := range args {
cArgs = append(cArgs, C.CString(arg))
}
defer func() {
for _, arg := range cArgs {
C.free(unsafe.Pointer(arg))
}
}()
// Call the C function
result := C.parseCli(C.int(len(cArgs)), (**C.char)(unsafe.Pointer(&cArgs[0])))
fmt.Println(C.GoString(result))
}

11
cli/main.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import (
"os"
"github.com/hiddify/libcore/cmd"
)
func main() {
cmd.ParseCli(os.Args[1:])
}

View File

@@ -1,24 +0,0 @@
package main
import (
"fmt"
"github.com/hiddify/libcore/admin_service"
"github.com/spf13/cobra"
)
var commandService = &cobra.Command{
Use: "admin-service",
Short: "Sign box service start/stop/install/uninstall",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
arg := ""
if len(args) > 1 {
arg = args[1]
}
code, out := admin_service.StartService(arg)
fmt.Printf("exitCode:%d msg=%s", code, out)
},
}

View File

@@ -1,4 +1,4 @@
package main
package cmd
import (
"encoding/json"
@@ -109,6 +109,15 @@ func readConfigAt(path string) (*option.Options, error) {
return &options, nil
}
func readConfigBytes(content []byte) (*option.Options, error) {
var options option.Options
err := options.UnmarshalJSON(content)
if err != nil {
return nil, err
}
return &options, nil
}
func readConfigOptionsAt(path string) (*config.ConfigOptions, error) {
content, err := os.ReadFile(path)
if err != nil {

View File

@@ -1,4 +1,4 @@
package main
package cmd
import (
"os"

View File

@@ -1,4 +1,4 @@
package main
package cmd
import (
"fmt"

View File

@@ -1,65 +1,33 @@
package main
package cmd
import (
"fmt"
"os"
"os/signal"
"syscall"
"github.com/hiddify/libcore/config"
"github.com/hiddify/libcore/global"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/spf13/cobra"
)
var commandRunInputPath string
var (
hiddifySettingPath string
configPath string
)
var commandRun = &cobra.Command{
Use: "run",
Short: "run",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
err := runSingbox(commandRunInputPath)
if err != nil {
log.Fatal(err)
}
},
Args: cobra.OnlyValidArgs,
Run: runCommand,
}
func init() {
commandRun.Flags().StringVarP(&commandRunInputPath, "config", "c", "", "read config")
commandRun.PersistentFlags().BoolP("help", "", false, "help for this command")
commandRun.Flags().StringVarP(&hiddifySettingPath, "hiddify", "h", "", "Hiddify Setting JSON Path")
commandRun.Flags().StringVarP(&configPath, "config", "c", "", "proxy config path or url")
commandRun.MarkFlagRequired("config")
mainCommand.AddCommand(commandRun)
}
func runSingbox(configPath string) error {
options, err := readConfigAt(configPath)
if err != nil {
return err
}
options.Log = &option.LogOptions{}
options.Log.Disabled = false
options.Log.Level = "trace"
options.Log.Output = ""
options.Log.DisableColor = false
err = global.SetupC("./", "./", "./tmp", false)
if err != nil {
return err
}
configStr, err := config.ToJson(*options)
if err != nil {
return err
}
go global.StartServiceC(false, configStr)
fmt.Printf("Waiting for 30 seconds\n")
// <-time.After(time.Second * 30)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan
return err
func runCommand(cmd *cobra.Command, args []string) {
global.RunStandalone(hiddifySettingPath, configPath)
}

21
cmd/cmd_tunnel_service.go Normal file
View File

@@ -0,0 +1,21 @@
package cmd
import (
"fmt"
"github.com/hiddify/libcore/admin_service"
"github.com/spf13/cobra"
)
var commandService = &cobra.Command{
Use: "tunnel run/start/stop/install/uninstall",
Short: "Tunnel Service run/start/stop/install/uninstall",
ValidArgs: []string{"run", "start", "stop", "install", "uninstall"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
Run: func(cmd *cobra.Command, args []string) {
arg := args[0]
code, out := admin_service.StartService(arg)
fmt.Printf("exitCode:%d msg=%s", code, out)
},
}

View File

@@ -1,4 +1,4 @@
package main
package cmd
import (
"bufio"

View File

@@ -1,4 +1,4 @@
package main
package cmd
import (
"os"
@@ -17,7 +17,7 @@ var (
)
var mainCommand = &cobra.Command{
Use: "hiddify-next",
Use: "HiddifyCli",
PersistentPreRun: preRun,
}
@@ -30,10 +30,13 @@ func init() {
}
func main() {
if err := mainCommand.Execute(); err != nil {
func ParseCli(args []string) error {
mainCommand.SetArgs(args)
err := mainCommand.Execute()
if err != nil {
log.Fatal(err)
}
return err
}
func preRun(cmd *cobra.Command, args []string) {

View File

@@ -7,54 +7,43 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
)
func ExecuteCmd(executablePath, args string, background bool) (string, error) {
func ExecuteCmd(executablePath string, background bool, args ...string) (string, error) {
cwd := filepath.Dir(executablePath)
if appimage := os.Getenv("APPIMAGE"); appimage != "" {
executablePath = appimage + " HiddifyService"
executablePath = appimage
if !background {
return "Fail", fmt.Errorf("Appimage can not have service")
return "Fail", fmt.Errorf("Appimage cannot have service")
}
}
if err := execCmdImp([]string{"cocoasudo", "--prompt=Hiddify needs root for tunneling.", executablePath, args}, cwd, background); err == nil {
return "Ok", nil
}
if err := execCmdImp([]string{"gksu", executablePath, args}, cwd, background); err == nil {
return "Ok", nil
}
if err := execCmdImp([]string{"pkexec", executablePath, args}, cwd, background); err == nil {
return "Ok", nil
}
if err := execCmdImp([]string{"xterm", "-e", "sudo " + executablePath + " " + args}, cwd, background); err == nil {
return "Ok", nil
commands := [][]string{
{"cocoasudo", "--prompt=Hiddify needs root for tunneling.", executablePath},
{"gksu", executablePath},
{"pkexec", executablePath},
{"xterm", "-e", "sudo", executablePath, strings.Join(args, " ")},
{"sudo", executablePath},
}
if err := execCmdImp([]string{"sudo", executablePath, args}, cwd, background); err == nil {
return "Ok", nil
var err error
var cmd *exec.Cmd
for _, command := range commands {
cmd = exec.Command(command[0], command[1:]...)
cmd.Dir = cwd
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf("Running command: %v\n", command)
if background {
err = cmd.Start()
} else {
err = cmd.Run()
}
if err == nil {
return "Ok", nil
}
}
return "", fmt.Errorf("Error executing run as root shell command")
}
func execCmdImp(commands []string, cwd string, background bool) error {
cmd := exec.Command(commands[0], commands[1:]...)
cmd.Dir = cwd
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf("Running command: %v", commands)
if background {
if err := cmd.Start(); err != nil {
fmt.Printf("Error: %v\n", err)
return err
}
} else {
if err := cmd.Run(); err != nil {
fmt.Printf("Error: %v\n", err)
return err
}
}
return nil
}

View File

@@ -9,18 +9,30 @@ import (
"golang.org/x/sys/windows"
)
func ExecuteCmd(exe string, args string, background bool) (string, error) {
func ExecuteCmd(exe string, background bool, args ...string) (string, error) {
verb := "runas"
cwd, _ := os.Getwd()
cwd, err := os.Getwd() // Error handling added
if err != nil {
return "", err
}
verbPtr, _ := syscall.UTF16PtrFromString(verb)
exePtr, _ := syscall.UTF16PtrFromString(exe)
cwdPtr, _ := syscall.UTF16PtrFromString(cwd)
argPtr, _ := syscall.UTF16PtrFromString(args)
var showCmd int32 = 1 //SW_NORMAL
// Convert args to UTF16Ptr slice
var argsPtr []*uint16
for _, arg := range args {
argPtr, err := syscall.UTF16PtrFromString(arg)
if err != nil {
return "", err
}
argsPtr = append(argsPtr, argPtr)
}
err := windows.ShellExecute(0, verbPtr, exePtr, argPtr, cwdPtr, showCmd)
var showCmd int32 = 1 // SW_NORMAL
err = windows.ShellExecute(0, verbPtr, exePtr, nil, cwdPtr, showCmd)
if err != nil {
return "", err
}

View File

@@ -94,10 +94,10 @@ func stopTunnelRequest() (bool, error) {
func runTunnelService(opt ConfigOptions) (bool, error) {
executablePath := getTunnelServicePath()
fmt.Printf("Executable path is %s", executablePath)
out, err := ExecuteCmd(executablePath, "install", false)
out, err := ExecuteCmd(executablePath, false, "tunnel", "install")
fmt.Println("Shell command executed:", out, err)
if err != nil {
out, err = ExecuteCmd(executablePath, "", true)
out, err = ExecuteCmd(executablePath, true, "tunnel", "run")
fmt.Println("Shell command executed without flag:", out, err)
}
if err == nil {
@@ -112,11 +112,11 @@ func getTunnelServicePath() string {
binFolder := filepath.Dir(exePath)
switch runtime.GOOS {
case "windows":
fullPath = "HiddifyService.exe"
fullPath = "HiddifyCli.exe"
case "darwin":
fallthrough
default:
fullPath = "HiddifyService"
fullPath = "HiddifyCli"
}
abspath, _ := filepath.Abs(filepath.Join(binFolder, fullPath))

View File

@@ -61,7 +61,7 @@ func BuildConfig(opt ConfigOptions, input option.Options) (*option.Options, erro
fmt.Printf("config options: %+v\n", opt)
var options option.Options
directDNSDomains := []string{}
directDNSDomains := make(map[string]bool)
dnsRules := []option.DefaultDNSRule{}
var bind string
@@ -221,7 +221,7 @@ func BuildConfig(opt ConfigOptions, input option.Options) (*option.Options, erro
}
parsedUrl, err := url.Parse(fmt.Sprintf("https://%s", remoteDNSAddress))
if err == nil && net.ParseIP(parsedUrl.Host) == nil {
directDNSDomains = append(directDNSDomains, fmt.Sprintf("full:%s", parsedUrl.Host))
directDNSDomains["full:"+parsedUrl.Host] = true
//TODO: IS it really needed
}
@@ -420,7 +420,7 @@ func BuildConfig(opt ConfigOptions, input option.Options) (*option.Options, erro
}
if serverDomain != "" {
directDNSDomains = append(directDNSDomains, serverDomain)
directDNSDomains[serverDomain] = true
}
out = *outbound
@@ -518,7 +518,12 @@ func BuildConfig(opt ConfigOptions, input option.Options) (*option.Options, erro
// trickDnsRule.Server = DNSTricksDirectTag
// options.DNS.Rules = append([]option.DNSRule{{Type: C.RuleTypeDefault, DefaultOptions: trickDnsRule}}, options.DNS.Rules...)
domains := strings.Join(directDNSDomains, ",")
directDNSDomainskeys := make([]string, 0, len(directDNSDomains))
for key := range directDNSDomains {
directDNSDomainskeys = append(directDNSDomainskeys, key)
}
domains := strings.Join(directDNSDomainskeys, ",")
directRule := Rule{Domains: domains, Outbound: OutboundBypassTag}
dnsRule := directRule.MakeDNSRule()
dnsRule.Server = DNSDirectTag

View File

@@ -96,7 +96,7 @@ func DefaultConfigOptions() *ConfigOptions {
EnableDNSRouting: false,
},
InboundOptions: InboundOptions{
EnableTun: true,
EnableTun: false,
SetSystemProxy: true,
MixedPort: 2334,
LocalDnsPort: 16450,
@@ -117,12 +117,12 @@ func DefaultConfigOptions() *ConfigOptions {
},
LogLevel: "info",
EnableClashApi: true,
ClashApiPort: 16756,
ClashApiPort: 6756,
GeoIPPath: "geoip.db",
GeoSitePath: "geosite.db",
Rules: []Rule{},
MuxOptions: MuxOptions{
EnableMux: true,
EnableMux: false,
MuxPadding: true,
MaxStreams: 8,
MuxProtocol: "h2mux",

27
custom/cmd_interface.go Normal file
View File

@@ -0,0 +1,27 @@
package main
/*
#include <stdlib.h>
*/
import "C"
import (
"fmt"
"unsafe"
"github.com/hiddify/libcore/cmd"
)
//export parseCli
func parseCli(argc C.int, argv **C.char) *C.char {
args := make([]string, argc)
for i := 0; i < int(argc); i++ {
fmt.Println("parseCli", C.GoString(*argv))
args[i] = C.GoString(*argv)
argv = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + uintptr(unsafe.Sizeof(*argv))))
}
err := cmd.ParseCli(args[1:])
if err != nil {
return C.CString(err.Error())
}
return C.CString("Ok")
}

View File

@@ -16,9 +16,6 @@ import (
func AdminServiceStart(arg *C.char) *C.char {
goArg := C.GoString(arg)
exitCode, outMessage := admin_service.StartService(goArg)
// Allocate memory for the message and copy the string content
return C.CString(fmt.Sprintf("%d %s", exitCode, outMessage))
}

203
global/standalone.go Normal file
View File

@@ -0,0 +1,203 @@
package global
import "C"
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
"github.com/hiddify/libcore/config"
"github.com/sagernet/sing-box/option"
)
type ConfigResult struct {
Config string
RefreshInterval int
}
func readAndBuildConfig(hiddifySettingPath string, configPath string) (ConfigResult, error) {
var result ConfigResult
result, err := readConfigContent(configPath)
if err != nil {
return result, err
}
hiddifyconfig := config.DefaultConfigOptions()
if hiddifySettingPath != "" {
hiddifyconfig, err = readConfigOptionsAt(hiddifySettingPath)
if err != nil {
return result, err
}
}
result.Config, err = buildConfig(result.Config, *hiddifyconfig)
if err != nil {
return result, err
}
return result, nil
}
func readConfigContent(configPath string) (ConfigResult, error) {
var content string
var refreshInterval int
if strings.HasPrefix(configPath, "http://") || strings.HasPrefix(configPath, "https://") {
resp, err := http.Get(configPath)
if err != nil {
return ConfigResult{}, fmt.Errorf("failed to get config from URL: %w", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return ConfigResult{}, fmt.Errorf("failed to read config body: %w", err)
}
content = string(body)
refreshInterval, _ = extractRefreshInterval(resp.Header, content)
fmt.Printf("Refresh interval: %d\n", refreshInterval)
} else {
data, err := ioutil.ReadFile(configPath)
if err != nil {
return ConfigResult{}, fmt.Errorf("failed to read config file: %w", err)
}
content = string(data)
}
return ConfigResult{
Config: content,
RefreshInterval: refreshInterval,
}, nil
}
func extractRefreshInterval(header http.Header, bodyStr string) (int, error) {
refreshIntervalStr := header.Get("profile-update-interval")
if refreshIntervalStr != "" {
refreshInterval, err := strconv.Atoi(refreshIntervalStr)
if err != nil {
return 0, fmt.Errorf("failed to parse refresh interval from header: %w", err)
}
return refreshInterval, nil
}
lines := strings.Split(bodyStr, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "//profile-update-interval:") || strings.HasPrefix(line, "#profile-update-interval:") {
parts := strings.SplitN(line, ":", 2)
str := strings.TrimSpace(parts[1])
refreshInterval, err := strconv.Atoi(str)
if err != nil {
return 0, fmt.Errorf("failed to parse refresh interval from body: %w", err)
}
return refreshInterval, nil
}
}
return 0, nil
}
func buildConfig(configContent string, options config.ConfigOptions) (string, error) {
parsedContent, err := config.ParseConfigContent(configContent, true)
if err != nil {
return "", fmt.Errorf("failed to parse config content: %w", err)
}
singconfigs, err := readConfigBytes([]byte(parsedContent))
if err != nil {
return "", err
}
finalconfig, err := config.BuildConfig(options, *singconfigs)
if err != nil {
return "", fmt.Errorf("failed to build config: %w", err)
}
finalconfig.Log.Output = ""
finalconfig.Experimental.ClashAPI.ExternalUI = "webui"
finalconfig.Experimental.ClashAPI.ExternalController = "0.0.0.0:6756"
if finalconfig.Experimental.ClashAPI.Secret == "" {
// finalconfig.Experimental.ClashAPI.Secret = "hiddify"
}
if err := SetupC("./", "./", "./tmp", false); err != nil {
return "", fmt.Errorf("failed to set up global configuration: %w", err)
}
configStr, err := config.ToJson(*finalconfig)
if err != nil {
return "", fmt.Errorf("failed to convert config to JSON: %w", err)
}
return configStr, nil
}
func updateConfigInterval(current ConfigResult, hiddifySettingPath string, configPath string) {
if current.RefreshInterval <= 0 {
return
}
for {
<-time.After(time.Duration(current.RefreshInterval) * time.Hour)
new, err := readAndBuildConfig(hiddifySettingPath, configPath)
if err != nil {
continue
}
if new.Config != current.Config {
go StopServiceC()
go StartServiceC(false, new.Config)
}
current = new
}
}
func RunStandalone(hiddifySettingPath string, configPath string) error {
current, err := readAndBuildConfig(hiddifySettingPath, configPath)
if err != nil {
return err
}
go StartServiceC(false, current.Config)
go updateConfigInterval(current, hiddifySettingPath, configPath)
fmt.Printf("Press CTRL+C to stop\n")
fmt.Printf("Open http://localhost:6756/?secret=hiddify in your browser\n")
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan
err = StopServiceC()
return err
}
func readConfigBytes(content []byte) (*option.Options, error) {
var options option.Options
err := options.UnmarshalJSON(content)
if err != nil {
return nil, err
}
return &options, nil
}
func readConfigOptionsAt(path string) (*config.ConfigOptions, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var options config.ConfigOptions
err = json.Unmarshal(content, &options)
if err != nil {
return nil, err
}
if options.Warp.WireguardConfigStr != "" {
err := json.Unmarshal([]byte(options.Warp.WireguardConfigStr), &options.Warp.WireguardConfig)
if err != nil {
return nil, err
}
}
return &options, nil
}

10
go.mod
View File

@@ -4,6 +4,7 @@ go 1.21.5
require (
github.com/bepass-org/wireguard-go v0.0.16-alpha
github.com/golang/protobuf v1.5.3
github.com/hiddify/ray2sing v0.0.0-20240221185915-96fae128815c
github.com/kardianos/service v1.2.2
github.com/sagernet/gomobile v0.1.3
@@ -11,18 +12,20 @@ require (
github.com/sagernet/sing-box v1.8.4
github.com/sagernet/sing-dns v0.1.12
github.com/spf13/cobra v1.8.0
github.com/urfave/cli/v2 v2.27.1
github.com/xmdhs/clash2singbox v0.0.2
golang.org/x/sys v0.17.0
google.golang.org/grpc v1.62.0
gopkg.in/yaml.v3 v3.0.1
)
require (
berty.tech/go-libtor v1.0.385 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/akavel/rsrc v0.10.2 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/caddyserver/certmagic v0.20.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/cretz/bine v0.2.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
@@ -34,7 +37,6 @@ require (
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
@@ -66,6 +68,7 @@ require (
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/quic-go/quic-go v0.40.1 // indirect
github.com/refraction-networking/utls v1.6.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // 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/gvisor v0.0.0-20231209105102-8d27a30e436e // indirect
@@ -88,6 +91,7 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
@@ -99,8 +103,8 @@ require (
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.17.0 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 // indirect
google.golang.org/grpc v1.62.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
)

12
go.sum
View File

@@ -2,14 +2,13 @@ berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
@@ -129,6 +128,7 @@ github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
github.com/refraction-networking/utls v1.6.1 h1:n1JG5karzdGWsI6iZmGrOv3SNzR4c+4M8J6KWGsk3lA=
github.com/refraction-networking/utls v1.6.1/go.mod h1:+EbcQOvQvXoFV9AEKbuGlljt1doLRKAVY1jJHe9EtDo=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
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=
@@ -190,10 +190,14 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/xmdhs/clash2singbox v0.0.2 h1:/gxaFm8fmv+UcUZzK508Z0yR01wg1LHrrq872Qibk1I=
github.com/xmdhs/clash2singbox v0.0.2/go.mod h1:B5pbJCwIHhJg6YRPCT04EXw6XXNIIOllMfL3XyJ7ob8=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
@@ -284,8 +288,8 @@ golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 h1:DKU1r6Tj5s1vlU/moGhuGz7E3xRfwjdAfDzbsaQJtEY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=