191 lines
7.0 KiB
Python
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()
|