package cli import ( "flag" "fmt" "io" "os" "strings" "syscall" ) type RoutesUpdateDeps struct { LockFile string Update func(iface string) (ok bool, message string) Stdout io.Writer Stderr io.Writer } func RunRoutesUpdate(args []string, deps RoutesUpdateDeps) int { if deps.Update == nil || deps.LockFile == "" { return 1 } stdout := deps.Stdout if stdout == nil { stdout = os.Stdout } stderr := deps.Stderr if stderr == nil { stderr = os.Stderr } fs := flag.NewFlagSet("routes-update", flag.ContinueOnError) fs.SetOutput(stderr) iface := fs.String("iface", "", "VPN interface (empty/auto = detect active)") if err := fs.Parse(args); err != nil { return 2 } lock, err := os.OpenFile(deps.LockFile, os.O_CREATE|os.O_RDWR, 0o644) if err != nil { fmt.Fprintf(stderr, "lock open error: %v\n", err) return 1 } defer lock.Close() if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil { fmt.Fprintln(stdout, "routes update already running") return 0 } defer func() { _ = syscall.Flock(int(lock.Fd()), syscall.LOCK_UN) }() ok, message := deps.Update(*iface) if ok { fmt.Fprintln(stdout, strings.TrimSpace(message)) return 0 } msg := strings.TrimSpace(message) if msg == "" { msg = "routes update failed" } fmt.Fprintln(stderr, msg) return 1 }