#!/usr/bin/env python3 """Fixed local SSH -> VPS-001 -> VPS-003 transport. No local HTTP or direct fallback.""" import argparse import json import os from pathlib import Path import shlex import subprocess import sys import tempfile OPERATIONS = ('catalog', 'source', 'prepare', 'review', 'publish', 'status') REMOTE_COMMAND = 'cd /www/wwwroot/clashfreenode-new.com && exec runuser -u nginx -- php artisan editorial:release' def ssh_command(): options = ['-F', '/dev/null', '-T', '-o', 'StrictHostKeyChecking=yes', '-o', 'PreferredAuthentications=password', '-o', 'PubkeyAuthentication=no', '-o', 'NumberOfPasswordPrompts=1', '-o', 'ConnectTimeout=15', '-o', 'ServerAliveInterval=15', '-o', 'ServerAliveCountMax=3', '-o', 'ControlMaster=no', '-o', 'ControlPath=none', '-o', 'ClearAllForwardings=yes', '-o', 'PermitLocalCommand=no'] jump = shlex.join(['ssh', *options, '-W', '45.153.247.44:22', 'root@104.168.30.40']) return ['ssh', *options, '-o', 'ProxyCommand=' + jump, 'root@45.153.247.44', REMOTE_COMMAND] def atomic_json(path, data): path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) fd, temp = tempfile.mkstemp(prefix=path.name + '.', dir=path.parent) try: with os.fdopen(fd, 'w') as f: json.dump(data, f, ensure_ascii=False, indent=2) f.flush() os.fsync(f.fileno()) os.replace(temp, path) finally: if os.path.exists(temp): os.unlink(temp) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('operation', choices=OPERATIONS) parser.add_argument('--input', type=Path, help='JSON request, without operation; stdin if omitted') parser.add_argument('--output', type=Path, required=True, help='Private JSON result; review includes HTML, never load its online assets locally') args = parser.parse_args() raw = args.input.read_bytes() if args.input else sys.stdin.buffer.read(48 * 1024 * 1024 + 1) if len(raw) > 48 * 1024 * 1024: parser.error('Input exceeds 48 MiB') payload = json.loads(raw or b'{}') if not isinstance(payload, dict) or ('operation' in payload and payload['operation'] != args.operation): parser.error('Invalid operation payload') payload['operation'] = args.operation helper = Path(__file__).with_name('ssh_askpass.py').resolve() env = os.environ.copy() env.update(SSH_ASKPASS=str(helper), SSH_ASKPASS_REQUIRE='force', DISPLAY=':0') try: result = subprocess.run(ssh_command(), input=json.dumps(payload, ensure_ascii=False).encode(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, timeout=660) try: reply = json.loads(result.stdout) except (ValueError, UnicodeError): reply = {'ok': False, 'error': 'SSH/server response unavailable. Query status with the original release_id; no direct fallback.', 'transport_exit': result.returncode} atomic_json(args.output, reply) print(json.dumps({'ok': reply.get('ok', False), 'phase': reply.get('data', {}).get('phase'), 'result_file': str(args.output.resolve()), 'transport_exit': result.returncode})) return result.returncode if result.returncode in (0, 1, 2) else 1 except subprocess.TimeoutExpired: atomic_json(args.output, {'ok': False, 'error': 'Transport timed out; publication may have completed. Query original release_id.'}) return 1 if __name__ == '__main__': raise SystemExit(main())