From 7992dd607a1fd15b5342ea7b9635d2229d977134 Mon Sep 17 00:00:00 2001 From: Alberto Chiaravalli Date: Sun, 28 Sep 2025 14:32:41 +0200 Subject: [PATCH] feat: added TCP healthcheck with nc, better clipboard support, fixed docker deployments and minor changes --- .github/workflows/deploy.yml | 4 +-- .gitignore | 6 ++++- Dockerfile | 4 +++ main.go | 2 +- src/cli/cli.go | 4 +-- src/comm/comm.go | 4 ++- src/comm/comm_test.go | 25 +++++++++++++----- src/croc/croc.go | 49 ++++++++++++++++++++++-------------- src/install/updateversion.go | 6 ++--- src/utils/utils.go | 3 ++- 10 files changed, 70 insertions(+), 37 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6ab832a4..a4d42760 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -45,5 +45,5 @@ jobs: file: ./Dockerfile platforms: linux/amd64,linux/arm,linux/arm64,linux/386 push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.docker_meta.outputs.tags }} - labels: ${{ steps.docker_meta.outputs.labels }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index 6f72f892..d91f46b2 100644 --- a/.gitignore +++ b/.gitignore @@ -21,5 +21,9 @@ go.work go.work.sum -# env file +# Environment variables file .env + +# Croc builds +/croc +croc_v* diff --git a/Dockerfile b/Dockerfile index f1fc0c6e..6a5fcd1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,5 +20,9 @@ COPY --from=builder /go/croc/croc /go/croc/croc-entrypoint.sh / USER nobody +# Simple TCP health check with nc! +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD nc -z localhost 9009 || exit 1 + ENTRYPOINT ["/croc-entrypoint.sh"] CMD ["relay"] diff --git a/main.go b/main.go index 17dbe406..f2e15dc7 100644 --- a/main.go +++ b/main.go @@ -47,7 +47,7 @@ func main() { }() // Wait for a termination signal - _ = <-sigs + <-sigs utils.RemoveMarkedFiles() // Exit the program gracefully diff --git a/src/cli/cli.go b/src/cli/cli.go index fb39f1f5..a2d19364 100644 --- a/src/cli/cli.go +++ b/src/cli/cli.go @@ -119,7 +119,7 @@ func Run() (err error) { &cli.StringFlag{Name: "pass", Value: models.DEFAULT_PASSPHRASE, Usage: "password for the relay", EnvVars: []string{"CROC_PASS"}}, &cli.StringFlag{Name: "socks5", Value: "", Usage: "add a socks5 proxy", EnvVars: []string{"SOCKS5_PROXY"}}, &cli.StringFlag{Name: "connect", Value: "", Usage: "add a http proxy", EnvVars: []string{"HTTP_PROXY"}}, - &cli.StringFlag{Name: "throttleUpload", Value: "", Usage: "Throttle the upload speed e.g. 500k"}, + &cli.StringFlag{Name: "throttleUpload", Value: "", Usage: "throttle the upload speed e.g. 500k"}, } app.EnableBashCompletion = true app.HideHelp = false @@ -478,9 +478,7 @@ Or you can go back to the classic croc behavior by enabling classic mode: // save the config saveConfig(c, crocOptions) - err = cr.Send(minimalFileInfos, emptyFoldersToTransfer, totalNumberFolders) - return } diff --git a/src/comm/comm.go b/src/comm/comm.go index b1b0f257..a0d7bbe3 100644 --- a/src/comm/comm.go +++ b/src/comm/comm.go @@ -142,7 +142,9 @@ func (c *Comm) Read() (buf []byte, numBytes int, bs []byte, err error) { log.Warnf("error setting read deadline: %v", err) } // must clear the timeout setting - defer c.connection.SetDeadline(time.Time{}) + if err := c.connection.SetDeadline(time.Time{}); err != nil { + log.Warnf("failed to clear deadline: %v", err) + } // read until we get 4 bytes for the magic header := make([]byte, 4) diff --git a/src/comm/comm_test.go b/src/comm/comm_test.go index 30925391..d9981664 100644 --- a/src/comm/comm_test.go +++ b/src/comm/comm_test.go @@ -16,14 +16,27 @@ func TestComm(t *testing.T) { t.Error(err) } - port := "8001" + // Use dynamic port allocation to avoid conflicts + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + port := listener.Addr().(*net.TCPAddr).Port + portStr := listener.Addr().String() + listener.Close() // Close the listener so we can reopen it in the goroutine + go func() { - log.Debug("starting TCP server on " + port) - server, err := net.Listen("tcp", "0.0.0.0:"+port) + log.Debug("starting TCP server on " + portStr) + server, err := net.Listen("tcp", portStr) if err != nil { log.Error(err) + return } - defer server.Close() + defer func() { + if err := server.Close(); err != nil { + log.Error(err) + } + }() // spawn a new goroutine whenever a client connects for { connection, err := server.Accept() @@ -31,7 +44,7 @@ func TestComm(t *testing.T) { log.Error(err) } log.Debugf("client %s connected", connection.RemoteAddr().String()) - go func(_ string, connection net.Conn) { + go func(_ int, connection net.Conn) { c := New(connection) err = c.Send([]byte("hello, world")) assert.Nil(t, err) @@ -49,7 +62,7 @@ func TestComm(t *testing.T) { }() time.Sleep(300 * time.Millisecond) - a, err := NewConnection("127.0.0.1:"+port, 10*time.Minute) + a, err := NewConnection(portStr, 10*time.Minute) assert.Nil(t, err) data, err := a.Receive() assert.Equal(t, []byte("hello, world"), data) diff --git a/src/croc/croc.go b/src/croc/croc.go index 01019e1a..1796ee44 100644 --- a/src/croc/croc.go +++ b/src/croc/croc.go @@ -258,6 +258,7 @@ type TransferOptions struct { KeepPathInRemote bool } +// helper function checking for an empty folder func isEmptyFolder(folderPath string) (bool, error) { f, err := os.Open(folderPath) if err != nil { @@ -313,15 +314,6 @@ func isChild(parentPath, childPath string) bool { return !strings.HasPrefix(relPath, "..") } -func recursiveFiles(path string) (paths []string, err error) { - paths = []string{strings.ToLower(path)} - err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error { - paths = append(paths, strings.ToLower(path)) - return nil - }) - return -} - // This function retrieves the important file information // for every file that will be transferred func GetFilesInfo(fnames []string, zipfolder bool, ignoreGit bool, exclusions []string) (filesInfo []FileInfo, emptyFolders []FileInfo, totalNumberFolders int, err error) { @@ -679,7 +671,7 @@ On the other computer run: machid, _ := machineid.ID() fmt.Fprintf(os.Stderr, "\rYour machine ID is '%s'\n", machid) } - // // c.spinner.Suffix = " waiting for recipient..." + // c.spinner.Suffix = " waiting for recipient..." // c.spinner.Start() // create channel for quitting // connect to the relay for messaging @@ -830,8 +822,7 @@ On the other computer run: err = <-errchan if err == nil { - // return if no error - return + return // no error } else { log.Debugf("error from errchan: %v", err) if strings.Contains(err.Error(), "could not secure channel") { @@ -988,7 +979,6 @@ func (c *Client) Receive() (err error) { if c.Options.TestFlag || (!usingLocal && !c.Options.DisableLocal && !isIPset) { // ask the sender for their local ips and port // and try to connect to them - var ips []string err = func() (err error) { var A *pake.Pake @@ -1291,7 +1281,7 @@ func (c *Client) processMessageFileInfo(m message.Message) (done bool, err error } } } - // // check the totalSize does not exceed disk space + // check the totalSize does not exceed disk space // usage := diskusage.NewDiskUsage(".") // if usage.Available() < uint64(totalSize) { // return true, fmt.Errorf("not enough disk space") @@ -1455,7 +1445,6 @@ func (c *Client) processMessagePake(m message.Message) (err error) { }(i) } wg.Wait() - if !c.Options.IsSender { log.Debug("sending external IP") err = message.Send(c.conn[0], c.Key, message.Message{ @@ -2135,26 +2124,48 @@ func (c *Client) sendData(i int) { } } +// isExecutableInPath checks for the availability of an executable +func isExecutableInPath(executableName string) bool { + _, err := exec.LookPath(executableName) + return err == nil +} + +// copyToClipboard tries to send the code to the operating system clipboard func copyToClipboard(str string) { var cmd *exec.Cmd switch runtime.GOOS { + // Windows should always have clip.exe in PATH by default case "windows": cmd = exec.Command("clip") + // MacOS uses pbcopy case "darwin": cmd = exec.Command("pbcopy") - case "linux": - if os.Getenv("XDG_SESSION_TYPE") == "wayland" { + // These Unix-like systems are likely using Xorg(with xclip or xsel) or Wayland(with wl-copy) + case "linux", "hurd", "freebsd", "openbsd", "netbsd", "dragonfly", "solaris", "illumos", "plan9": + if os.Getenv("XDG_SESSION_TYPE") == "wayland" { // Wayland running cmd = exec.Command("wl-copy") + } else if os.Getenv("XDG_SESSION_TYPE") == "x11" || os.Getenv("XDG_SESSION_TYPE") == "xorg" { // Xorg running + if isExecutableInPath("xclip") { + cmd = exec.Command("xclip", "-selection", "clipboard") + } } else { - cmd = exec.Command("xclip", "-selection", "clipboard") + if isExecutableInPath("xsel") { + cmd = exec.Command("xsel", "-b") + } } default: return } + // Nothing has been found + if cmd == nil { + log.Warn("system not supported, display server not supported or supported clipboard tool missing!") + return + } + // Sending stdin into the available clipboard program cmd.Stdin = bytes.NewReader([]byte(str)) if err := cmd.Run(); err != nil { log.Debugf("error copying to clipboard: %v", err) return } - fmt.Fprintf(os.Stderr, "Code copied to clipboard\n") + fmt.Fprintf(os.Stderr, "Code copied to clipboard!\n") } diff --git a/src/install/updateversion.go b/src/install/updateversion.go index c6ce5671..29646f25 100644 --- a/src/install/updateversion.go +++ b/src/install/updateversion.go @@ -46,7 +46,7 @@ func replaceInFile(fname, start, end, replacement string) (err error) { if err != nil { return } - oldVersion := GetStringInBetween(string(b), start, end) + oldVersion := getStringInBetween(string(b), start, end) if oldVersion == "" { err = fmt.Errorf("nothing") return @@ -61,8 +61,8 @@ func replaceInFile(fname, start, end, replacement string) (err error) { return } -// GetStringInBetween Returns empty string if no start string found -func GetStringInBetween(str string, start string, end string) (result string) { +// getStringInBetween Returns empty string if no start string found +func getStringInBetween(str, start, end string) (result string) { s := strings.Index(str, start) if s == -1 { return diff --git a/src/utils/utils.go b/src/utils/utils.go index 6c04b410..d4334ff0 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -281,11 +281,12 @@ func LocalIP() string { return localAddr.IP.String() } +// GenerateRandomPin returns a randomly generated pin with set lenght func GenerateRandomPin() string { s := "" max := new(big.Int) max.SetInt64(9) - for i := 0; i < NbPinNumbers; i++ { + for range NbPinNumbers { v, err := rand.Int(rand.Reader, max) if err != nil { panic(err)