76 lines
4.7 KiB
Python
Executable File
76 lines
4.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Migrate Nginx Proxy Manager proxy hosts from SQLite into RustProxy."""
|
|
from __future__ import annotations
|
|
|
|
from migrate_npm_core import * # stable migration/model API
|
|
from migrate_npm_api import * # stable administration API helpers
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--database", type=Path, default=Path("/data/database.sqlite"), help="NPM SQLite database (default: /data/database.sqlite)")
|
|
parser.add_argument("--rustproxy", required=True, help="RustProxy admin origin, e.g. https://rustproxy.example:9090")
|
|
parser.add_argument("--username", default=os.environ.get("RUSTPROXY_USERNAME"), help="RustProxy administrator username (or RUSTPROXY_USERNAME)")
|
|
parser.add_argument("--password-env", default="RUSTPROXY_PASSWORD", help="environment variable containing the RustProxy password")
|
|
parser.add_argument("--ca-file", type=Path, help="CA file for RustProxy administration HTTPS")
|
|
parser.add_argument("--insecure-admin-tls", action="store_true", help="disable certificate verification for the RustProxy admin HTTPS connection")
|
|
parser.add_argument("--http-listener", action="append", help="RustProxy HTTP listener ID; repeat or comma-separate. Default: first enabled HTTP listener")
|
|
parser.add_argument("--https-listener", action="append", help="RustProxy TLS listener ID; repeat or comma-separate. Default: first enabled TLS listener")
|
|
parser.add_argument("--certificate-id", help="named RustProxy certificate for imported TLS hosts; omit to use the application default certificate")
|
|
parser.add_argument("--force-https", action="store_true", help="enable RustProxy Force SSL for every imported host and attach it to HTTP+TLS listeners")
|
|
parser.add_argument("--npm-compatible-backend-tls", action="store_true", help="legacy no-op: backend tls_verify already defaults to false in RustProxy 0.22.1")
|
|
parser.add_argument("--plan-out", type=Path, default=Path("npm-migration-plan.json"), help="write the generated plan/config JSON here")
|
|
parser.add_argument("--apply", action="store_true", help="perform the revisioned PUT /api/config; without this flag the run is read-only")
|
|
args = parser.parse_args()
|
|
|
|
if args.ca_file and args.insecure_admin_tls:
|
|
parser.error("choose either --ca-file or --insecure-admin-tls")
|
|
username = args.username or input("RustProxy username: ").strip()
|
|
if not username:
|
|
parser.error("RustProxy username is required")
|
|
password = os.environ.get(args.password_env)
|
|
if password is None:
|
|
password = getpass.getpass("RustProxy password: ")
|
|
|
|
try:
|
|
npm_hosts = load_npm_hosts(args.database)
|
|
client = RustProxyClient(args.rustproxy, ca_file=args.ca_file, insecure_tls=args.insecure_admin_tls)
|
|
client.login(username, password)
|
|
envelope = client.config()
|
|
merged, decisions = build_migration(
|
|
npm_hosts,
|
|
envelope["config"],
|
|
http_listener_ids=split_ids(args.http_listener),
|
|
https_listener_ids=split_ids(args.https_listener),
|
|
certificate_id=args.certificate_id,
|
|
force_https=args.force_https,
|
|
npm_compatible_backend_tls=args.npm_compatible_backend_tls,
|
|
)
|
|
config_bytes = len(json.dumps(merged, separators=(",", ":"), ensure_ascii=False).encode("utf-8"))
|
|
if config_bytes > MAX_CONFIG_BYTES:
|
|
raise MigrationError(f"merged RustProxy config is {config_bytes} bytes; API limit is {MAX_CONFIG_BYTES} bytes")
|
|
plan = {
|
|
"source": {"type": "nginx-proxy-manager-sqlite", "database": str(args.database)},
|
|
"rustproxy": {"origin": args.rustproxy, "expected_revision": envelope["revision"], "config_bytes": config_bytes},
|
|
"apply_requested": bool(args.apply),
|
|
"decisions": [asdict(item) for item in decisions],
|
|
"config": merged,
|
|
}
|
|
write_private_json(args.plan_out, plan)
|
|
print_report(decisions)
|
|
print(f"\nPlan written: {args.plan_out}")
|
|
if args.apply:
|
|
imported = sum(1 for item in decisions if item.action == "import")
|
|
if imported == 0:
|
|
raise MigrationError("nothing to import; configuration was not written")
|
|
applied = client.apply(envelope["revision"], merged)
|
|
print(f"Applied RustProxy configuration revision {applied.get('revision', '?')} ({imported} NPM proxy host records imported).")
|
|
else:
|
|
print("Dry run only. Review the plan and rerun with --apply to write RustProxy configuration.")
|
|
except (MigrationError, OSError, sqlite3.Error) as error:
|
|
parser.exit(1, f"Migration failed: {error}\n")
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|