MC-texture-gen / sync-client.py
applepie69's picture
Upload sync-client.py
042db62 verified
Raw
History Blame Contribute Delete
8.17 kB
#!/usr/bin/env python3
"""
sync-client.py — Push config changes to your MC server's HuggingFace dataset
from your local machine.
The server's background watcher will detect the changes and apply them
within 60 seconds.
Usage:
# Push a single config file
python sync-client.py push server.properties
# Push multiple files
python sync-client.py push server.properties ops.json
# Push an entire directory (e.g. plugin configs)
python sync-client.py push plugins/
# Pull all server files to local directory
python sync-client.py pull [output_dir]
# List what's in the dataset
python sync-client.py list
# Watch for changes locally and auto-push
python sync-client.py watch [directory]
Environment variables:
HF_DATASET_ID — Your HuggingFace dataset ID
HF_TOKEN — Your HuggingFace write token
"""
import os
import sys
import shutil
import tempfile
import subprocess
import time
import json
from pathlib import Path
DATASET_ID = os.environ.get("HF_DATASET_ID", "applepie69/MCP-Server")
HF_TOKEN = os.environ.get("HF_TOKEN", "")
BRANCH = os.environ.get("SYNC_BRANCH", "main")
REPO_URL = f"https://x-access-token:{HF_TOKEN}@huggingface.co/datasets/{DATASET_ID}"
# The files that are considered "config" and safe to push
CONFIG_FILES = {
"server.properties", "ops.json", "whitelist.json",
"banned-players.json", "banned-ips.json", "bukkit.yml",
"spigot.yml", "paper-global.yml", "paper-world-defaults.yml",
"commands.yml", "help.yml",
}
CONFIG_DIRS = {"plugins"}
def run(cmd, **kwargs):
"""Run a command and return the result."""
return subprocess.run(cmd, shell=True, capture_output=True, text=True, **kwargs)
def clone_repo(dest):
"""Clone the dataset repo to dest directory."""
if os.path.exists(dest):
shutil.rmtree(dest)
os.makedirs(dest, exist_ok=True)
result = run(f'git clone --depth 1 --branch {BRANCH} "{REPO_URL}" "{dest}"')
if result.returncode != 0:
# Try without branch
result = run(f'git clone --depth 1 "{REPO_URL}" "{dest}"')
if result.returncode != 0:
print(f"ERROR: Failed to clone dataset. Check HF_DATASET_ID and HF_TOKEN.")
print(f" stderr: {result.stderr}")
sys.exit(1)
# Configure git
run(f'cd "{dest}" && git config user.email "mc-sync@local" && git config user.name "MC Sync Client"')
def push_to_repo(repo_dir, message="Config update from local"):
"""Commit and push changes in repo_dir."""
run(f'cd "{repo_dir}" && git add -A')
result = run(f'cd "{repo_dir}" && git diff --cached --quiet')
if result.returncode == 0:
print("No changes to push.")
return
run(f'cd "{repo_dir}" && git commit -m "{message}"')
result = run(f'cd "{repo_dir}" && git push origin HEAD:{BRANCH}')
if result.returncode != 0:
print(f"ERROR: Push failed: {result.stderr}")
sys.exit(1)
print(f"Pushed: {message}")
def cmd_push(files):
"""Push local files to the dataset."""
if not DATASET_ID or not HF_TOKEN:
print("ERROR: Set HF_DATASET_ID and HF_TOKEN environment variables.")
sys.exit(1)
with tempfile.TemporaryDirectory() as tmp:
print(f"Cloning dataset {DATASET_ID}...")
clone_repo(tmp)
for f in files:
src = Path(f)
if not src.exists():
print(f"WARNING: {f} not found, skipping.")
continue
dst = Path(tmp) / f
if src.is_dir():
# Copy directory contents
if dst.exists():
shutil.rmtree(dst)
shutil.copytree(src, dst)
print(f" Staged directory: {f}/")
else:
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
print(f" Staged file: {f}")
push_to_repo(tmp, f"Config push — {', '.join(files)}")
print("Done! Server will pick up changes within 60 seconds.")
def cmd_pull(output_dir=None):
"""Pull all files from the dataset to a local directory."""
if not DATASET_ID or not HF_TOKEN:
print("ERROR: Set HF_DATASET_ID and HF_TOKEN environment variables.")
sys.exit(1)
out = Path(output_dir or "./mc-server-files")
out.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory() as tmp:
print(f"Cloning dataset {DATASET_ID}...")
clone_repo(tmp)
# Copy everything except .git
for item in Path(tmp).iterdir():
if item.name == ".git":
continue
dst = out / item.name
if item.is_dir():
if dst.exists():
shutil.rmtree(dst)
shutil.copytree(item, dst)
else:
shutil.copy2(item, dst)
print(f"Pulled all files to {out}/")
def cmd_list():
"""List files in the dataset."""
if not DATASET_ID or not HF_TOKEN:
print("ERROR: Set HF_DATASET_ID and HF_TOKEN environment variables.")
sys.exit(1)
with tempfile.TemporaryDirectory() as tmp:
clone_repo(tmp)
result = run(f'cd "{tmp}" && git ls-files')
if result.stdout.strip():
print("Files in dataset:")
for line in result.stdout.strip().split("\n"):
print(f" {line}")
else:
print("Dataset is empty (no tracked files).")
def cmd_watch(directory=None):
"""Watch a local directory for changes and auto-push."""
import hashlib
watch_dir = Path(directory or ".")
if not watch_dir.exists():
print(f"ERROR: Directory {watch_dir} does not exist.")
sys.exit(1)
print(f"Watching {watch_dir}/ for changes... (Ctrl+C to stop)")
file_hashes = {}
def get_hashes():
hashes = {}
for f in watch_dir.rglob("*"):
if f.is_file() and ".git" not in f.parts:
rel = f.relative_to(watch_dir)
try:
hashes[str(rel)] = hashlib.md5(f.read_bytes()).hexdigest()
except:
pass
return hashes
file_hashes = get_hashes()
while True:
time.sleep(5)
new_hashes = get_hashes()
if new_hashes != file_hashes:
changed = []
for k, v in new_hashes.items():
if file_hashes.get(k) != v:
changed.append(k)
for k in list(file_hashes.keys()):
if k not in new_hashes:
changed.append(f"{k} (deleted)")
print(f"\nChanges detected: {', '.join(changed)}")
print("Pushing to dataset...")
try:
with tempfile.TemporaryDirectory() as tmp:
clone_repo(tmp)
# Copy changed files
for f in watch_dir.rglob("*"):
if f.is_file() and ".git" not in f.parts:
rel = f.relative_to(watch_dir)
dst = Path(tmp) / rel
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(f, dst)
push_to_repo(tmp, f"Auto-push: {', '.join(changed[:3])}{'...' if len(changed) > 3 else ''}")
except Exception as e:
print(f"Push failed: {e}")
file_hashes = new_hashes
def main():
if len(sys.argv) < 2:
print(__doc__)
sys.exit(0)
cmd = sys.argv[1]
if cmd == "push":
if len(sys.argv) < 3:
print("Usage: sync-client.py push <file1> [file2] ...")
sys.exit(1)
cmd_push(sys.argv[2:])
elif cmd == "pull":
output = sys.argv[2] if len(sys.argv) > 2 else None
cmd_pull(output)
elif cmd == "list":
cmd_list()
elif cmd == "watch":
directory = sys.argv[2] if len(sys.argv) > 2 else None
cmd_watch(directory)
else:
print(f"Unknown command: {cmd}")
print("Commands: push, pull, list, watch")
sys.exit(1)
if __name__ == "__main__":
main()