Skip to content

Commit cbf5a54

Browse files
committed
fix: refresh sync state on session restart
1 parent 8b90fde commit cbf5a54

6 files changed

Lines changed: 92 additions & 12 deletions

File tree

docs/command-reference.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
## Commands
1414

1515
- `okdev version`
16-
- `okdev init [--template basic|gpu|llm-stack] [--force]`
16+
- `okdev init [--template basic|gpu|llm-stack] [--stignore-preset default|python|node|go|rust] [--force]`
1717
- `okdev validate`
1818
- `okdev up [--wait-timeout 10m] [--dry-run]`
1919
- `okdev down [--delete-pvc] [--dry-run]`
@@ -23,21 +23,23 @@
2323
- `okdev connect [--shell /bin/bash] [--cmd "..."] [--no-tty]`
2424
- `okdev ssh [--setup-key] [--user root] [--cmd "..."] [--no-tmux]`
2525
- `okdev ports`
26-
- `okdev sync [--mode up|down|bi] [--background] [--reset] [--dry-run]`
26+
- `okdev sync [--mode up|down|bi] [--foreground] [--reset] [--dry-run]`
2727
- `okdev prune [--ttl-hours 72] [--all-namespaces] [--all-users] [--include-pvc] [--dry-run]`
2828

29-
### `okdev init [--template basic|gpu|llm-stack] [--force]`
29+
### `okdev init [--template basic|gpu|llm-stack] [--stignore-preset default|python|node|go|rust] [--force]`
3030

3131
- Writes a starter `.okdev.yaml`.
3232
- For built-in templates, it also writes a starter local `.stignore` file for the initialized sync root.
33+
- `--stignore-preset`: override the starter `.stignore` patterns with a project-oriented preset.
34+
- When `--stignore-preset` is omitted, `okdev init` tries to detect a preset from common repo markers like `go.mod`, `package.json`, `Cargo.toml`, and `pyproject.toml`.
3335

3436
### `okdev up [--wait-timeout 10m] [--dry-run]`
3537

3638
- Reconciles Pod/PVC resources, updates SSH config, initializes managed forwarding/sync, then exits.
3739
- tmux-backed persistent interactive shells are enabled by default.
3840
- `--tmux`: explicitly enable tmux mode in the dev container.
3941
- `--no-tmux`: disable tmux mode for this pod.
40-
- When `sync.engine=syncthing`, `okdev up` starts background sync in bidirectional mode by default.
42+
- When `sync.engine=syncthing`, `okdev up` refreshes the session's local Syncthing processes and starts background sync in bidirectional mode by default.
4143
- `spec.ports` is materialized as SSH `LocalForward`.
4244

4345
### `okdev ssh [--setup-key] [--user root] [--cmd "..."] [--no-tmux]`
@@ -51,9 +53,10 @@
5153
- Advanced/recovery command. Rebuilds managed SSH `LocalForward` state from `spec.ports` after disconnects or local port changes.
5254
- No-op when managed forwards are already healthy and config is unchanged.
5355

54-
### `okdev sync [--mode up|down|bi] [--background] [--reset] [--dry-run]`
56+
### `okdev sync [--mode up|down|bi] [--foreground] [--reset] [--dry-run]`
5557

56-
- Advanced command. Use for foreground sync debugging, or explicit one-way sync (`up`/`down`).
58+
- Advanced command. Starts detached background sync by default; use `--foreground` for sync debugging, or explicit one-way sync (`up`/`down`).
5759
- For default `--mode bi`, no-op when background sync is already active for the session.
60+
- `--background`: explicitly request detached background mode.
5861
- `--reset`: stop the session's existing local sync processes and local Syncthing state, then bootstrap sync again.
59-
- `okdev init` writes the starter config and, when `spec.sync.exclude` is populated, a local `.stignore` file for the initialized sync root.
62+
- `okdev init` writes the starter config and, for built-in templates, a starter local `.stignore` file for the initialized sync root.

docs/quickstart.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ go build -o bin/okdev ./cmd/okdev
2727
okdev init
2828
# GPU/LLM-focused scaffold
2929
okdev init --template gpu
30+
# Go project with a Go-oriented local sync ignore preset
31+
okdev init --template basic --stignore-preset go
3032
```
3133

3234
This generates `.okdev.yaml` in the current directory. See [Config Manifest](config-manifest.md) for the full field reference and examples.
3335

34-
For built-in templates, `okdev init` also writes a starter local `.stignore` file for the initialized sync root.
36+
For built-in templates, `okdev init` also writes a starter local `.stignore` file for the initialized sync root. Use `--stignore-preset` to override that starter with a project-oriented preset like `python`, `node`, `go`, or `rust`.
37+
When `--stignore-preset` is omitted, `okdev init` will try to detect a preset from common repo markers like `go.mod`, `package.json`, `Cargo.toml`, or `pyproject.toml`.
3538

3639
okdev discovers configuration in this order:
3740

@@ -111,11 +114,11 @@ Use `okdev ssh` when you want the tmux-backed interactive shell managed by okdev
111114
`okdev up` starts bidirectional Syncthing sync automatically. For manual control:
112115

113116
```bash
114-
# Foreground sync (useful for troubleshooting)
117+
# Detached background sync (default)
115118
okdev sync
116119

117-
# Detached background sync
118-
okdev sync --background
120+
# Foreground sync (useful for troubleshooting)
121+
okdev sync --foreground
119122

120123
# One-way sync (local → remote or remote → local)
121124
okdev sync --mode up

internal/cli/sync.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
func newSyncCmd(opts *Options) *cobra.Command {
2020
var mode string
2121
var background bool
22+
var foreground bool
2223
var dryRun bool
2324
var reset bool
2425

@@ -49,6 +50,12 @@ func newSyncCmd(opts *Options) *cobra.Command {
4950
if engine != "syncthing" {
5051
return fmt.Errorf("unsupported sync engine %q (only syncthing is supported)", engine)
5152
}
53+
if foreground && cmd.Flags().Changed("background") {
54+
return fmt.Errorf("--background and --foreground cannot be used together")
55+
}
56+
if foreground {
57+
background = false
58+
}
5259
pairs, err := syncengine.ParsePairs(cfg.Spec.Sync.Paths, cfg.WorkspaceMountPath())
5360
if err != nil {
5461
return err
@@ -101,7 +108,8 @@ func newSyncCmd(opts *Options) *cobra.Command {
101108
}
102109

103110
cmd.Flags().StringVar(&mode, "mode", "bi", "Sync mode: up|down|bi")
104-
cmd.Flags().BoolVar(&background, "background", false, "Run syncthing sync as a detached background process")
111+
cmd.Flags().BoolVar(&background, "background", true, "Run syncthing sync as a detached background process")
112+
cmd.Flags().BoolVar(&foreground, "foreground", false, "Run syncthing sync in the foreground for troubleshooting")
105113
cmd.Flags().BoolVar(&reset, "reset", false, "Stop existing local sync state for this session and bootstrap again")
106114
cmd.Flags().BoolVar(&dryRun, "dry-run", false, "Preview sync actions without transferring files")
107115
return cmd

internal/cli/sync_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ import (
66
"testing"
77
)
88

9+
func TestNewSyncCmdDefaultsToBackground(t *testing.T) {
10+
cmd := newSyncCmd(&Options{})
11+
flag := cmd.Flags().Lookup("background")
12+
if flag == nil {
13+
t.Fatal("expected background flag")
14+
}
15+
if flag.DefValue != "true" {
16+
t.Fatalf("expected background default true, got %q", flag.DefValue)
17+
}
18+
if cmd.Flags().Lookup("foreground") == nil {
19+
t.Fatal("expected foreground flag")
20+
}
21+
}
22+
923
func TestResetSyncthingSessionStateRemovesLocalState(t *testing.T) {
1024
home := t.TempDir()
1125
origHome := os.Getenv("HOME")
@@ -42,3 +56,42 @@ func TestResetSyncthingSessionStateRemovesLocalState(t *testing.T) {
4256
t.Fatalf("expected session home to remain a directory")
4357
}
4458
}
59+
60+
func TestRefreshSyncthingSessionProcessesPreservesLocalState(t *testing.T) {
61+
home := t.TempDir()
62+
origHome := os.Getenv("HOME")
63+
if err := os.Setenv("HOME", home); err != nil {
64+
t.Fatalf("set HOME: %v", err)
65+
}
66+
defer func() {
67+
_ = os.Setenv("HOME", origHome)
68+
}()
69+
70+
sessionName := "test-sync-refresh"
71+
sessionHome, err := localSyncthingHome(sessionName)
72+
if err != nil {
73+
t.Fatalf("localSyncthingHome: %v", err)
74+
}
75+
stateFile := filepath.Join(sessionHome, "config.xml")
76+
if err := os.WriteFile(stateFile, []byte("<config/>"), 0o644); err != nil {
77+
t.Fatalf("write state file: %v", err)
78+
}
79+
pidPath, err := syncthingPIDPath(sessionName)
80+
if err != nil {
81+
t.Fatalf("syncthingPIDPath: %v", err)
82+
}
83+
if err := os.WriteFile(pidPath, []byte("999999\n"), 0o644); err != nil {
84+
t.Fatalf("write pid file: %v", err)
85+
}
86+
87+
if err := refreshSyncthingSessionProcesses(sessionName); err != nil {
88+
t.Fatalf("refreshSyncthingSessionProcesses: %v", err)
89+
}
90+
91+
if _, err := os.Stat(stateFile); err != nil {
92+
t.Fatalf("expected state file to remain after refresh: %v", err)
93+
}
94+
if _, err := os.Stat(pidPath); !os.IsNotExist(err) {
95+
t.Fatalf("expected stale pid file to be removed, got err=%v", err)
96+
}
97+
}

internal/cli/syncthing.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,16 @@ func resetSyncthingSessionState(sessionName string) error {
294294
return nil
295295
}
296296

297+
func refreshSyncthingSessionProcesses(sessionName string) error {
298+
if err := stopDetachedSyncthingSync(sessionName); err != nil {
299+
return fmt.Errorf("stop background sync: %w", err)
300+
}
301+
if err := stopLocalSyncthingForSession(sessionName); err != nil {
302+
return fmt.Errorf("stop local syncthing: %w", err)
303+
}
304+
return nil
305+
}
306+
297307
func startSyncthingPortForward(opts *Options, namespace, pod string) (context.CancelFunc, string, string, error) {
298308
lastErr := error(nil)
299309
for i := 0; i < 5; i++ {

internal/cli/up.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ func newUpCmd(opts *Options) *cobra.Command {
196196
syncSummary := "disabled"
197197
syncModeSymbol := ""
198198
if cfg.Spec.Sync.Engine == "" || cfg.Spec.Sync.Engine == "syncthing" {
199+
if err := refreshSyncthingSessionProcesses(sn); err != nil {
200+
return fmt.Errorf("refresh local syncthing session state: %w", err)
201+
}
199202
logPath, started, err := startDetachedSyncthingSync(opts, upDefaultSyncMode, sn, ns, cfgPath)
200203
if err != nil {
201204
return fmt.Errorf("start syncthing background sync: %w", err)

0 commit comments

Comments
 (0)