#!/usr/bin/env python3 """Read-only public DNS and IPv4/IPv6 HTTPS checks for punchblog.com. Requires Python 3, curl and dig. Does not change DNS, use account credentials, retry requests or disable TLS verification. Existing output is preserved. This checks the current state; it cannot recreate the historical DNS change. """ import argparse from datetime import datetime import json from pathlib import Path import platform import subprocess from zoneinfo import ZoneInfo def main(): parser = argparse.ArgumentParser() parser.add_argument('--output', required=True) args = parser.parse_args() output = Path(args.output) if output.exists(): raise SystemExit('Choose a new output path; existing observations are preserved.') dns = {} for record_type in ('NS', 'A', 'AAAA'): result = subprocess.run(['dig', '+time=3', '+tries=1', '+short', 'punchblog.com', record_type], capture_output=True, text=True, timeout=5) dns[record_type] = {'exit': result.returncode, 'answers': sorted(result.stdout.splitlines())} checks = [] for family in (4, 6): for route in ('/', '/privacy/'): url = 'https://punchblog.com' + route result = subprocess.run( ['curl', '-' + str(family), '-sS', '--connect-timeout', '3', '--max-time', '10', '-o', '/dev/null', '-w', '%{http_code} %{remote_ip} %{ssl_verify_result}', url], capture_output=True, text=True, timeout=12) values = result.stdout.strip().split() status = int(values[0]) if values and values[0].isdigit() else None checks.append({'label': 'IPv' + str(family) + ' ' + route, 'url': url, 'family': family, 'curl_exit': result.returncode, 'status': status, 'remote_ip': values[1] if len(values) == 3 else None, 'ssl_verify_result': values[2] if len(values) == 3 else None, 'tls_verification': 'enabled', 'method': 'GET'}) record = { 'observed_at': datetime.now(ZoneInfo('Asia/Seoul')).isoformat(), 'environment': {'os': platform.system(), 'python': platform.python_version()}, 'method': 'System resolver DNS queries; four sequential GET requests with explicit IPv4/IPv6, TLS verification enabled, no redirects or retries, response bodies discarded.', 'scope': 'One server and one observation. An IPv6 failure may also mean the client has no IPv6 connectivity. This does not establish historical failures, global availability, Google crawler access or an AdSense rejection cause.', 'dns': dns, 'checks': checks, } output.parent.mkdir(parents=True, exist_ok=True) with output.open('x') as stream: json.dump(record, stream, ensure_ascii=False, indent=2) stream.write('\n') print(json.dumps({'output': str(output), 'checks': [{'label': x['label'], 'status': x['status'], 'curl_exit': x['curl_exit']} for x in checks]})) if __name__ == '__main__': main()