|
|
|
@ -1,6 +1,6 @@
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
|
|
|
|
VERSION = "0.2.1"
|
|
|
|
VERSION = "0.3.0"
|
|
|
|
|
|
|
|
|
|
|
|
import re
|
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
import sys
|
|
|
|
@ -360,10 +360,13 @@ def parse_rsync_action(line: str) -> str:
|
|
|
|
else f"{direction} {item_type.upper()}: {filename} ({', '.join(details)})"
|
|
|
|
else f"{direction} {item_type.upper()}: {filename} ({', '.join(details)})"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def sync() -> None:
|
|
|
|
def sync(reverse: bool = False) -> None:
|
|
|
|
"""Synchronize local project directory to all nodes, honoring per-node dirs.
|
|
|
|
"""Synchronize local project directory to all nodes.
|
|
|
|
|
|
|
|
|
|
|
|
Uses rsync with `--delete` so remote deletions track local deletions.
|
|
|
|
If reverse=True:
|
|
|
|
|
|
|
|
1) Pull files that exist on remotes but are missing locally into master
|
|
|
|
|
|
|
|
(without overwriting existing local files).
|
|
|
|
|
|
|
|
2) Then push the updated master to all nodes using --delete (to converge).
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
config, project_dir = load_config()
|
|
|
|
config, project_dir = load_config()
|
|
|
|
ignore_file = project_dir / config.get("ignore_file", ".dsyncignore")
|
|
|
|
ignore_file = project_dir / config.get("ignore_file", ".dsyncignore")
|
|
|
|
@ -377,6 +380,30 @@ def sync() -> None:
|
|
|
|
print(colored("No nodes configured", Colors.YELLOW))
|
|
|
|
print(colored("No nodes configured", Colors.YELLOW))
|
|
|
|
return
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Reverse phase: pull missing files from each node into master ---
|
|
|
|
|
|
|
|
if reverse:
|
|
|
|
|
|
|
|
print(colored("[Reverse] Pulling missing files from nodes into master...", Colors.CYAN))
|
|
|
|
|
|
|
|
for n in nodes:
|
|
|
|
|
|
|
|
node = n["name"]
|
|
|
|
|
|
|
|
rdir = n["remote_dir"] or master_dir
|
|
|
|
|
|
|
|
remote = f"{node}:{rdir}/" # trailing slash to copy dir contents
|
|
|
|
|
|
|
|
print(f"[Reverse] From {remote} -> {project_dir}/ (ignore-existing)")
|
|
|
|
|
|
|
|
# --ignore-existing ensures we only fetch files that do NOT exist locally.
|
|
|
|
|
|
|
|
pull_cmd = [
|
|
|
|
|
|
|
|
"rsync", "-avz", "--ignore-existing",
|
|
|
|
|
|
|
|
f"--exclude-from={ignore_file}",
|
|
|
|
|
|
|
|
"-e", "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10",
|
|
|
|
|
|
|
|
remote, f"{project_dir}/"
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
subprocess.run(pull_cmd, check=True, capture_output=True, text=True)
|
|
|
|
|
|
|
|
print(colored(f"[OK] Pulled missing files from {node}", Colors.GREEN))
|
|
|
|
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
|
|
|
|
print(colored(f"[X] Failed reverse-pull from {node} ({rdir}): {e}", Colors.RED))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print() # spacing
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Forward phase: push master to all nodes (same as before) ---
|
|
|
|
success_count = 0
|
|
|
|
success_count = 0
|
|
|
|
failed_nodes: List[str] = []
|
|
|
|
failed_nodes: List[str] = []
|
|
|
|
|
|
|
|
|
|
|
|
@ -655,7 +682,7 @@ Usage:
|
|
|
|
dsync clear-node-dir <node> Clear node dir (fallback to master_dir)
|
|
|
|
dsync clear-node-dir <node> Clear node dir (fallback to master_dir)
|
|
|
|
dsync list-nodes List nodes with their remote dirs
|
|
|
|
dsync list-nodes List nodes with their remote dirs
|
|
|
|
dsync status Show sync status for all nodes (with dirs)
|
|
|
|
dsync status Show sync status for all nodes (with dirs)
|
|
|
|
dsync sync Sync to all configured nodes
|
|
|
|
dsync sync [-r | --reverse] Sync; reverse pulls missing files from nodes to master first
|
|
|
|
dsync help Show this help message
|
|
|
|
dsync help Show this help message
|
|
|
|
dsync version Print dsync version
|
|
|
|
dsync version Print dsync version
|
|
|
|
|
|
|
|
|
|
|
|
@ -706,7 +733,9 @@ if __name__ == "__main__":
|
|
|
|
elif cmd == "status":
|
|
|
|
elif cmd == "status":
|
|
|
|
status()
|
|
|
|
status()
|
|
|
|
elif cmd == "sync":
|
|
|
|
elif cmd == "sync":
|
|
|
|
sync()
|
|
|
|
# Parse optional flags for sync, e.g., dsync sync -r / --reverse
|
|
|
|
|
|
|
|
reverse = any(arg in ("-r", "--reverse") for arg in sys.argv[2:])
|
|
|
|
|
|
|
|
sync(reverse=reverse)
|
|
|
|
elif cmd in ("-h", "--help", "help"):
|
|
|
|
elif cmd in ("-h", "--help", "help"):
|
|
|
|
show_help()
|
|
|
|
show_help()
|
|
|
|
elif cmd in ("-v", "--version", "version"):
|
|
|
|
elif cmd in ("-v", "--version", "version"):
|
|
|
|
|