1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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
}
|