"""SFTP upload-delta.js + delta_to_upload.txt to AD2, then run via SSH with creds in env vars.""" import paramiko, subprocess, sys, time, yaml, os LIMIT = 0 # 0 = all BATCH = 100 DRY = False START = 0 # Allow CLI overrides for i, a in enumerate(sys.argv[1:]): if a == '--limit': LIMIT = int(sys.argv[i+2]) if a == '--batch': BATCH = int(sys.argv[i+2]) if a == '--start': START = int(sys.argv[i+2]) if a == '--dry-run': DRY = True ad2_pwd = yaml.safe_load(subprocess.run(['sops','-d','D:/vault/clients/dataforth/ad2.sops.yaml'], capture_output=True, text=True, timeout=30, check=True).stdout)['credentials']['password'].replace('\\','') api = yaml.safe_load(subprocess.run(['sops','-d','D:/vault/clients/dataforth/api-oauth.sops.yaml'], capture_output=True, text=True, timeout=30, check=True).stdout) REMOTE_DIR = 'C:/Users/sysadmin/Documents/dataforth-uploader' LOCAL_DELTA = r'C:\Users\guru\AppData\Local\Temp\delta_to_upload.txt' LOCAL_JS = r'D:\claudetools\projects\dataforth-dos\datasheet-pipeline\upload-delta.js' c = paramiko.SSHClient(); c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) c.connect('192.168.0.6', username='sysadmin', password=ad2_pwd, timeout=30, banner_timeout=45, look_for_keys=False, allow_agent=False) def run(cmd, to=120): _, o, e = c.exec_command(cmd, timeout=to) return o.read().decode('utf-8','replace'), e.read().decode('utf-8','replace'), o.channel.recv_exit_status() print('[1] mkdir + sftp upload') run(f'powershell -Command "New-Item -ItemType Directory -Force -Path \\"{REMOTE_DIR}\\" | Out-Null"') sftp = c.open_sftp() sftp.put(LOCAL_JS, f'{REMOTE_DIR}/upload-delta.js') sftp.put(LOCAL_DELTA, f'{REMOTE_DIR}/delta_to_upload.txt') sftp.close() out, _, _ = run(f'powershell -Command "Get-ChildItem \\"{REMOTE_DIR}\\" | Select Name,Length | Format-Table -AutoSize | Out-String"') print(out.rstrip()) print('\n[2] run uploader on AD2 (env-var creds)') flags = [] if LIMIT: flags += ['--limit', str(LIMIT)] if BATCH: flags += ['--batch', str(BATCH)] if START: flags += ['--start', str(START)] if DRY: flags += ['--dry-run'] flag_str = ' '.join(flags) # Build powershell command that sets env vars then runs node ps_cmd = ( f'$env:CF_TOKEN_URL = "{api["endpoints"]["token-url"]}"; ' f'$env:CF_API_BASE = "{api["endpoints"]["api-base"]}"; ' f'$env:CF_CLIENT_ID = "{api["credentials"]["client-id"]}"; ' f'$env:CF_CLIENT_SECRET = "{api["credentials"]["client-secret"]}"; ' f'$env:CF_SCOPE = "{api["credentials"]["scope"]}"; ' f'cd "{REMOTE_DIR}"; ' f'& node upload-delta.js {flag_str} 2>&1' ) import base64 enc = base64.b64encode(ps_cmd.encode('utf-16-le')).decode() stdin, stdout, stderr = c.exec_command(f'powershell -NoProfile -EncodedCommand {enc}', timeout=7200, get_pty=False) import threading def reader(stream, label): try: for line in iter(lambda: stream.readline(), ''): if not line: break print(line.rstrip(), flush=True) except Exception as e: print(f'[{label} reader err] {e}') t = threading.Thread(target=reader, args=(stdout,'out'), daemon=True); t.start() err_t = threading.Thread(target=reader, args=(stderr,'err'), daemon=True); err_t.start() t0 = time.time() while time.time() - t0 < 7200: if stdout.channel.exit_status_ready(): break time.sleep(2) t.join(timeout=5) err_t.join(timeout=5) rc = stdout.channel.recv_exit_status() if stdout.channel.exit_status_ready() else -1 print(f'\n[exit {rc}]') c.close()