"""Build delta = entire For_Web folder, upload via upload-delta.js. Server is idempotent; already-present items return Unchanged, new ones Created. Avoids the slow full server-inventory pull when we just want to drain new content. """ import base64, paramiko, subprocess, time, threading, yaml 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' DELTA_NAME = 'delta_for_web_all.txt' 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 ps_b64(cmd, to=300): enc = base64.b64encode(cmd.encode('utf-16-le')).decode() _, o, e = c.exec_command(f'powershell -NoProfile -EncodedCommand {enc}', timeout=to) return o.read().decode('utf-8','replace'), e.read().decode('utf-8','replace') print('[1] enumerate For_Web on AD2') ps = ( f'$out = "{REMOTE_DIR}/{DELTA_NAME}"; ' r'Get-ChildItem "C:\Shares\webshare\For_Web" -File -Filter *.TXT | ' r'ForEach-Object { ' r' $sn = [System.IO.Path]::GetFileNameWithoutExtension($_.Name); ' r' "$sn|$($_.FullName)|$($_.Length)|$($_.LastWriteTime.ToString("o"))" ' r'} | Set-Content -Path $out -Encoding ASCII; ' r'(Get-Content $out).Count' ) out, err = ps_b64(ps, to=180) print(f' delta entries: {out.strip()}') if err.strip() and 'CLIXML' not in err: print('[stderr]', err[:300]) print('\n[2] upload all via upload-delta.js (idempotent)') ps_upload = ( 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 --delta "{REMOTE_DIR}/{DELTA_NAME}" --batch 100 2>&1' ) enc = base64.b64encode(ps_upload.encode('utf-16-le')).decode() stdin, stdout, stderr = c.exec_command(f'powershell -NoProfile -EncodedCommand {enc}', timeout=3600) def reader(s): for line in iter(lambda: s.readline(), ''): if not line: break print(line.rstrip(), flush=True) t = threading.Thread(target=reader, args=(stdout,), daemon=True); t.start() t2 = threading.Thread(target=reader, args=(stderr,), daemon=True); t2.start() t0 = time.time() while time.time() - t0 < 3600: if stdout.channel.exit_status_ready(): break time.sleep(1) t.join(timeout=5); t2.join(timeout=5) rc = stdout.channel.recv_exit_status() if stdout.channel.exit_status_ready() else -1 print(f'\n[upload rc={rc}]') print('\n[3] post-upload server stats') import json, urllib.request, urllib.parse body = urllib.parse.urlencode({'grant_type':api['credentials']['grant-type'], 'client_id':api['credentials']['client-id'], 'client_secret':api['credentials']['client-secret'], 'scope':api['credentials']['scope']}).encode() req = urllib.request.Request(api['endpoints']['token-url'], data=body, method='POST', headers={'Content-Type':'application/x-www-form-urlencoded'}) tok = json.loads(urllib.request.urlopen(req,timeout=30).read())['access_token'] req = urllib.request.Request(f'{api["endpoints"]["api-base"]}/api/v1/TestReportDataFiles/stats', headers={'Authorization':f'Bearer {tok}'}) print(json.dumps(json.loads(urllib.request.urlopen(req,timeout=30).read()), indent=2)) c.close()