mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
`make install-lint` could hang indefinitely on the actionlint / markdownlint binary download because the underlying `urlopen` calls in `utils.install.github_release.resolve_latest_tag` and `utils.install.primitives.download_file` ran without a timeout parameter. Slow / dropped TLS handshakes against `github.com` (e.g. behind an MTU-broken bridge) translated into a frozen install step with no error. Adds keyword-only `timeout=` parameters with sensible defaults: 30s for the latest-tag redirect resolve, 60s for the actual binary download. Either can be lowered per call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
58 lines
1.4 KiB
Python
58 lines
1.4 KiB
Python
"""Shared low-level install helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
from urllib.request import urlopen
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Sequence
|
|
|
|
|
|
def log(msg: str) -> None:
|
|
print(msg, flush=True)
|
|
|
|
|
|
def warn(msg: str) -> None:
|
|
print(msg, file=sys.stderr, flush=True)
|
|
|
|
|
|
def which(cmd: str) -> bool:
|
|
return shutil.which(cmd) is not None
|
|
|
|
|
|
def run_privileged(cmd: Sequence[str]) -> None:
|
|
argv: list[str] = list(cmd)
|
|
if os.geteuid() != 0 and shutil.which("sudo") is not None:
|
|
argv = ["sudo", *argv]
|
|
subprocess.run(argv, check=True)
|
|
|
|
|
|
def ensure_dir_on_path(directory: str) -> None:
|
|
if not directory:
|
|
return
|
|
current = os.environ.get("PATH", "")
|
|
parts = current.split(os.pathsep) if current else []
|
|
if directory in parts:
|
|
return
|
|
os.environ["PATH"] = directory + (os.pathsep + current if current else "")
|
|
|
|
|
|
def download_file(url: str, output: str, *, timeout: float = 60.0) -> None:
|
|
with urlopen(url, timeout=timeout) as response: # noqa: S310 - trusted release URLs only
|
|
data = response.read()
|
|
Path(output).write_bytes(data)
|
|
|
|
|
|
def install_with_optional_sudo(cmd: Sequence[str]) -> None:
|
|
argv = list(cmd)
|
|
try:
|
|
subprocess.run(argv, check=True)
|
|
except subprocess.CalledProcessError:
|
|
run_privileged(argv)
|