#!/usr/bin/env python3 # AUTHOR: Daesorin # CREATED: 2026-06-03 20:12 +0100 # UPDATED: 2026-07-24 09:40 +0100 """ The script was modified to replace the archive flag with explicit metadata-stripping and checksum evaluation flags (`-rltz`, `--checksum`, `--no-perms`, `--omit-dir-times`) to prevent inode timestamp updates during rsync pulls, while introducing a native post-pull sanitisation function that removes iOS-injected carriage returns without altering file modification times. """ import argparse import os import subprocess import time from pathlib import Path from threading import Timer from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer # PRODUCTION CONFIGURATION local_dir = "//home/sgbv/obsidian/" remote_dest = "ish:/root/obsidian/" debounce_seconds = 2.0 # ignore workspace ui states to prevent infinite sync loops excludes = [ "--exclude=.obsidian/workspace*", "--exclude=.trash/", "--exclude=.git/" ] def build_rsync_cmd(src, dest): # use checksum and strip posix metadata mapping to prevent inode ctime destruction flags = [ "rsync", "-rltz", "--checksum", "--delete", "--no-perms", "--no-owner", "--no-group", "--omit-dir-times" ] return flags + excludes + [src, dest] def sanitize_line_endings(): # strip dos carriage returns natively while preserving file modification timestamps print("sanitising line endings...") vault_path = Path(local_dir) for file_path in vault_path.rglob("*.md"): try: content = file_path.read_bytes() if b"\r\n" in content: clean_content = content.replace(b"\r\n", b"\n") # record original timestamps before writing stat_data = file_path.stat() file_path.write_bytes(clean_content) # restore original access and modification timestamps os.utime(file_path, (stat_data.st_atime, stat_data.st_mtime)) print(f"cleaned carriage returns: {file_path.name}") except Exception as error: print(f"sanitisation error on {file_path.name}: {error}") def pull_sync(): print("pulling changes from ish to arch...") subprocess.run(build_rsync_cmd(remote_dest, local_dir)) sanitize_line_endings() print("pull complete.") def push_sync(): print("pushing changes from arch to ish...") subprocess.run(build_rsync_cmd(local_dir, remote_dest)) print("push complete.") class watchdog_push_handler(FileSystemEventHandler): def __init__(self): self.sync_timer = None def on_any_event(self, event): # ignore directory changes and workspace or hidden cache updates if event.is_directory or any(x in event.src_path for x in [".obsidian", ".trash"]): return print(f"local change detected: {event.event_type}") if self.sync_timer: self.sync_timer.cancel() self.sync_timer = Timer(debounce_seconds, push_sync) self.sync_timer.start() def watch_mode(): print(f"arch watchdog listening on {local_dir}") handler = watchdog_push_handler() observer = Observer() observer.schedule(handler, local_dir, recursive=True) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: print("\nstopping watchdog...") observer.stop() observer.join() if __name__ == "__main__": parser = argparse.ArgumentParser(description="arch sync node") parser.add_argument("mode", nargs="?", default="watch", choices=["watch", "push", "pull"]) args = parser.parse_args() if args.mode == "pull": pull_sync() elif args.mode == "push": push_sync() else: watch_mode()