|
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
|
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
|
# Leaf Agent
A Go daemon that sits next to a Fossil repo and keeps it synced over NATS.
## Data Flow
``` pikchr
right
R: cylinder "repo.fossil" color white fill 0x3a3a3a width 1.2 height 0.5 rad 3px
move 0.2
arrow color white thick <->
move 0.1
A: box "Leaf Agent" color white fill 0x2a6b3a width 1.5 height 0.5 rad 5px
move 0.2
arrow color white thick "sync request" above
move 0.1
N: cylinder "NATS" color white fill 0x2a4a6b width 1.0 height 0.5 rad 5px
move 0.2
arrow color white thick
move 0.1
B: box "Bridge" color white fill 0x6b5a2a width 1.2 height 0.5 rad 5px
move 0.2
arrow color white thick "HTTP" above
move 0.1
F: box "Fossil" color white fill 0x6b2a2a width 1.2 height 0.5 rad 5px
```
## How It Works
1. Opens a `.fossil` repo — reads project/server codes from SQLite
2. Connects to NATS
3. Polls every 5s (configurable):
- Checks `unsent`/`unclustered` tables
- If new artifacts found, calls `sync.Sync()` via NATS transport
4. `SIGUSR1` triggers immediate sync
5. `SIGINT`/`SIGTERM` graceful shutdown
## Usage
```
leaf-agent --repo /path/to/repo.fossil \
--nats nats://localhost:4222 \
--poll 5s \
--push --pull
```
| Flag | Env Var | Default | Description |
|------|---------|---------|-------------|
| `--repo` | `LEAF_REPO` | (required) | Path to `.fossil` file |
| `--nats` | `LEAF_NATS_URL` | `nats://localhost:4222` | NATS server URL |
| `--poll` | — | `5s` | Poll interval |
| `--user` | `LEAF_USER` | `anonymous` | Sync user |
| `--password` | `LEAF_PASSWORD` | — | Sync password |
| `--push` | — | `true` | Push local artifacts |
| `--pull` | — | `true` | Pull remote artifacts |
| `--prefix` | — | `fossil` | NATS subject prefix |
## Concurrency
The poll goroutine is the sole executor of sync. `SyncNow()` sends on a buffered channel — it never calls sync directly. This avoids concurrent SQLite access.
## See Also
- [Bridge](wiki:Bridge) — the other half
- [Home](wiki:index) — architecture overview
|