1
1
Fork 0
mirror of https://github.com/schollz/croc.git synced 2025-10-10 21:01:02 +02:00

feat: added TCP healthcheck with nc, better clipboard support, fixed docker deployments and minor changes

This commit is contained in:
Alberto Chiaravalli 2025-09-28 14:32:41 +02:00
parent 990696e419
commit 7992dd607a
No known key found for this signature in database
10 changed files with 70 additions and 37 deletions

View file

@ -45,5 +45,5 @@ jobs:
file: ./Dockerfile file: ./Dockerfile
platforms: linux/amd64,linux/arm,linux/arm64,linux/386 platforms: linux/amd64,linux/arm,linux/arm64,linux/386
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}

6
.gitignore vendored
View file

@ -21,5 +21,9 @@
go.work go.work
go.work.sum go.work.sum
# env file # Environment variables file
.env .env
# Croc builds
/croc
croc_v*

View file

@ -20,5 +20,9 @@ COPY --from=builder /go/croc/croc /go/croc/croc-entrypoint.sh /
USER nobody 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"] ENTRYPOINT ["/croc-entrypoint.sh"]
CMD ["relay"] CMD ["relay"]

View file

@ -47,7 +47,7 @@ func main() {
}() }()
// Wait for a termination signal // Wait for a termination signal
_ = <-sigs <-sigs
utils.RemoveMarkedFiles() utils.RemoveMarkedFiles()
// Exit the program gracefully // Exit the program gracefully

View file

@ -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: "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: "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: "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.EnableBashCompletion = true
app.HideHelp = false app.HideHelp = false
@ -478,9 +478,7 @@ Or you can go back to the classic croc behavior by enabling classic mode:
// save the config // save the config
saveConfig(c, crocOptions) saveConfig(c, crocOptions)
err = cr.Send(minimalFileInfos, emptyFoldersToTransfer, totalNumberFolders) err = cr.Send(minimalFileInfos, emptyFoldersToTransfer, totalNumberFolders)
return return
} }

View file

@ -142,7 +142,9 @@ func (c *Comm) Read() (buf []byte, numBytes int, bs []byte, err error) {
log.Warnf("error setting read deadline: %v", err) log.Warnf("error setting read deadline: %v", err)
} }
// must clear the timeout setting // 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 // read until we get 4 bytes for the magic
header := make([]byte, 4) header := make([]byte, 4)

View file

@ -16,14 +16,27 @@ func TestComm(t *testing.T) {
t.Error(err) 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() { go func() {
log.Debug("starting TCP server on " + port) log.Debug("starting TCP server on " + portStr)
server, err := net.Listen("tcp", "0.0.0.0:"+port) server, err := net.Listen("tcp", portStr)
if err != nil { if err != nil {
log.Error(err) 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 // spawn a new goroutine whenever a client connects
for { for {
connection, err := server.Accept() connection, err := server.Accept()
@ -31,7 +44,7 @@ func TestComm(t *testing.T) {
log.Error(err) log.Error(err)
} }
log.Debugf("client %s connected", connection.RemoteAddr().String()) log.Debugf("client %s connected", connection.RemoteAddr().String())
go func(_ string, connection net.Conn) { go func(_ int, connection net.Conn) {
c := New(connection) c := New(connection)
err = c.Send([]byte("hello, world")) err = c.Send([]byte("hello, world"))
assert.Nil(t, err) assert.Nil(t, err)
@ -49,7 +62,7 @@ func TestComm(t *testing.T) {
}() }()
time.Sleep(300 * time.Millisecond) 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) assert.Nil(t, err)
data, err := a.Receive() data, err := a.Receive()
assert.Equal(t, []byte("hello, world"), data) assert.Equal(t, []byte("hello, world"), data)

View file

@ -258,6 +258,7 @@ type TransferOptions struct {
KeepPathInRemote bool KeepPathInRemote bool
} }
// helper function checking for an empty folder
func isEmptyFolder(folderPath string) (bool, error) { func isEmptyFolder(folderPath string) (bool, error) {
f, err := os.Open(folderPath) f, err := os.Open(folderPath)
if err != nil { if err != nil {
@ -313,15 +314,6 @@ func isChild(parentPath, childPath string) bool {
return !strings.HasPrefix(relPath, "..") 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 // This function retrieves the important file information
// for every file that will be transferred // 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) { 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() machid, _ := machineid.ID()
fmt.Fprintf(os.Stderr, "\rYour machine ID is '%s'\n", machid) 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() // c.spinner.Start()
// create channel for quitting // create channel for quitting
// connect to the relay for messaging // connect to the relay for messaging
@ -830,8 +822,7 @@ On the other computer run:
err = <-errchan err = <-errchan
if err == nil { if err == nil {
// return if no error return // no error
return
} else { } else {
log.Debugf("error from errchan: %v", err) log.Debugf("error from errchan: %v", err)
if strings.Contains(err.Error(), "could not secure channel") { 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) { if c.Options.TestFlag || (!usingLocal && !c.Options.DisableLocal && !isIPset) {
// ask the sender for their local ips and port // ask the sender for their local ips and port
// and try to connect to them // and try to connect to them
var ips []string var ips []string
err = func() (err error) { err = func() (err error) {
var A *pake.Pake 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(".") // usage := diskusage.NewDiskUsage(".")
// if usage.Available() < uint64(totalSize) { // if usage.Available() < uint64(totalSize) {
// return true, fmt.Errorf("not enough disk space") // return true, fmt.Errorf("not enough disk space")
@ -1455,7 +1445,6 @@ func (c *Client) processMessagePake(m message.Message) (err error) {
}(i) }(i)
} }
wg.Wait() wg.Wait()
if !c.Options.IsSender { if !c.Options.IsSender {
log.Debug("sending external IP") log.Debug("sending external IP")
err = message.Send(c.conn[0], c.Key, message.Message{ 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) { func copyToClipboard(str string) {
var cmd *exec.Cmd var cmd *exec.Cmd
switch runtime.GOOS { switch runtime.GOOS {
// Windows should always have clip.exe in PATH by default
case "windows": case "windows":
cmd = exec.Command("clip") cmd = exec.Command("clip")
// MacOS uses pbcopy
case "darwin": case "darwin":
cmd = exec.Command("pbcopy") cmd = exec.Command("pbcopy")
case "linux": // These Unix-like systems are likely using Xorg(with xclip or xsel) or Wayland(with wl-copy)
if os.Getenv("XDG_SESSION_TYPE") == "wayland" { case "linux", "hurd", "freebsd", "openbsd", "netbsd", "dragonfly", "solaris", "illumos", "plan9":
if os.Getenv("XDG_SESSION_TYPE") == "wayland" { // Wayland running
cmd = exec.Command("wl-copy") 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 { } else {
cmd = exec.Command("xclip", "-selection", "clipboard") if isExecutableInPath("xsel") {
cmd = exec.Command("xsel", "-b")
}
} }
default: default:
return 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)) cmd.Stdin = bytes.NewReader([]byte(str))
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
log.Debugf("error copying to clipboard: %v", err) log.Debugf("error copying to clipboard: %v", err)
return return
} }
fmt.Fprintf(os.Stderr, "Code copied to clipboard\n") fmt.Fprintf(os.Stderr, "Code copied to clipboard!\n")
} }

View file

@ -46,7 +46,7 @@ func replaceInFile(fname, start, end, replacement string) (err error) {
if err != nil { if err != nil {
return return
} }
oldVersion := GetStringInBetween(string(b), start, end) oldVersion := getStringInBetween(string(b), start, end)
if oldVersion == "" { if oldVersion == "" {
err = fmt.Errorf("nothing") err = fmt.Errorf("nothing")
return return
@ -61,8 +61,8 @@ func replaceInFile(fname, start, end, replacement string) (err error) {
return return
} }
// GetStringInBetween Returns empty string if no start string found // getStringInBetween Returns empty string if no start string found
func GetStringInBetween(str string, start string, end string) (result string) { func getStringInBetween(str, start, end string) (result string) {
s := strings.Index(str, start) s := strings.Index(str, start)
if s == -1 { if s == -1 {
return return

View file

@ -281,11 +281,12 @@ func LocalIP() string {
return localAddr.IP.String() return localAddr.IP.String()
} }
// GenerateRandomPin returns a randomly generated pin with set lenght
func GenerateRandomPin() string { func GenerateRandomPin() string {
s := "" s := ""
max := new(big.Int) max := new(big.Int)
max.SetInt64(9) max.SetInt64(9)
for i := 0; i < NbPinNumbers; i++ { for range NbPinNumbers {
v, err := rand.Int(rand.Reader, max) v, err := rand.Int(rand.Reader, max)
if err != nil { if err != nil {
panic(err) panic(err)