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

feat: a better clipboard, a simple TCP healthcheck and fixed docker deployments

This commit is contained in:
Alberto Chiaravalli 2025-09-27 10:24:35 +02:00
parent 990696e419
commit 1773cd975b
No known key found for this signature in database
12 changed files with 61 additions and 47 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"]

4
go.mod
View file

@ -17,6 +17,7 @@ require (
github.com/schollz/progressbar/v3 v3.18.0 github.com/schollz/progressbar/v3 v3.18.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
golang.design/x/clipboard v0.7.1
golang.org/x/crypto v0.42.0 golang.org/x/crypto v0.42.0
golang.org/x/net v0.44.0 golang.org/x/net v0.44.0
golang.org/x/sys v0.36.0 golang.org/x/sys v0.36.0
@ -34,5 +35,8 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tscholl2/siec v0.0.0-20240310163802-c2c6f6198406 // indirect github.com/tscholl2/siec v0.0.0-20240310163802-c2c6f6198406 // indirect
github.com/twmb/murmur3 v1.1.8 // indirect github.com/twmb/murmur3 v1.1.8 // indirect
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 // indirect
golang.org/x/image v0.28.0 // indirect
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

8
go.sum
View file

@ -62,6 +62,8 @@ github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.design/x/clipboard v0.7.1 h1:OEG3CmcYRBNnRwpDp7+uWLiZi3hrMRJpE9JkkkYtz2c=
golang.design/x/clipboard v0.7.1/go.mod h1:i5SiIqj0wLFw9P/1D7vfILFK0KHMk7ydE72HRrUIgkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
@ -70,6 +72,12 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 h1:Wdx0vgH5Wgsw+lF//LJKmWOJBLWX6nprsMqnf99rYDE=
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f h1:/n+PL2HlfqeSiDCuhdBbRNlGS/g2fM4OHufalHaTVG8=
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f/go.mod h1:ESkJ836Z6LpG6mTVAhA48LpfW/8fNR0ifStlH2axyfg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=

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

@ -12,10 +12,8 @@ import (
"math" "math"
"net" "net"
"os" "os"
"os/exec"
"path" "path"
"path/filepath" "path/filepath"
"runtime"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -28,6 +26,7 @@ import (
"github.com/schollz/peerdiscovery" "github.com/schollz/peerdiscovery"
"github.com/schollz/progressbar/v3" "github.com/schollz/progressbar/v3"
"github.com/skip2/go-qrcode" "github.com/skip2/go-qrcode"
"golang.design/x/clipboard"
"golang.org/x/term" "golang.org/x/term"
"golang.org/x/time/rate" "golang.org/x/time/rate"
@ -47,6 +46,11 @@ var (
func init() { func init() {
log.SetLevel("debug") log.SetLevel("debug")
// Initializing clipboard
err := clipboard.Init()
if err != nil {
panic(err)
}
} }
// Debug toggles debug mode // Debug toggles debug mode
@ -58,7 +62,7 @@ func Debug(debug bool) {
} }
} }
// Options specifies user specific options // Options specifies user options
type Options struct { type Options struct {
IsSender bool IsSender bool
SharedSecret string SharedSecret string
@ -671,7 +675,8 @@ On the other computer run:
(For Linux/macOS) (For Linux/macOS)
CROC_SECRET=%[1]q croc %[2]s CROC_SECRET=%[1]q croc %[2]s
`, c.Options.SharedSecret, flags.String()) `, c.Options.SharedSecret, flags.String())
copyToClipboard(c.Options.SharedSecret) // Copying to clipboard
clipboard.Write(clipboard.FmtText, []byte(c.Options.SharedSecret))
if c.Options.ShowQrCode { if c.Options.ShowQrCode {
showReceiveCommandQrCode(fmt.Sprintf("%[1]s", c.Options.SharedSecret)) showReceiveCommandQrCode(fmt.Sprintf("%[1]s", c.Options.SharedSecret))
} }
@ -1828,7 +1833,6 @@ func (c *Client) updateIfRecipientHasFileInfo() (err error) {
log.Debugf("hashed %s to %x using %s", fileInfo.Name, fileHash, c.Options.HashAlgorithm) log.Debugf("hashed %s to %x using %s", fileInfo.Name, fileHash, c.Options.HashAlgorithm)
log.Debugf("hashes are not equal %x != %x", fileHash, fileInfo.Hash) log.Debugf("hashes are not equal %x != %x", fileHash, fileInfo.Hash)
if errHash == nil && !c.Options.Overwrite && errRecipientFile == nil && !strings.HasPrefix(fileInfo.Name, "croc-stdin-") && !c.Options.SendingText { if errHash == nil && !c.Options.Overwrite && errRecipientFile == nil && !strings.HasPrefix(fileInfo.Name, "croc-stdin-") && !c.Options.SendingText {
missingChunks := utils.ChunkRangesToChunks(utils.MissingChunks( missingChunks := utils.ChunkRangesToChunks(utils.MissingChunks(
path.Join(fileInfo.FolderRemote, fileInfo.Name), path.Join(fileInfo.FolderRemote, fileInfo.Name),
fileInfo.Size, fileInfo.Size,
@ -2134,27 +2138,3 @@ func (c *Client) sendData(i int) {
} }
} }
} }
func copyToClipboard(str string) {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("clip")
case "darwin":
cmd = exec.Command("pbcopy")
case "linux":
if os.Getenv("XDG_SESSION_TYPE") == "wayland" {
cmd = exec.Command("wl-copy")
} else {
cmd = exec.Command("xclip", "-selection", "clipboard")
}
default:
return
}
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")
}

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)