"""Switch tunnel origin from http://172.16.3.10:80 to https://172.16.3.10:443. Each ingress gets originRequest.originServerName= so IX's Apache serves the right vhost cert via SNI. noTLSVerify=true to tolerate cPanel's self-signed or hostname-mismatch quirks (cloudflared still uses TLS). """ import socket import paramiko HOST, USER = "172.16.3.20", "root" import subprocess as _sp, yaml as _y PWD = _y.safe_load(_sp.run(["sops","-d","D:/vault/infrastructure/jupiter-unraid-primary.sops.yaml"],capture_output=True,text=True,timeout=30,check=True).stdout)["credentials"]["password"] APPDATA = '/mnt/cache/appdata/cloudflared' HOSTNAMES = ['azcomputerguru.com','analytics.azcomputerguru.com','community.azcomputerguru.com','radio.azcomputerguru.com'] socket.setdefaulttimeout(60) c = paramiko.SSHClient(); c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) c.connect(HOST, username=USER, password=PWD, timeout=30, look_for_keys=False, allow_agent=False) def run(cmd, to=60): _, 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() # Read existing tunnel UUID from config out, _, _ = run(f'grep "^tunnel:" {APPDATA}/config.yml') UUID = out.split(':',1)[1].strip() print(f'tunnel UUID: {UUID}') config = f'''tunnel: {UUID} credentials-file: /home/nonroot/.cloudflared/{UUID}.json ingress: ''' for h in HOSTNAMES: config += ( f' - hostname: {h}\n' f' service: https://172.16.3.10:443\n' f' originRequest:\n' f' originServerName: {h}\n' f' noTLSVerify: true\n' ) config += ' - service: http_status:404\n' print('\n=== new config.yml ===') print(config) HEREDOC = "'EOF_CFG'" out, err, rc = run(f"cat > {APPDATA}/config.yml <<{HEREDOC}\n{config}\nEOF_CFG") run(f'chown 65532:65532 {APPDATA}/config.yml') out, _, _ = run(f'cat {APPDATA}/config.yml') print('=== written ===') print(out) print('\n=== restart cloudflared ===') out, _, _ = run('docker restart cloudflared') print(out.rstrip()) print('\n=== wait for reconnect ===') import time for i in range(15): time.sleep(3) out, _, _ = run('docker logs cloudflared 2>&1 | tail -30') conns = out.count('Registered tunnel connection') print(f' [try {i+1}] registered: {conns}') if conns >= 4: break print('\n=== external HEAD probes ===') c.close() # External test from this workstation import urllib.request, urllib.error for h in HOSTNAMES: try: req = urllib.request.Request(f'https://{h}/', method='HEAD', headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0'}) with urllib.request.urlopen(req, timeout=15) as r: server = r.headers.get('Server','-') print(f' {h}: HTTP {r.status} Server={server}') except urllib.error.HTTPError as e: print(f' {h}: HTTP {e.code}') except Exception as e: print(f' {h}: ERR {e}')