Files

191 lines
7.0 KiB
Python

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()