This commit is contained in:
vvzvlad 2024-08-05 16:24:37 +03:00
commit 8fd8b08fe2
3 changed files with 202 additions and 0 deletions

13
Pipfile Normal file
View File

@ -0,0 +1,13 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
sysrsync = "*"
watchdog = "*"
[dev-packages]
[requires]
python_version = "3.11"

77
Pipfile.lock generated Normal file
View File

@ -0,0 +1,77 @@
{
"_meta": {
"hash": {
"sha256": "9e22bfb09a5c4ca9d55498299d1b5eebe2d649be0adc7e90821d3c4a9195c038"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.11"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"sysrsync": {
"hashes": [
"sha256:435f9eb620e68ffb18ca5cbad32b113396a432361c7722038eab65c97dd83bd5",
"sha256:9c8877f162dce9c480804445fca56cd19bf41b998d2172651468ccebfdc60850"
],
"index": "pypi",
"markers": "python_version >= '3.6' and python_version < '4.0'",
"version": "==1.1.1"
},
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
"version": "==0.10.2"
},
"watchdog": {
"hashes": [
"sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7",
"sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767",
"sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175",
"sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459",
"sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5",
"sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429",
"sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6",
"sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d",
"sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7",
"sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28",
"sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235",
"sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57",
"sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a",
"sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5",
"sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709",
"sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee",
"sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84",
"sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd",
"sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba",
"sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db",
"sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682",
"sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35",
"sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d",
"sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645",
"sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253",
"sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193",
"sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b",
"sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44",
"sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b",
"sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625",
"sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e",
"sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==4.0.1"
}
},
"develop": {}
}

112
syncer.py Normal file
View File

@ -0,0 +1,112 @@
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()