89 lines
4.1 KiB
Python
89 lines
4.1 KiB
Python
"""Authorized fixed-source collection; password lives only in this process's memory."""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import getpass
|
|
import importlib.util
|
|
import json
|
|
import logging
|
|
import socket
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--username", required=True)
|
|
parser.add_argument("--source-ip", required=True)
|
|
args = parser.parse_args()
|
|
password = getpass.getpass("SSH password (not stored): ")
|
|
logging.disable(logging.CRITICAL)
|
|
import paramiko
|
|
original_client = paramiko.SSHClient
|
|
|
|
class DirectLanClient(original_client):
|
|
def connect(self, hostname, port=22, **kwargs):
|
|
if hostname != "192.168.110.111" or port != 22:
|
|
raise ValueError("Unexpected SSH destination")
|
|
direct = socket.create_connection((hostname, port), timeout=6, source_address=(args.source_ip, 0))
|
|
try:
|
|
return super().connect(hostname, port=port, sock=direct, **kwargs)
|
|
except Exception:
|
|
direct.close()
|
|
raise
|
|
|
|
paramiko.SSHClient = DirectLanClient
|
|
root = Path(__file__).resolve().parents[1]
|
|
destination = root / "artifacts/ai_full_verification_20261027/dify_current_source"
|
|
destination.mkdir(parents=True, exist_ok=True)
|
|
original = root / "app/artifacts/ai_concurrency/r35_root_source_acquisition"
|
|
print("AUTH_READY_FIXED_READONLY_ACTIONS", flush=True)
|
|
for line in sys.stdin:
|
|
action = line.strip()
|
|
if action == "quit":
|
|
break
|
|
operations = {"inspect", "stage", "imports", "build", "deploy", "rollback", "installed", "health", "logs-safe",
|
|
"diagnose-imports", "prepare-deploy", "deployment-status"}
|
|
if action not in {"collect", "dependencies"} | operations:
|
|
print('{"ok":false,"error_code":"UNKNOWN_ACTION"}', flush=True)
|
|
continue
|
|
module_name = "collect_sources" if action == "collect" else "collect_dependencies"
|
|
try:
|
|
if action in operations:
|
|
operation_module = "private_dify_ops_actions_20261027.py"
|
|
if action == "diagnose-imports":
|
|
operation_module = "private_dify_ops_actions_v2_20261027.py"
|
|
elif action in {"prepare-deploy", "deploy", "rollback", "deployment-status"}:
|
|
operation_module = "private_dify_rollout_actions_20261027.py"
|
|
spec = importlib.util.spec_from_file_location("private_dify_ops_actions",
|
|
root / "artifacts" / operation_module)
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = module
|
|
spec.loader.exec_module(module)
|
|
client = paramiko.SSHClient()
|
|
client.load_system_host_keys()
|
|
client.set_missing_host_key_policy(paramiko.RejectPolicy())
|
|
try:
|
|
client.connect("192.168.110.111", username=args.username, password=password,
|
|
look_for_keys=False, allow_agent=False, timeout=6, auth_timeout=6, banner_timeout=6)
|
|
summary = module.run_action(client, action, root)
|
|
finally:
|
|
client.close()
|
|
print(json.dumps({"ok": True, "action": action, "summary": summary}), flush=True)
|
|
continue
|
|
spec = importlib.util.spec_from_file_location("private_dify_" + module_name, original / (module_name + ".py"))
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
module.HERE = destination
|
|
# BASE keeps the read-only historical inventory. Output is NEW.
|
|
summary = module.collect(username=args.username, password=password)
|
|
print(json.dumps({"ok": True, "action": action, "summary": summary}), flush=True)
|
|
except Exception as exc:
|
|
# Do not print remote stderr, credentials, commands, or source bodies.
|
|
print(json.dumps({"ok": False, "action": action, "error_type": type(exc).__name__}), flush=True)
|
|
password = ""
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|