esync

Directory watching and remote syncing
Log | Files | Refs | README | LICENSE

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)