release: nexus btpanel session fix and app-v2
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Upload and optionally apply a Nexus release bundle over SSH.
|
||||
|
||||
The script deliberately avoids local shell pipelines so it behaves consistently
|
||||
on Windows, Git Bash, WSL, and Linux/macOS. By default it only uploads the
|
||||
tarball and runs the remote preflight; applying the release requires --apply.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import posixpath
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
DEFAULT_DEPLOY_PATH = "/opt/nexus"
|
||||
DEFAULT_REMOTE_TARBALL = "/tmp/nexus-release-20260708.tar.gz"
|
||||
DEFAULT_REMOTE_EXTRACT_DIR = "/tmp"
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def run(cmd: list[str], *, dry_run: bool = False) -> None:
|
||||
printable = " ".join(shlex.quote(part) for part in cmd)
|
||||
print(f"\n$ {printable}", flush=True)
|
||||
if dry_run:
|
||||
return
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
|
||||
def build_ssh_base(args: argparse.Namespace) -> list[str]:
|
||||
cmd = ["ssh"]
|
||||
if args.identity_file:
|
||||
cmd.extend(["-i", str(args.identity_file)])
|
||||
for option in args.ssh_option:
|
||||
cmd.extend(["-o", option])
|
||||
return cmd
|
||||
|
||||
|
||||
def build_scp_base(args: argparse.Namespace) -> list[str]:
|
||||
cmd = ["scp"]
|
||||
if args.identity_file:
|
||||
cmd.extend(["-i", str(args.identity_file)])
|
||||
for option in args.ssh_option:
|
||||
cmd.extend(["-o", option])
|
||||
return cmd
|
||||
|
||||
|
||||
def remote_bash(ssh_base: list[str], remote: str, script: str, *, dry_run: bool = False) -> None:
|
||||
run([*ssh_base, remote, "bash", "-lc", script], dry_run=dry_run)
|
||||
|
||||
|
||||
def q(value: str | Path) -> str:
|
||||
return shlex.quote(str(value))
|
||||
|
||||
|
||||
def remote_join(*parts: str) -> str:
|
||||
return posixpath.join(*parts)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--remote", required=True, help="SSH target, for example 'nexus' or 'root@1.2.3.4'.")
|
||||
parser.add_argument("--tarball", required=True, type=Path, help="Local release tarball path.")
|
||||
parser.add_argument("--sha256", required=True, help="Expected tarball SHA256.")
|
||||
parser.add_argument("--deploy-path", default=DEFAULT_DEPLOY_PATH, help=f"Remote deploy path. Default: {DEFAULT_DEPLOY_PATH}")
|
||||
parser.add_argument("--remote-tarball", default=DEFAULT_REMOTE_TARBALL, help=f"Remote tarball path. Default: {DEFAULT_REMOTE_TARBALL}")
|
||||
parser.add_argument("--remote-extract-dir", default=DEFAULT_REMOTE_EXTRACT_DIR, help=f"Remote extract dir. Default: {DEFAULT_REMOTE_EXTRACT_DIR}")
|
||||
parser.add_argument("--identity-file", type=Path, help="Optional SSH private key.")
|
||||
parser.add_argument("--ssh-option", action="append", default=[], help="Extra ssh/scp -o option. Can be repeated.")
|
||||
parser.add_argument("--no-upload", action="store_true", help="Skip scp upload and use --remote-tarball already on the host.")
|
||||
parser.add_argument("--apply", action="store_true", help="Apply the release after preflight. Without this flag, only preflight runs.")
|
||||
parser.add_argument("--no-sudo", action="store_true", help="Do not prefix the apply command with sudo.")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Print commands without executing them.")
|
||||
args = parser.parse_args()
|
||||
|
||||
tarball = args.tarball.resolve()
|
||||
if not tarball.exists():
|
||||
print(f"ERROR: local tarball not found: {tarball}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
actual_sha = sha256_file(tarball)
|
||||
if actual_sha.lower() != args.sha256.lower():
|
||||
print("ERROR: local tarball SHA256 mismatch", file=sys.stderr)
|
||||
print(f"expected: {args.sha256}", file=sys.stderr)
|
||||
print(f"actual: {actual_sha}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
ssh_base = build_ssh_base(args)
|
||||
scp_base = build_scp_base(args)
|
||||
|
||||
print(f"Local tarball SHA256 OK: {actual_sha}")
|
||||
print(f"Remote target: {args.remote}")
|
||||
print(f"Remote tarball: {args.remote_tarball}")
|
||||
print(f"Remote deploy path: {args.deploy_path}")
|
||||
print(f"Mode: {'apply release' if args.apply else 'preflight only'}")
|
||||
|
||||
if not args.no_upload:
|
||||
remote_target = f"{args.remote}:{args.remote_tarball}"
|
||||
run([*scp_base, str(tarball), remote_target], dry_run=args.dry_run)
|
||||
|
||||
extract_script = " && ".join(
|
||||
[
|
||||
f"test -f {q(args.remote_tarball)}",
|
||||
f"mkdir -p {q(args.remote_extract_dir)}",
|
||||
(
|
||||
f"tar xzf {q(args.remote_tarball)} -C {q(args.remote_extract_dir)} "
|
||||
"nexus-release/deploy/preflight-release-host.sh "
|
||||
"nexus-release/deploy/apply-release-bundle.sh"
|
||||
),
|
||||
]
|
||||
)
|
||||
remote_bash(ssh_base, args.remote, extract_script, dry_run=args.dry_run)
|
||||
|
||||
preflight = (
|
||||
f"bash {q(remote_join(args.remote_extract_dir, 'nexus-release/deploy/preflight-release-host.sh'))} "
|
||||
f"--tarball {q(args.remote_tarball)} "
|
||||
f"--sha256 {q(args.sha256)} "
|
||||
f"--deploy-path {q(args.deploy_path)}"
|
||||
)
|
||||
remote_bash(ssh_base, args.remote, preflight, dry_run=args.dry_run)
|
||||
|
||||
if args.apply:
|
||||
apply_prefix = "" if args.no_sudo else "sudo "
|
||||
apply_cmd = (
|
||||
f"{apply_prefix}bash {q(remote_join(args.remote_extract_dir, 'nexus-release/deploy/apply-release-bundle.sh'))} "
|
||||
f"--tarball {q(args.remote_tarball)} "
|
||||
f"--sha256 {q(args.sha256)} "
|
||||
f"--deploy-path {q(args.deploy_path)}"
|
||||
)
|
||||
remote_bash(ssh_base, args.remote, apply_cmd, dry_run=args.dry_run)
|
||||
else:
|
||||
print("\nPreflight finished. Re-run with --apply to apply the release after reviewing the output.")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user