"""Upload a tested artifact and environment over authenticated SSH; never log secrets.""" import os from pathlib import Path import re import shlex import subprocess import tempfile import uuid def main(): required = ["DEPLOY_HOST", "DEPLOY_USER", "DEPLOY_SSH_KEY", "DEPLOY_KNOWN_HOSTS", "DEPLOY_PATH", "LOTTERY_ENV"] for key in required: if not os.getenv(key, "").strip(): raise SystemExit(f"Missing Drone secret/environment: {key}") host, user = os.environ["DEPLOY_HOST"], os.environ["DEPLOY_USER"] commit, build = os.environ["DRONE_COMMIT_SHA"], os.environ["DRONE_BUILD_NUMBER"] if not re.fullmatch(r"[a-zA-Z0-9.-]+", host) or not re.fullmatch(r"[a-zA-Z_][a-zA-Z0-9_-]*", user): raise SystemExit("Invalid deployment host/user") if not re.fullmatch(r"[a-f0-9]{40}", commit) or not re.fullmatch(r"[0-9]+", build): raise SystemExit("Invalid build identity") destination = f"{user}@{host}" remote = f"/tmp/lottery-deploy-{uuid.uuid4().hex}" with tempfile.TemporaryDirectory(prefix="lottery-ssh-") as directory: directory = Path(directory) key, known, environment = directory / "key", directory / "known_hosts", directory / "runtime.env" for path, value in ((key, "DEPLOY_SSH_KEY"), (known, "DEPLOY_KNOWN_HOSTS"), (environment, "LOTTERY_ENV")): path.write_text(os.environ[value].strip() + "\n", encoding="utf-8") path.chmod(0o600) options = ["-i", str(key), "-o", "BatchMode=yes", "-o", "IdentitiesOnly=yes", "-o", "StrictHostKeyChecking=yes", "-o", f"UserKnownHostsFile={known}", "-o", "ConnectTimeout=15"] def ssh(command): subprocess.run(["ssh", *options, destination, command], check=True, timeout=1200) ssh(f"umask 077; mkdir {shlex.quote(remote)}") try: subprocess.run(["scp", *options, "dist/lottery.tar.gz", "scripts/deploy_release.sh", str(environment), f"{destination}:{remote}/"], check=True, timeout=300) ssh("sh " + shlex.join([remote + "/deploy_release.sh", os.environ["DEPLOY_PATH"], remote, commit, build])) finally: # Remove only the exact uploaded secret, even when a deployment fails. ssh("rm -f -- " + shlex.quote(remote + "/runtime.env")) if __name__ == "__main__": main()