aboutsummaryrefslogtreecommitdiffstats
path: root/cmd_linux.go
diff options
context:
space:
mode:
authorBernhard Guillon <Bernhard.Guillon@begu.org>2026-07-07 09:03:31 +0200
committerBernhard Guillon <Bernhard.Guillon@begu.org>2026-07-07 09:03:31 +0200
commita0d157d45c5b4c63afc2cfc82bde3ae3207bc455 (patch)
treec6152cf56c437945359a3b80fc16b76e9ba7332a /cmd_linux.go
parente4c2f78abe427d9fdbfb46bb205f2210a03704fa (diff)
downloadsourcewatch-a0d157d45c5b4c63afc2cfc82bde3ae3207bc455.tar.gz
sourcewatch-a0d157d45c5b4c63afc2cfc82bde3ae3207bc455.zip
Add Windows native watcher with Unix socket IPC
Architecture: - Windows binary: watches files via ReadDirectoryChangesW (fsnotify), sends events as newline-delimited JSON over Unix domain socket - Container: performs initial sync, then listens on socket for events from Windows watcher New files: - cmd_windows.go: Windows serve command (watcher + socket server) - cmd_linux.go: Linux socket client + event listener - types.go: shared FileEvent, FileMeta, Manifest types - watcher_test_helper.go: inotify-based watcher for integration tests Changes: - main.go: simplified to just call run() - watcher.go: removed fsnotify dependency, extracted Watcher interface - sync.go: removed duplicate type definitions - watcher_test.go: uses watchForTests() helper Usage: sourcewatch.exe -dir C:\myproject -socket D:\sourcewatch.sock docker run -v D:\sourcewatch.sock:/run/sourcewatch.sock \ -v C:\myproject:/host:ro -v mydata:/data sourcewatch
Diffstat (limited to 'cmd_linux.go')
-rw-r--r--cmd_linux.go131
1 files changed, 131 insertions, 0 deletions
diff --git a/cmd_linux.go b/cmd_linux.go
new file mode 100644
index 0000000..9e7e237
--- /dev/null
+++ b/cmd_linux.go
@@ -0,0 +1,131 @@
+//go:build !windows
+
+package main
+
+import (
+ "bufio"
+ "context"
+ "encoding/json"
+ "flag"
+ "net"
+ "os"
+ "os/signal"
+ "path/filepath"
+ "syscall"
+ "time"
+)
+
+var (
+ hostDir = flag.String("host", "/host", "Host source directory (read-only)")
+ dataDir = flag.String("data", "/data", "Data destination directory")
+ verbose = flag.Bool("verbose", false, "Enable verbose logging (shorthand for -log-level debug)")
+ logLevel = flag.String("log-level", "info", "Log level: debug, info, warn, error, none")
+ dryRun = flag.Bool("dry-run", false, "Preview what would be synced without copying")
+ socketPath = flag.String("socket", "", "Unix socket to receive events from (Windows watcher)")
+)
+
+func run() {
+ flag.Parse()
+
+ if *verbose {
+ *logLevel = "debug"
+ }
+
+ level, err := ParseLogLevel(*logLevel)
+ if err != nil {
+ logErrorf("%v", err)
+ os.Exit(1)
+ }
+ SetLogLevel(level)
+
+ stack := NewGitignoreStack(*hostDir)
+
+ manifest, err := initialSync(*hostDir, *dataDir, stack, *dryRun)
+ if err != nil {
+ logErrorf("initial sync failed: %v", err)
+ os.Exit(1)
+ }
+
+ if *dryRun {
+ logInfo("dry-run complete, exiting")
+ return
+ }
+
+ if *socketPath == "" {
+ logInfo("no -socket specified, running in standalone mode (no file watching)")
+ ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
+ defer cancel()
+ <-ctx.Done()
+ return
+ }
+
+ logInfo("watcher: connecting to %s ...", *socketPath)
+ ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
+ defer cancel()
+
+ if err := listenSocket(ctx, *socketPath, manifest, stack); err != nil {
+ logErrorf("socket listener failed: %v", err)
+ os.Exit(1)
+ }
+}
+
+func listenSocket(ctx context.Context, socketPath string, manifest *Manifest, stack *GitignoreStack) error {
+ var conn net.Conn
+ var err error
+
+ for {
+ if ctx.Err() != nil {
+ return ctx.Err()
+ }
+ conn, err = net.Dial("unix", socketPath)
+ if err == nil {
+ break
+ }
+ logDebug("connect failed: %v, retrying...", err)
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-time.After(1 * time.Second):
+ }
+ }
+ defer conn.Close()
+
+ logInfo("watcher: connected, monitoring for changes ...")
+
+ scanner := bufio.NewScanner(conn)
+ for scanner.Scan() {
+ var evt FileEvent
+ if err := json.Unmarshal(scanner.Bytes(), &evt); err != nil {
+ logDebug("bad event: %v", err)
+ continue
+ }
+
+ logDebug("event: %s %s", evt.Op, evt.Path)
+
+ if isGitDir(evt.Path) {
+ continue
+ }
+
+ if stack.IsIgnored(evt.Path) {
+ continue
+ }
+
+ destPath := filepath.Join(*dataDir, evt.Path)
+ srcPath := filepath.Join(*hostDir, evt.Path)
+
+ switch evt.Op {
+ case "create":
+ syncCreate(srcPath, destPath, evt.Path, manifest, stack, *dryRun, nil)
+ case "write":
+ syncWrite(srcPath, destPath, evt.Path, manifest, *dryRun)
+ case "remove":
+ syncRemove(destPath, evt.Path, manifest, *dryRun)
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return err
+ }
+
+ return nil
+}