113 lines
4.0 KiB
Python
113 lines
4.0 KiB
Python
|
import time
|
||
|
import logging
|
||
|
import argparse
|
||
|
import sysrsync
|
||
|
from watchdog.observers import Observer
|
||
|
from watchdog.events import FileSystemEventHandler
|
||
|
|
||
|
# Setup logging
|
||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||
|
logger = logging.getLogger()
|
||
|
|
||
|
class SyncHandler(FileSystemEventHandler):
|
||
|
def __init__(self, local_dir, remote_dir, remote_user, remote_host, exclude_dirs, sync_interval, jump_host=None):
|
||
|
self.local_dir = local_dir
|
||
|
self.remote_dir = remote_dir
|
||
|
self.remote_user = remote_user
|
||
|
self.remote_host = remote_host
|
||
|
self.exclude_dirs = exclude_dirs
|
||
|
self.jump_host = jump_host
|
||
|
self.sync_interval = sync_interval
|
||
|
self.observer = Observer()
|
||
|
|
||
|
# Initial sync
|
||
|
logger.info("Initial sync in both directions")
|
||
|
self.sync_local_to_remote()
|
||
|
self.sync_remote_to_local()
|
||
|
|
||
|
self.observer.schedule(self, path=self.local_dir, recursive=True)
|
||
|
self.observer.start()
|
||
|
|
||
|
def on_any_event(self, event):
|
||
|
if any(excluded in event.src_path for excluded in self.exclude_dirs):
|
||
|
return
|
||
|
logger.info(f"Local change detected: {event.src_path} - {event.event_type}")
|
||
|
self.sync_local_to_remote()
|
||
|
|
||
|
def sync_local_to_remote(self):
|
||
|
ssh_command = f"ssh -J {self.jump_host}" if self.jump_host else "ssh"
|
||
|
try:
|
||
|
sysrsync.run(
|
||
|
source=self.local_dir,
|
||
|
destination=f"{self.remote_user}@{self.remote_host}:{self.remote_dir}",
|
||
|
options=[
|
||
|
"--archive",
|
||
|
"--compress",
|
||
|
f"-e {ssh_command}"
|
||
|
],
|
||
|
exclusions=self.exclude_dirs
|
||
|
)
|
||
|
logger.info("Local => Remote complete")
|
||
|
except Exception as e:
|
||
|
logger.error(f"Error during local to remote sync: {e}")
|
||
|
|
||
|
def sync_remote_to_local(self):
|
||
|
ssh_command = f"ssh -J {self.jump_host}" if self.jump_host else "ssh"
|
||
|
try:
|
||
|
sysrsync.run(
|
||
|
source=f"{self.remote_user}@{self.remote_host}:{self.remote_dir}",
|
||
|
destination=self.local_dir,
|
||
|
options=[
|
||
|
"--archive",
|
||
|
"--compress",
|
||
|
f"-e {ssh_command}"
|
||
|
],
|
||
|
exclusions=self.exclude_dirs
|
||
|
)
|
||
|
logger.info("Local <= Remote complete")
|
||
|
except Exception as e:
|
||
|
logger.error(f"Error during remote to local sync: {e}")
|
||
|
|
||
|
def start(self):
|
||
|
count = 0
|
||
|
try:
|
||
|
while True:
|
||
|
time.sleep(self.sync_interval)
|
||
|
self.sync_remote_to_local()
|
||
|
if count % 10 == 0:
|
||
|
self.sync_local_to_remote()
|
||
|
count += 1
|
||
|
except KeyboardInterrupt:
|
||
|
self.observer.stop()
|
||
|
self.observer.join()
|
||
|
|
||
|
def main():
|
||
|
parser = argparse.ArgumentParser(description="Two-way auto sync script using rsync")
|
||
|
parser.add_argument("--local-dir", required=True, help="Local directory to sync")
|
||
|
parser.add_argument("--remote-dir", required=True, help="Remote directory to sync")
|
||
|
parser.add_argument("--remote-user", default="root", help="Remote user")
|
||
|
parser.add_argument("--remote-host", required=True, help="Remote host")
|
||
|
parser.add_argument("--sync-interval", type=int, default=5, help="Sync interval in seconds")
|
||
|
parser.add_argument("--jump-host", help="Jump host for SSH")
|
||
|
parser.add_argument("--exclude-dirs", nargs='*', default=[".git", ".venv", "__pycache__", "node_modules", ".DS_Store"], help="Directories to exclude from sync")
|
||
|
|
||
|
args = parser.parse_args()
|
||
|
|
||
|
sync_handler = SyncHandler(
|
||
|
local_dir=args.local_dir,
|
||
|
remote_dir=args.remote_dir,
|
||
|
remote_user=args.remote_user,
|
||
|
remote_host=args.remote_host,
|
||
|
exclude_dirs=args.exclude_dirs,
|
||
|
sync_interval=args.sync_interval,
|
||
|
jump_host=args.jump_host
|
||
|
)
|
||
|
|
||
|
# Start the sync handler
|
||
|
sync_handler.start()
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|
||
|
|
||
|
|