64 lines
1.9 KiB
Python
64 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Install the pinned Hugo release after verifying its published SHA-256."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import io
|
|
from pathlib import Path
|
|
import sys
|
|
import tarfile
|
|
import urllib.request
|
|
|
|
|
|
VERSION = "0.150.1"
|
|
URL = (
|
|
f"https://github.com/gohugoio/hugo/releases/download/v{VERSION}/"
|
|
f"hugo_extended_{VERSION}_linux-amd64.tar.gz"
|
|
)
|
|
SHA256 = "e1248fa077d99232794e38df5ec533494993aafafca1ae3e331a4ed629079ed6"
|
|
MAX_BYTES = 128 * 1024 * 1024
|
|
|
|
|
|
class FetchError(RuntimeError):
|
|
pass
|
|
|
|
|
|
def extract_hugo(archive: bytes, expected: str, destination: Path) -> Path:
|
|
actual = hashlib.sha256(archive).hexdigest()
|
|
if actual != expected:
|
|
raise FetchError(f"Hugo archive digest mismatch: {actual}")
|
|
with tarfile.open(fileobj=io.BytesIO(archive), mode="r:gz") as tar:
|
|
member = tar.getmember("hugo")
|
|
if not member.isfile():
|
|
raise FetchError("Hugo archive member is not a regular file")
|
|
source = tar.extractfile(member)
|
|
if source is None:
|
|
raise FetchError("cannot read the Hugo binary")
|
|
destination.mkdir(parents=True, exist_ok=True)
|
|
binary = destination / "hugo"
|
|
binary.write_bytes(source.read())
|
|
binary.chmod(0o755)
|
|
return binary
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--destination", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
try:
|
|
with urllib.request.urlopen(URL, timeout=60) as response:
|
|
archive = response.read(MAX_BYTES + 1)
|
|
if len(archive) > MAX_BYTES:
|
|
raise FetchError("Hugo archive exceeds the size limit")
|
|
print(extract_hugo(archive, SHA256, args.destination))
|
|
except (OSError, KeyError, tarfile.TarError, FetchError) as exc:
|
|
print(f"error: {exc}", file=sys.stderr)
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|