2026-03-01-go-rewrite-design.md (8772B)
1 # esync Go Rewrite — Design Document 2 3 Date: 2026-03-01 4 5 ## Motivation 6 7 Rewrite esync from Python to Go for three equal priorities: 8 9 1. **Single binary distribution** — no Python/pip dependency; download and run 10 2. **Performance** — faster startup, lower memory, better for long-running watch processes 11 3. **Better TUI** — polished interactive dashboard using Bubbletea/Lipgloss 12 13 ## Technology Stack 14 15 | Component | Library | Purpose | 16 |-----------|---------|---------| 17 | CLI framework | [Cobra](https://github.com/spf13/cobra) | Subcommands, flags, help generation | 18 | Configuration | [Viper](https://github.com/spf13/viper) | TOML loading, config file search path, env vars | 19 | TUI framework | [Bubbletea](https://github.com/charmbracelet/bubbletea) | Interactive terminal UI | 20 | TUI styling | [Lipgloss](https://github.com/charmbracelet/lipgloss) | Borders, colors, layout | 21 | File watching | [fsnotify](https://github.com/fsnotify/fsnotify) | Cross-platform filesystem events | 22 | Sync engine | rsync (external) | File transfer via subprocess | 23 24 ## Project Structure 25 26 ``` 27 esync/ 28 ├── cmd/ 29 │ ├── root.go # Root command, global flags 30 │ ├── sync.go # esync sync — main watch+sync command 31 │ ├── init.go # esync init — smart config generation 32 │ ├── check.go # esync check — validate config + preview 33 │ ├── edit.go # esync edit — open in $EDITOR + preview 34 │ └── status.go # esync status — check running daemon 35 ├── internal/ 36 │ ├── config/ # TOML config models, loading, validation 37 │ ├── watcher/ # fsnotify wrapper with debouncing 38 │ ├── syncer/ # rsync command builder and executor 39 │ ├── tui/ # Bubbletea models, views, styles 40 │ │ ├── app.go # Root Bubbletea model 41 │ │ ├── dashboard.go # Main dashboard view 42 │ │ ├── logview.go # Scrollable log view 43 │ │ └── styles.go # Lipgloss style definitions 44 │ └── logger/ # Structured logging (file + JSON) 45 ├── main.go # Entry point 46 ├── esync.toml # Example config 47 └── go.mod 48 ``` 49 50 ## CLI Commands 51 52 ``` 53 esync sync [flags] Start watching and syncing 54 -c, --config PATH Config file path 55 -l, --local PATH Override local path 56 -r, --remote PATH Override remote path 57 --daemon Run without TUI, log to file 58 --dry-run Show what would sync without executing 59 --initial-sync Force full sync on startup 60 -v, --verbose Verbose output 61 62 esync init [flags] Generate config from current directory 63 -c, --config PATH Output path (default: ./esync.toml) 64 -r, --remote PATH Pre-fill remote destination 65 66 esync check [flags] Validate config and show file include/exclude preview 67 -c, --config PATH Config file path 68 69 esync edit [flags] Open config in $EDITOR, then show preview 70 -c, --config PATH Config file path 71 72 esync status Check if daemon is running, show last sync info 73 ``` 74 75 ### Quick usage (no config file) 76 77 `esync sync -l ./src -r user@host:/deploy` works without a config file when both local and remote are provided as flags. 78 79 ## Configuration 80 81 ### Format 82 83 TOML. Search order: 84 85 1. `-c` / `--config` flag 86 2. `./esync.toml` 87 3. `~/.config/esync/config.toml` 88 4. `/etc/esync/config.toml` 89 90 ### Schema 91 92 ```toml 93 [sync] 94 local = "./src" 95 remote = "user@host:/deploy/src" 96 interval = 1 # debounce interval in seconds 97 98 [sync.ssh] 99 host = "example.com" 100 user = "deploy" 101 port = 22 102 identity_file = "~/.ssh/id_ed25519" 103 interactive_auth = true # for 2FA prompts 104 105 [settings] 106 watcher_debounce = 500 # ms, batch rapid changes 107 initial_sync = true # full rsync on startup 108 ignore = ["*.log", "*.tmp", ".env"] 109 110 [settings.rsync] 111 archive = true 112 compress = true 113 backup = true 114 backup_dir = ".rsync_backup" 115 progress = true 116 extra_args = ["--delete"] # pass-through for any rsync flags 117 ignore = [".git/", "node_modules/", "**/__pycache__/"] 118 119 [settings.log] 120 file = "~/.local/share/esync/esync.log" 121 format = "json" # "json" or "text" 122 ``` 123 124 ### Smart init 125 126 `esync init` inspects the current directory: 127 128 - Detects `.gitignore` and imports patterns into `settings.rsync.ignore` 129 - Auto-excludes common directories (`.git/`, `node_modules/`, `__pycache__/`, `build/`, `.venv/`) 130 - Pre-fills `sync.local` with `.` 131 - Accepts `-r` flag or prompts for remote destination 132 - Shows `esync check` preview after generating 133 134 ## TUI Design 135 136 ### Main dashboard view 137 138 ``` 139 esync ───────────────────────────────────────── 140 ./src → user@host:/deploy/src 141 ● Watching (synced 3s ago) 142 143 Recent ────────────────────────────────────── 144 ✓ src/main.go 2.1KB 0.3s 145 ✓ src/config.go 1.4KB 0.2s 146 ⟳ src/handler.go syncing... 147 ✓ src/util.go 890B 0.1s 148 149 Stats ─────────────────────────────────────── 150 142 synced │ 3.2MB total │ 0 errors 151 152 q quit p pause r full resync l logs d dry-run / filter 153 ``` 154 155 ### Log view (toggle with `l`) 156 157 ``` 158 esync ─ logs ────────────────────────────────── 159 14:23:01 INF synced src/main.go (2.1KB, 0.3s) 160 14:23:03 INF synced src/config.go (1.4KB, 0.2s) 161 14:23:05 INF syncing src/handler.go... 162 14:23:06 INF synced src/handler.go (890B, 0.4s) 163 14:23:06 WRN permission denied: .env (skipped) 164 14:23:15 INF idle, watching for changes 165 166 ↑↓ scroll / filter l back q quit 167 ``` 168 169 ### Keyboard shortcuts 170 171 | Key | Action | 172 |-----|--------| 173 | `q` | Quit | 174 | `p` | Pause/resume watching | 175 | `r` | Trigger full resync | 176 | `l` | Toggle log view | 177 | `d` | Dry-run next sync | 178 | `/` | Filter file list / log entries | 179 180 ### Styling 181 182 Lipgloss with a subtle color palette: 183 - Green: success/synced 184 - Yellow: in-progress/syncing 185 - Red: errors 186 - Dim: timestamps, stats 187 188 Clean and minimal — not flashy. 189 190 ## Runtime Modes 191 192 ### Interactive (default) 193 194 `esync sync` launches the Bubbletea TUI dashboard. All events render live. 195 196 ### Daemon 197 198 `esync sync --daemon` runs without TUI: 199 - Writes JSON lines to log file 200 - Prints PID on startup 201 - Terminal bell on sync errors 202 203 Log format: 204 ```json 205 {"time":"14:23:01","level":"info","event":"synced","file":"src/main.go","size":2150,"duration_ms":300} 206 {"time":"14:23:06","level":"warn","event":"skipped","file":".env","reason":"permission denied"} 207 ``` 208 209 Check with `esync status`: 210 ``` 211 esync daemon running (PID 42351) 212 Watching: ./src → user@host:/deploy/src 213 Last sync: 3s ago (src/main.go) 214 Session: 142 files synced, 0 errors 215 ``` 216 217 ## Data Flow 218 219 ``` 220 fsnotify event 221 → debouncer (batches events over configurable window, default 500ms) 222 → syncer (builds rsync command, executes) 223 → result (parsed rsync output: files, sizes, duration, errors) 224 → TUI update OR log write 225 ``` 226 227 ## Features 228 229 ### Carried from Python version 230 - File watching with configurable ignore patterns 231 - rsync-based sync with SSH support 232 - TOML configuration with search path 233 - Archive, compress, backup options 234 - SSH authentication (key, password, interactive/2FA) 235 - CLI flag overrides for local/remote paths 236 237 ### New in Go version 238 - **Debouncing** — batch rapid file changes into single rsync call 239 - **Initial sync on start** — optional full rsync before entering watch mode 240 - **Dry-run mode** — `--dry-run` flag and `d` key in TUI 241 - **Daemon mode** — `--daemon` with JSON log output and PID tracking 242 - **`esync status`** — check running daemon state 243 - **`esync check`** — validate config and show file include/exclude preview 244 - **`esync edit`** — open config in `$EDITOR`, then show preview 245 - **Smart `esync init`** — generate config from current directory, import .gitignore 246 - **rsync extra_args** — pass-through for arbitrary rsync flags 247 - **Pause/resume** — `p` key in TUI 248 - **Scrollable log view** — `l` key with `/` filter 249 - **SSH ControlMaster** — keep SSH connections alive between syncs 250 - **Sync sound** — terminal bell on errors 251 - **File filter in TUI** — `/` to search recent events and logs 252 253 ### Dropped from Python version 254 - Watchman backend (fsnotify only) 255 - YAML dependency 256 - Dual watcher abstraction layer 257 258 ## System Requirements 259 260 - Go 1.22+ (build time only) 261 - rsync 3.x 262 - macOS / Linux (fsnotify supports both)