232 lines
5.7 KiB
Go
232 lines
5.7 KiB
Go
// clip-sync — bidirectional LAN clipboard synchronization daemon.
|
|
//
|
|
// Usage:
|
|
//
|
|
// clip-sync [flags] # run the daemon
|
|
// clip-sync history # print local clipboard history
|
|
// clip-sync --one-shot [text] # send the clipboard (or text) once, then exit
|
|
//
|
|
// Flags:
|
|
//
|
|
// --config path config file (default ~/.config/clip-sync/peers.toml)
|
|
// --version print version and exit
|
|
// --generate-key generate a shared key and exit
|
|
// --generate-cert generate a self-signed TLS cert/key and exit
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"git.dracodev.net/Projets/clip-sync/internal/clipboard"
|
|
"git.dracodev.net/Projets/clip-sync/internal/config"
|
|
"git.dracodev.net/Projets/clip-sync/internal/daemon"
|
|
"git.dracodev.net/Projets/clip-sync/internal/history"
|
|
"git.dracodev.net/Projets/clip-sync/internal/peer"
|
|
"git.dracodev.net/Projets/clip-sync/internal/security"
|
|
"git.dracodev.net/Projets/clip-sync/internal/transfer"
|
|
)
|
|
|
|
// version is overridden at build time via -ldflags "-X main.version=...".
|
|
var version = "0.2.0"
|
|
|
|
func main() {
|
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
|
log.SetPrefix("")
|
|
|
|
fs := flag.NewFlagSet("clip-sync", flag.ExitOnError)
|
|
configPath := fs.String("config", "", "config file path")
|
|
showVersion := fs.Bool("version", false, "print version and exit")
|
|
generateKey := fs.Bool("generate-key", false, "generate a shared key and exit")
|
|
generateCert := fs.Bool("generate-cert", false, "generate a self-signed TLS certificate and exit")
|
|
oneShot := fs.Bool("one-shot", false, "send the clipboard once and exit")
|
|
fs.Usage = func() {
|
|
fmt.Fprintf(os.Stderr, "Usage:\n clip-sync [flags]\n clip-sync history\n\nFlags:\n")
|
|
fs.PrintDefaults()
|
|
}
|
|
_ = fs.Parse(os.Args[1:])
|
|
|
|
// Subcommand: history
|
|
if fs.NArg() > 0 && fs.Arg(0) == "history" {
|
|
runHistory()
|
|
return
|
|
}
|
|
|
|
if *showVersion {
|
|
fmt.Printf("clip-sync %s\n", version)
|
|
return
|
|
}
|
|
|
|
if *generateKey {
|
|
key, err := security.GenerateKey()
|
|
if err != nil {
|
|
log.Fatalf("clip-sync: generate key: %v", err)
|
|
}
|
|
fmt.Println(key)
|
|
return
|
|
}
|
|
|
|
if *generateCert {
|
|
runGenerateCert()
|
|
return
|
|
}
|
|
|
|
cfg, err := loadConfig(*configPath)
|
|
if err != nil {
|
|
log.Fatalf("clip-sync: config: %v", err)
|
|
}
|
|
|
|
if *oneShot {
|
|
runOneShot(cfg, fs.Arg(0))
|
|
return
|
|
}
|
|
|
|
// Subcommand: send-file <path>
|
|
if fs.NArg() > 0 && fs.Arg(0) == "send-file" {
|
|
path := ""
|
|
if fs.NArg() > 1 {
|
|
path = fs.Arg(1)
|
|
}
|
|
runSendFile(cfg, path)
|
|
return
|
|
}
|
|
|
|
runDaemon(cfg)
|
|
}
|
|
|
|
func loadConfig(path string) (*config.Config, error) {
|
|
if path != "" {
|
|
return config.LoadFrom(path)
|
|
}
|
|
return config.Load()
|
|
}
|
|
|
|
func runDaemon(cfg *config.Config) {
|
|
d, err := daemon.New(cfg)
|
|
if err != nil {
|
|
log.Fatalf("clip-sync: init: %v", err)
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
sig := <-sigCh
|
|
log.Printf("clip-sync: received %v, shutting down...", sig)
|
|
cancel()
|
|
}()
|
|
|
|
if err := d.Run(ctx); err != nil && err != context.Canceled {
|
|
log.Fatalf("clip-sync: %v", err)
|
|
}
|
|
|
|
log.Println("clip-sync: stopped")
|
|
}
|
|
|
|
func runOneShot(cfg *config.Config, textArg string) {
|
|
origin, err := os.Hostname()
|
|
if err != nil {
|
|
log.Fatalf("clip-sync: hostname: %v", err)
|
|
}
|
|
|
|
b := peer.NewBroadcaster(cfg.Peers, origin, peer.Options{
|
|
SharedKey: cfg.Security.SharedKey,
|
|
TLS: cfg.Security.TLS,
|
|
CertFile: cfg.Security.CertFile,
|
|
InsecureSkipVerify: cfg.Security.InsecureSkipVerify,
|
|
})
|
|
|
|
text := textArg
|
|
if text == "" {
|
|
cb, err := clipboard.New()
|
|
if err != nil {
|
|
log.Fatalf("clip-sync: clipboard: %v", err)
|
|
}
|
|
text, err = cb.Read()
|
|
if err != nil {
|
|
log.Fatalf("clip-sync: clipboard read: %v", err)
|
|
}
|
|
}
|
|
|
|
if text == "" {
|
|
log.Fatal("clip-sync: nothing to send (clipboard is empty)")
|
|
}
|
|
|
|
b.BroadcastSync(text)
|
|
fmt.Printf("clip-sync: sent %d bytes to %d peer(s)\n", len(text), b.PeerCount())
|
|
}
|
|
|
|
func runSendFile(cfg *config.Config, path string) {
|
|
if path == "" {
|
|
log.Fatal("clip-sync: usage: clip-sync send-file <path>")
|
|
}
|
|
|
|
origin, err := os.Hostname()
|
|
if err != nil {
|
|
log.Fatalf("clip-sync: hostname: %v", err)
|
|
}
|
|
|
|
f, err := transfer.ReadFile(path, origin)
|
|
if err != nil {
|
|
log.Fatalf("clip-sync: read file: %v", err)
|
|
}
|
|
|
|
b := peer.NewBroadcaster(cfg.Peers, origin, peer.Options{
|
|
SharedKey: cfg.Security.SharedKey,
|
|
TLS: cfg.Security.TLS,
|
|
CertFile: cfg.Security.CertFile,
|
|
InsecureSkipVerify: cfg.Security.InsecureSkipVerify,
|
|
})
|
|
|
|
b.SendFileSync(*f)
|
|
fmt.Printf("clip-sync: sent %q (%s) to %d peer(s)\n", f.Name, f.Mime, b.PeerCount())
|
|
}
|
|
|
|
func runHistory() {
|
|
path, err := history.DefaultPath()
|
|
if err != nil {
|
|
log.Fatalf("clip-sync: history: %v", err)
|
|
}
|
|
entries := history.New(path, 0).List()
|
|
if len(entries) == 0 {
|
|
fmt.Println("clip-sync: no history yet")
|
|
return
|
|
}
|
|
for _, e := range entries {
|
|
ts := time.Unix(0, e.Ts).Format("2006-01-02 15:04:05")
|
|
text := strings.ReplaceAll(e.Text, "\n", "\\n")
|
|
if len(text) > 80 {
|
|
text = text[:80] + "…"
|
|
}
|
|
fmt.Printf("%s [%s] %s\n", ts, e.Origin, text)
|
|
}
|
|
}
|
|
|
|
func runGenerateCert() {
|
|
certFile, err := config.DefaultCertFile()
|
|
if err != nil {
|
|
log.Fatalf("clip-sync: cert path: %v", err)
|
|
}
|
|
keyFile, err := config.DefaultKeyFile()
|
|
if err != nil {
|
|
log.Fatalf("clip-sync: key path: %v", err)
|
|
}
|
|
host, _ := os.Hostname()
|
|
|
|
if err := security.GenerateSelfSignedCert(certFile, keyFile, []string{host, "localhost"}); err != nil {
|
|
log.Fatalf("clip-sync: generate cert: %v", err)
|
|
}
|
|
fmt.Printf("clip-sync: certificate written to %s and %s\n", certFile, keyFile)
|
|
fmt.Println("Set [security] tls = true and cert_file/key_file in peers.toml, and share these files with your peers.")
|
|
}
|