diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd5a6ed..c4ae3f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/Makefile b/Makefile index 1a30e53..2ffb9d9 100644 --- a/Makefile +++ b/Makefile @@ -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" diff --git a/admin_service/cmd/admin_service.manifest b/admin_service/cmd/admin_service.manifest deleted file mode 100644 index fd7d880..0000000 --- a/admin_service/cmd/admin_service.manifest +++ /dev/null @@ -1,17 +0,0 @@ - - - -Hiddify Tunnel Service - - - - - - - - \ No newline at end of file diff --git a/admin_service/cmd/main.go b/admin_service/cmd/main.go deleted file mode 100644 index 94f7feb..0000000 --- a/admin_service/cmd/main.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -/* -#include -#include - -// 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) - } -} diff --git a/admin_service/service.go b/admin_service/service.go index a33d25c..4fa5c37 100644 --- a/admin_service/service.go +++ b/admin_service/service.go @@ -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, diff --git a/assets/hiddify-service.ico b/assets/hiddify-cli.ico similarity index 100% rename from assets/hiddify-service.ico rename to assets/hiddify-cli.ico diff --git a/build_windows.bat b/build_windows.bat index 26e5258..c2fe184 100644 --- a/build_windows.bat +++ b/build_windows.bat @@ -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 diff --git a/cli/bydll/clibydll.go b/cli/bydll/clibydll.go new file mode 100644 index 0000000..02c7184 --- /dev/null +++ b/cli/bydll/clibydll.go @@ -0,0 +1,35 @@ +package main + +/* +#include +#include + +// 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)) +} diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 0000000..9ecd69f --- /dev/null +++ b/cli/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "os" + + "github.com/hiddify/libcore/cmd" +) + +func main() { + cmd.ParseCli(os.Args[1:]) +} diff --git a/cmd/cmd_admin_service.go b/cmd/cmd_admin_service.go deleted file mode 100644 index f295332..0000000 --- a/cmd/cmd_admin_service.go +++ /dev/null @@ -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) - - }, -} diff --git a/cmd/cmd_config.go b/cmd/cmd_config.go index d564444..f4bc868 100644 --- a/cmd/cmd_config.go +++ b/cmd/cmd_config.go @@ -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 { diff --git a/cmd/cmd_gen_cert.go b/cmd/cmd_gen_cert.go index 6915ded..d6770cb 100644 --- a/cmd/cmd_gen_cert.go +++ b/cmd/cmd_gen_cert.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "os" diff --git a/cmd/cmd_parse.go b/cmd/cmd_parse.go index 193ed48..6006fa5 100644 --- a/cmd/cmd_parse.go +++ b/cmd/cmd_parse.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "fmt" diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index 21f6833..95c97c0 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -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) } diff --git a/cmd/cmd_tunnel_service.go b/cmd/cmd_tunnel_service.go new file mode 100644 index 0000000..b61d6e0 --- /dev/null +++ b/cmd/cmd_tunnel_service.go @@ -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) + }, +} diff --git a/cmd/cmd_warp.go b/cmd/cmd_warp.go index 36fa0da..1f16116 100644 --- a/cmd/cmd_warp.go +++ b/cmd/cmd_warp.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "bufio" diff --git a/cmd/main.go b/cmd/interface.go similarity index 85% rename from cmd/main.go rename to cmd/interface.go index 6cb73b4..8d05e91 100644 --- a/cmd/main.go +++ b/cmd/interface.go @@ -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) { diff --git a/config/admin_service_cmd_runner.go b/config/admin_service_cmd_runner.go index 0baeef8..761b94a 100644 --- a/config/admin_service_cmd_runner.go +++ b/config/admin_service_cmd_runner.go @@ -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 } diff --git a/config/admin_service_cmd_runner_windows.go b/config/admin_service_cmd_runner_windows.go index f02f8c2..82a5806 100644 --- a/config/admin_service_cmd_runner_windows.go +++ b/config/admin_service_cmd_runner_windows.go @@ -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 } diff --git a/config/admin_service_commander.go b/config/admin_service_commander.go index 85f69ab..4f4c68a 100644 --- a/config/admin_service_commander.go +++ b/config/admin_service_commander.go @@ -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)) diff --git a/config/config.go b/config/config.go index fce5ea3..26eab3f 100644 --- a/config/config.go +++ b/config/config.go @@ -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 diff --git a/config/option.go b/config/option.go index 39d0d0d..27be0e1 100644 --- a/config/option.go +++ b/config/option.go @@ -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", diff --git a/custom/cmd_interface.go b/custom/cmd_interface.go new file mode 100644 index 0000000..50a3b11 --- /dev/null +++ b/custom/cmd_interface.go @@ -0,0 +1,27 @@ +package main + +/* +#include +*/ +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") +} diff --git a/custom/command_admin_service.go b/custom/command_admin_service.go index 7af2b62..982978b 100644 --- a/custom/command_admin_service.go +++ b/custom/command_admin_service.go @@ -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)) } diff --git a/global/standalone.go b/global/standalone.go new file mode 100644 index 0000000..670b3cf --- /dev/null +++ b/global/standalone.go @@ -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 +} diff --git a/go.mod b/go.mod index ee3b9d3..200c9f6 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 3ad0bb0..a7ac7ff 100644 --- a/go.sum +++ b/go.sum @@ -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=