import sys import os import argparse import socket import platform import time from pathlib import Path import httpx from colorama import init, Fore, Style # Add agent directory to sys.path sys.path.insert(0, str(Path(__file__).resolve().parent)) from agent.config import load_config, save_config, AgentConfig from agent.uploader import ChunkUploader from agent.service import AgentDaemon init(autoreset=True) def print_banner(): print(Fore.CYAN + Style.BRIGHT + """ +------------------------------------------------------------------+ | ONEVER DRIVE - WINDOWS AGENT CLI | | Enterprise Resilient Chunk Backup Engine for Windows | +------------------------------------------------------------------+ """) def cmd_register(args): print_banner() server_url = args.server.rstrip("/") code = args.code.strip().upper() hostname = socket.gethostname() os_info = f"{platform.system()} {platform.release()} (Build {platform.version()})" print(Fore.YELLOW + f"Connecting to {server_url} with registration code: {code}...") payload = { "registration_code": code, "name": args.name or hostname, "hostname": hostname, "os_info": os_info, "agent_version": "1.0.0" } try: with httpx.Client(timeout=30.0) as client: resp = client.post(f"{server_url}/api/clients/register", json=payload) if resp.status_code != 200: print(Fore.RED + f"Registration failed ({resp.status_code}): {resp.text}") sys.exit(1) data = resp.json() config = load_config() config.server_url = server_url config.client_code = data["client_code"] config.device_id = data["device_id"] config.device_token = data["device_token"] config.client_name = data["name"] save_config(config) print(Fore.GREEN + Style.BRIGHT + "\n[+] Agent registered successfully!") print(Fore.WHITE + f" Client Code : {data['client_code']}") print(Fore.WHITE + f" Device ID : {data['device_id']}") print(Fore.WHITE + f" Client Name : {data['name']}") print(Fore.CYAN + "\nYou can now start the agent daemon or run manual backups.") except Exception as ex: print(Fore.RED + f"Error connecting to server: {str(ex)}") sys.exit(1) def cmd_status(args): print_banner() config = load_config() if not config.device_id: print(Fore.YELLOW + "Agent is NOT registered yet. Run 'agent_cli.py register' first.") return print(Fore.GREEN + "[*] Agent Configuration:") print(f" Server URL : {config.server_url}") print(f" Client Code : {config.client_code}") print(f" Device ID : {config.device_id}") print(f" Client Name : {config.client_name}") print(f" Chunk Size : {config.chunk_size / (1024*1024):.1f} MB") print(Fore.CYAN + "\n[*] Testing connection to server...") try: headers = { "X-Device-Id": config.device_id, "X-Device-Token": config.device_token } with httpx.Client(base_url=config.server_url, headers=headers, timeout=10.0) as client: resp = client.get("/api/jobs/agent/assigned") if resp.status_code == 200: jobs = resp.json() print(Fore.GREEN + f" Connection OK. Assigned jobs count: {len(jobs)}") for j in jobs: print(Fore.WHITE + f" - [{j['job_code']}] {j['name']} ({j['source_path']} | {j['file_patterns']})") else: print(Fore.RED + f" Server returned {resp.status_code}: {resp.text}") except Exception as ex: print(Fore.RED + f" Connection failed: {str(ex)}") def cmd_backup(args): print_banner() filepath = Path(args.file).resolve() if not filepath.exists(): print(Fore.RED + f"File not found: {filepath}") sys.exit(1) config = load_config() if not config.device_id: print(Fore.RED + "Agent is not registered. Run registration first.") sys.exit(1) print(Fore.CYAN + f"[*] Initiating chunked backup for: {filepath.name}") print(f" File size : {filepath.stat().st_size / (1024*1024):.2f} MB") print(f" Chunk size : {config.chunk_size / (1024*1024):.1f} MB") uploader = ChunkUploader(config) start_time = time.time() def print_progress(received, total, pct): bar_len = 30 filled = int(bar_len * (pct / 100)) bar = "#" * filled + "-" * (bar_len - filled) sys.stdout.write(f"\r{Fore.YELLOW}Progress: [{bar}] {pct:.1f}% ({received}/{total} chunks)") sys.stdout.flush() try: result = uploader.upload_file(filepath, job_id=args.job, progress_callback=print_progress) elapsed = max(0.01, time.time() - start_time) mb = filepath.stat().st_size / (1024 * 1024) speed = mb / elapsed print(Fore.GREEN + Style.BRIGHT + f"\n\n[+] Backup Complete & Verified!") print(Fore.WHITE + f" Remote Path : {result.get('relative_path')}") print(Fore.WHITE + f" SHA-256 : {result.get('sha256')}") print(Fore.WHITE + f" Transfer : {mb:.2f} MB in {elapsed:.2f}s ({speed:.2f} MB/s)") except Exception as ex: print(Fore.RED + f"\n[-] Backup failed: {str(ex)}") sys.exit(1) def cmd_daemon(args): print_banner() config = load_config() daemon = AgentDaemon(config) daemon.start() print(Fore.GREEN + "[*] Agent daemon running. Press Ctrl+C to stop.") try: while True: time.sleep(1) except KeyboardInterrupt: daemon.stop() print(Fore.YELLOW + "\nAgent stopped.") def main(): parser = argparse.ArgumentParser(description="OnEver Drive Windows Agent CLI") subparsers = parser.add_subparsers(dest="command") # Register p_reg = subparsers.add_parser("register", help="Register agent with central server") p_reg.add_argument("--server", required=True, help="Server URL (e.g. http://192.168.1.100:8000)") p_reg.add_argument("--code", required=True, help="Registration code (e.g. OED-A1B2-C3D4)") p_reg.add_argument("--name", help="Custom name for this client machine") p_reg.set_defaults(func=cmd_register) # Status p_stat = subparsers.add_parser("status", help="Show current agent status and server connectivity") p_stat.set_defaults(func=cmd_status) # Backup p_bak = subparsers.add_parser("backup", help="Perform manual chunked backup of a file") p_bak.add_argument("--file", required=True, help="Path to file to back up") p_bak.add_argument("--job", type=int, help="Optional Backup Job ID") p_bak.set_defaults(func=cmd_backup) # Daemon p_daemon = subparsers.add_parser("daemon", help="Run the background worker loop in foreground") p_daemon.set_defaults(func=cmd_daemon) args = parser.parse_args() if hasattr(args, "func"): args.func(args) else: parser.print_help() if __name__ == "__main__": main()