From 492ed73127427aa5db85fe967f9779ac9c60b23e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 11 May 2026 22:53:29 -0700 Subject: [PATCH] device sync: serverless code sync (#38020) * serverless code sync * rm stuff * we have ls-files for this * rm * no debounce fix * fix * no del * no init * clean up * clean up * smol * fixup * rm * clean up * any change * rm arg * rm * rm --- tools/scripts/devsync.py | 93 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100755 tools/scripts/devsync.py diff --git a/tools/scripts/devsync.py b/tools/scripts/devsync.py new file mode 100755 index 000000000..98312dd54 --- /dev/null +++ b/tools/scripts/devsync.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +import argparse +import shlex +import subprocess +import threading +import time + +from watchdog.events import FileSystemEventHandler +from watchdog.observers import Observer + +from openpilot.common.basedir import BASEDIR + + +def build_rsync_cmd(args) -> list[str]: + ssh = [ + "ssh", + "-o", "ControlMaster=auto", + "-o", f"ControlPath=/tmp/devsync-{args.ip}.ctl", + "-o", "ControlPersist=10m", + "-o", "StrictHostKeyChecking=accept-new", + ] + if args.identity: + ssh += ["-i", args.identity] + + return [ + "rsync", "-az", + "--files-from=-", "--from0", + "-e", " ".join(shlex.quote(p) for p in ssh), + "--out-format=%n", + BASEDIR + "/", f"comma@{args.ip}:{args.remote}/", + ] + + +def git_tracked_files() -> bytes: + return subprocess.check_output( + ["git", "-C", BASEDIR, "ls-files", "--recurse-submodules", "-z"] + ) + + +class Handler(FileSystemEventHandler): + def __init__(self, sync_fn): + self.dirty = threading.Event() + self.sync_fn = sync_fn + + def on_any_event(self, event): + if not event.is_directory: + self.dirty.set() + + def run(self): + while True: + time.sleep(1) + if self.dirty.is_set(): + self.dirty.clear() + self.sync_fn() + + +def main(): + p = argparse.ArgumentParser() + p.add_argument("ip", help="device IP / hostname") + p.add_argument("--remote", default="/data/openpilot", help="remote path on device") + p.add_argument("-i", "--identity", default=None, help="ssh identity file") + args = p.parse_args() + + print(f"[devsync] watching {BASEDIR}") + print(f"[devsync] target comma@{args.ip}:{args.remote}") + + def run_sync(): + file_list = git_tracked_files() + cmd = build_rsync_cmd(args) + t0 = time.monotonic() + r = subprocess.run(cmd, input=file_list, capture_output=True) + dt = time.monotonic() - t0 + if r.returncode: + print(f"[devsync] ERR rc={r.returncode} in {dt:.2f}s") + return + files = [ln for ln in r.stdout.decode().splitlines() if ln.strip()] + msg = f"{len(files)} files: {', '.join(files)}" if files else "no changes" + print(f"[devsync] {dt:.2f}s ยท {msg}") + + run_sync() + + handler = Handler(run_sync) + obs = Observer() + obs.schedule(handler, BASEDIR, recursive=True) + obs.start() + handler.run() + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("\n[devsync] stopping")