#!/bin/sh # The final main call keeps an incomplete download from starting installation. main() { if command -v python3.12 >/dev/null 2>&1; then harpy_python=python3.12 elif command -v python3 >/dev/null 2>&1; then harpy_python=python3 else printf '%s\n' 'harPY needs Python 3.12 with venv. Install Python, then run this script again.' >&2 return 1 fi "$harpy_python" -I - "$@" <<'PYTHON' import argparse import hashlib import json import os from pathlib import Path import shlex import shutil import signal import subprocess import sys import tempfile from urllib.parse import urlsplit VERSION = "1.0.0" SOURCE = "2ea00c692dfdee216ea9036b2d3f7c45e81bc9c6" DEFAULT_URL = "https://harpy.williamshayden.com" parser = argparse.ArgumentParser(prog="install.sh", description=f"Install harPY {VERSION} in an isolated Python environment.") parser.add_argument("--base-url", default=DEFAULT_URL, help="download origin (default: %(default)s)") parser.add_argument("--prefix", type=Path, default=Path.home() / ".local/share/harpy", help="installation directory") parser.add_argument("--bin-dir", type=Path, default=Path.home() / ".local/bin", help="directory for the harpy command") parser.add_argument("--extra", choices=("pitch", "train"), help="also install optional Torch or Torch/PPO dependencies") args = parser.parse_args() def fail(message): raise RuntimeError(message) def cancel(_signum, _frame): raise KeyboardInterrupt def run(command): environment = {key: value for key, value in os.environ.items() if not key.startswith("PIP_") and key not in ("PYTHONPATH", "PYTHONHOME")} environment["PIP_CONFIG_FILE"] = os.devnull subprocess.run(command, check=True, env=environment) def install(): if sys.version_info[:2] != (3, 12) or sys.platform != "linux": fail("This installer supports Linux/WSL with Python 3.12.") if shutil.which("curl") is None: fail("curl is required. Install it, then run this script again.") import fcntl origin = args.base_url.rstrip("/") url = urlsplit(origin) local_http = url.scheme == "http" and url.hostname in ("localhost", "127.0.0.1", "::1") if ((url.scheme != "https" and not local_http) or not url.netloc or url.query or url.fragment or url.username is not None or url.password is not None): fail("--base-url must be HTTPS, or HTTP on localhost for local verification.") download_url = f"{origin}/downloads/{VERSION}" protocols = "=http,https" if local_http else "=https" def fetch(name, destination, limit): run(["curl", "--fail", "--silent", "--show-error", "--location", "--proto", protocols, "--proto-redir", protocols, "--connect-timeout", "15", "--max-time", "300", "--max-filesize", str(limit), "--output", str(destination), f"{download_url}/{name}"]) if destination.stat().st_size > limit: fail("Download exceeded its declared size.") prefix = args.prefix.expanduser().absolute() bin_dir = args.bin_dir.expanduser().absolute() profile = args.extra or "base" environment = prefix / VERSION / profile executable = environment / "bin/harpy" command_link = bin_dir / "harpy" def managed_link(): if not os.path.lexists(command_link): return None if command_link.is_symlink(): destination = command_link.resolve() previous = destination.parent.parent receipt = previous / ".harpy-install.json" if (destination.name == "harpy" and destination.parent.name == "bin" and previous.parent.parent == prefix.resolve() and previous.name in ("base", "pitch", "train") and destination.is_file() and receipt.is_file()): try: record = json.loads(receipt.read_text()) except (OSError, ValueError): record = {} if (isinstance(record, dict) and record.get("schema") == "harpy-download-v1" and record.get("status") in ("preview", "release") and record.get("version") == previous.parent.name and record.get("profile") == previous.name and isinstance(record.get("environment"), str) and Path(record.get("environment", "")).resolve() == previous and record.get("requires_python") == ">=3.12,<3.13" and all(isinstance(record.get(key), str) and len(record[key]) == length and all(character in "0123456789abcdef" for character in record[key]) for key, length in (("source_commit", 40), ("wheel_sha256", 64)))): return destination fail(f"{command_link} is not an installer-managed harPY command. Choose another --bin-dir.") def publish_link(): previous = managed_link() if previous == executable.resolve(): return with tempfile.TemporaryDirectory(prefix=".harpy-link-", dir=bin_dir) as staging: replacement = Path(staging) / "harpy" replacement.symlink_to(executable) if managed_link() != previous: fail("The harpy command changed during installation; it was left untouched.") os.replace(replacement, command_link) with tempfile.TemporaryDirectory(prefix="harpy-download-") as temporary: temporary = Path(temporary) manifest_path = temporary / "manifest.json" fetch("manifest.json", manifest_path, 1024 * 1024) manifest = json.loads(manifest_path.read_text()) required = {"schema": "harpy-download-v1", "status": "release", "version": VERSION, "source_commit": SOURCE, "requires_python": ">=3.12,<3.13"} if not isinstance(manifest, dict) or any(manifest.get(key) != value for key, value in required.items()): fail("The download manifest does not match this installer.") wheel = manifest.get("wheel") filename = f"harpy_audio-{VERSION}-py3-none-any.whl" if (not isinstance(wheel, dict) or wheel.get("filename") != filename or not isinstance(wheel.get("sha256"), str) or len(wheel["sha256"]) != 64 or any(character not in "0123456789abcdef" for character in wheel["sha256"]) or type(wheel.get("size_bytes")) is not int or not 0 < wheel["size_bytes"] <= 100 * 1024 * 1024): fail("The manifest contains invalid wheel metadata.") expected_receipt = {**required, "wheel_sha256": wheel["sha256"], "profile": profile, "environment": str(environment)} locks = prefix / ".locks" locks.mkdir(parents=True, exist_ok=True) bin_dir.mkdir(parents=True, exist_ok=True) with (bin_dir / ".harpy-install.lock").open("a") as bin_lock, (locks / f"{VERSION}-{profile}.lock").open("a") as lock: fcntl.flock(bin_lock, fcntl.LOCK_EX) fcntl.flock(lock, fcntl.LOCK_EX) managed_link() receipt = environment / ".harpy-install.json" if os.path.lexists(environment): if (environment.is_symlink() or not receipt.is_file() or json.loads(receipt.read_text()) != expected_receipt or not executable.is_file() or not os.access(executable, os.X_OK)): fail(f"{environment} already exists without a matching completed installation. It was left untouched.") print(f"harPY {VERSION} ({profile}) is already installed.") else: wheel_path = temporary / filename fetch(filename, wheel_path, wheel["size_bytes"]) with wheel_path.open("rb") as downloaded: digest = hashlib.file_digest(downloaded, "sha256").hexdigest() if wheel_path.stat().st_size != wheel["size_bytes"] or digest != wheel["sha256"]: fail("Wheel checksum or size verification failed. Nothing was installed.") environment.parent.mkdir(parents=True, exist_ok=True) environment.mkdir() owned = environment.stat() complete = False try: print(f"Installing harPY {VERSION} ({profile}) into {environment}", flush=True) run([sys.executable, "-I", "-m", "venv", str(environment)]) package = str(wheel_path) + (f"[{args.extra}]" if args.extra else "") run([str(environment / "bin/python"), "-I", "-m", "pip", "--isolated", "install", "--disable-pip-version-check", "--no-input", package]) run([str(executable), "--help"]) receipt.write_text(json.dumps(expected_receipt, indent=2) + "\n") complete = True finally: if not complete and environment.exists() and not environment.is_symlink(): current = environment.stat() if (current.st_dev, current.st_ino) == (owned.st_dev, owned.st_ino): shutil.rmtree(environment) publish_link() print(f"Run: {shlex.quote(str(command_link))} --help") if str(bin_dir) not in os.environ.get("PATH", "").split(os.pathsep): print(f"To use 'harpy' in this shell:\n export PATH={shlex.quote(str(bin_dir))}:\"$PATH\"") signal.signal(signal.SIGTERM, cancel) signal.signal(signal.SIGINT, cancel) try: install() except KeyboardInterrupt: print("harPY installation interrupted; this attempt's unfinished environment was removed.", file=sys.stderr) sys.exit(130) except (OSError, ValueError, RuntimeError, subprocess.CalledProcessError) as error: print(f"harPY installation failed: {error}", file=sys.stderr) sys.exit(1) PYTHON } main "$@"