import json, os, shutil, stat, subprocess, tempfile, time base = '/www/backup/txiaw-mysql-fix-20260831' target = '/www/server/panel/vhost/nginx/www.txiaw.com.conf' original = base + '/originals/original-www.txiaw.com.conf' nginx = '/www/server/nginx/sbin/nginx' stamp = time.strftime('%Y%m%d-%H%M%S') snapshot = base + '/www.txiaw.com-before-rate-limit-rollback-' + stamp + '.conf' includes = [ '/www/server/panel/vhost/nginx/txiaw-home-protection-http.inc', '/www/server/panel/vhost/nginx/txiaw-home-protection-server.inc', ] def run(args): return subprocess.check_output(args, stderr=subprocess.STDOUT, timeout=20).decode('utf-8', 'replace') def atomic_write(data, dst): meta = os.stat(dst) fd, tmp = tempfile.mkstemp(prefix='.txiaw-rollback-', dir=os.path.dirname(dst)) try: with os.fdopen(fd, 'wb') as out: out.write(data) out.flush(); os.fsync(out.fileno()) os.chmod(tmp, stat.S_IMODE(meta.st_mode)); os.chown(tmp, meta.st_uid, meta.st_gid) os.replace(tmp, dst) finally: if os.path.exists(tmp): os.unlink(tmp) before = open(target, 'rb').read() after = b''.join(line for line in before.splitlines(True) if line.strip() not in [ ('include ' + p + ';').encode('ascii') for p in includes ]) if before == after: print(json.dumps({'status':'nginx_rate_limit_includes_already_absent'}), flush=True) raise SystemExit(0) shutil.copy2(target, snapshot) os.chmod(snapshot, 0o600) try: atomic_write(after, target) test = run([nginx, '-t']) run([nginx, '-s', 'reload']) except Exception: atomic_write(before, target) run([nginx, '-t']) raise result = { 'status': 'nginx_rate_limit_rolled_back', 'snapshot': snapshot, 'nginx_test_ok': 'successful' in test.lower(), 'orphan_include_files_retained_but_not_loaded': [p for p in includes if os.path.isfile(p)], } json.dump(result, open(base + '/rate-limit-rollback.json', 'w'), indent=2) print(json.dumps(result), flush=True)