Update npm_install.py

This commit is contained in:
gru
2026-09-10 10:46:07 +02:00
parent 095cd0499e
commit 87979ef896
+144 -31
View File
@@ -255,66 +255,163 @@ def apply_interactive_choices(args, choices):
def _memory_gb():
"""Return total and available RAM in GiB using /proc when possible."""
"""Return RAM and swap information in GiB."""
try:
values = {}
for line in Path("/proc/meminfo").read_text().splitlines():
key, value = line.split(":", 1)
values[key] = int(value.split()[0])
total = values.get("MemTotal", 0) / (1024**2)
available = values.get("MemAvailable", values.get("MemFree", 0)) / (1024**2)
return total, available
return {
"ram_total": values.get("MemTotal", 0) / (1024**2),
"ram_available": values.get("MemAvailable", values.get("MemFree", 0)) / (1024**2),
"swap_total": values.get("SwapTotal", 0) / (1024**2),
"swap_free": values.get("SwapFree", 0) / (1024**2),
}
except Exception:
try:
page_size = os.sysconf("SC_PAGE_SIZE")
total = page_size * os.sysconf("SC_PHYS_PAGES") / (1024**3)
available = page_size * os.sysconf("SC_AVPHYS_PAGES") / (1024**3)
return total, available
return {
"ram_total": page_size * os.sysconf("SC_PHYS_PAGES") / (1024**3),
"ram_available": page_size * os.sysconf("SC_AVPHYS_PAGES") / (1024**3),
"swap_total": 0.0,
"swap_free": 0.0,
}
except Exception:
return 0.0, 0.0
return {
"ram_total": 0.0,
"ram_available": 0.0,
"swap_total": 0.0,
"swap_free": 0.0,
}
def _container_type() -> str | None:
"""Return container type when running inside one (for example: lxc, docker)."""
if shutil.which("systemd-detect-virt"):
try:
result = subprocess.run(
["systemd-detect-virt", "--container"],
check=False,
capture_output=True,
text=True,
timeout=5,
)
if result.returncode == 0:
value = (result.stdout or "").strip().lower()
if value and value != "none":
return value
except Exception:
pass
marker = Path("/run/systemd/container")
try:
if marker.exists():
value = marker.read_text(encoding="utf-8", errors="ignore").strip().lower()
if value:
return value
except Exception:
pass
try:
cgroup = Path("/proc/1/cgroup").read_text(encoding="utf-8", errors="ignore").lower()
for name in ("lxc", "docker", "podman", "containerd"):
if name in cgroup:
return name
except Exception:
pass
return None
def _print_manual_memory_help(container_type: str | None, available_gb: float):
print("\n" + "=" * 70)
print("INSUFFICIENT MEMORY FOR INSTALLATION / UPDATE")
print("=" * 70)
if container_type:
print(f"Container detected: {container_type}")
print("Automatic swapon inside a container is usually not permitted.")
else:
print("Automatic temporary swap could not be enabled.")
print(f"Available RAM + free swap: {available_gb:.1f} GB")
print(f"Required: {MIN_MEMORY_GB:.1f} GB")
print("")
print("Temporarily increase RAM or swap before running the installer again.")
if container_type:
print("For LXC, configure RAM/swap on the host or hypervisor, not inside the container.")
print("After the installation/update completes, you can restore the previous limits.")
print("=" * 70 + "\n")
def check_memory_and_create_swap():
"""Create temporary swap only when needed and remember what we changed."""
total_memory_gb, available_memory_gb = _memory_gb()
state = {"created": False, "activated_existing": False}
"""Ensure enough RAM+swap for builds; create temporary swap only outside containers."""
memory = _memory_gb()
state = {"created": False, "activated_existing": False, "ready": True}
ram_total = memory["ram_total"]
ram_available = memory["ram_available"]
swap_total = memory["swap_total"]
swap_free = memory["swap_free"]
available_total = ram_available + swap_free
print(f"\n{'='*70}")
print("MEMORY CHECK")
print(f"{'='*70}")
if total_memory_gb:
print(f"Total RAM: {total_memory_gb:.1f} GB")
print(f"Available: {available_memory_gb:.1f} GB")
else:
if not ram_total:
print("RAM detection: unavailable")
print("Continuing without automatic swap changes.")
print(f"{'='*70}\n")
return state
print(f"Threshold: {MIN_MEMORY_GB} GB")
if available_memory_gb >= MIN_MEMORY_GB:
print("✓ Memory sufficient")
print(f"Total RAM: {ram_total:.1f} GB")
print(f"Available RAM: {ram_available:.1f} GB")
print(f"Swap: {swap_free:.1f} GB free / {swap_total:.1f} GB total")
print(f"Usable now: {available_total:.1f} GB (available RAM + free swap)")
print(f"Threshold: {MIN_MEMORY_GB:.1f} GB")
if available_total >= MIN_MEMORY_GB:
print("\u2713 Memory/swap sufficient")
print(f"{'='*70}\n")
return state
print(f"\u26a0 Low working memory ({available_total:.1f} GB < {MIN_MEMORY_GB:.1f} GB)")
container_type = _container_type()
if container_type:
state["ready"] = False
_print_manual_memory_help(container_type, available_total)
return state
swap_file = Path("/swapfile")
print(f"⚠ Low available memory ({available_memory_gb:.1f} GB)")
try:
active_swap = run_out(["swapon", "--show=NAME", "--noheadings"], check=False)
if str(swap_file) in active_swap.split():
print("✓ /swapfile is already active; leaving it untouched")
print(f"{'='*70}\n")
# It was already counted in SwapFree above. If that was still not enough,
# do not alter a pre-existing swap file owned by the administrator.
state["ready"] = False
_print_manual_memory_help(None, available_total)
return state
except Exception:
pass
if swap_file.exists():
with step("Activating existing /swapfile"):
run(["swapon", str(swap_file)])
state["activated_existing"] = True
print("✓ Existing /swapfile activated temporarily")
print(f"{'='*70}\n")
try:
with step("Activating existing /swapfile"):
run(["swapon", str(swap_file)])
state["activated_existing"] = True
except subprocess.CalledProcessError:
state["ready"] = False
_print_manual_memory_help(None, available_total)
return state
memory = _memory_gb()
available_after = memory["ram_available"] + memory["swap_free"]
if available_after < MIN_MEMORY_GB:
state["ready"] = False
_print_manual_memory_help(None, available_after)
else:
print(f"\u2713 Existing /swapfile activated; usable memory: {available_after:.1f} GB")
print(f"{'='*70}\n")
return state
try:
@@ -330,16 +427,29 @@ def check_memory_and_create_swap():
run(["chmod", "600", str(swap_file)])
run(["mkswap", str(swap_file)])
run(["swapon", str(swap_file)])
except BaseException:
# The caller cannot receive `state` if this function fails before return,
# so clean up a partially created swap file here.
except subprocess.CalledProcessError:
if state["created"]:
run(["swapoff", str(swap_file)], check=False)
swap_file.unlink(missing_ok=True)
state["created"] = False
state["ready"] = False
_print_manual_memory_help(None, available_total)
return state
except BaseException:
if state["created"]:
run(["swapoff", str(swap_file)], check=False)
swap_file.unlink(missing_ok=True)
state["created"] = False
raise
print("✓ Temporary swap created and activated")
print(f"{'='*70}\n")
memory = _memory_gb()
available_after = memory["ram_available"] + memory["swap_free"]
if available_after < MIN_MEMORY_GB:
state["ready"] = False
_print_manual_memory_help(None, available_after)
else:
print(f"\u2713 Temporary swap created; usable memory: {available_after:.1f} GB")
print(f"{'='*70}\n")
return state
def cleanup_build_artifacts():
@@ -3141,6 +3251,9 @@ def main() -> int:
swap_state = {}
try:
swap_state = check_memory_and_create_swap()
if not swap_state.get("ready", True):
return 1
print("\n================== NPM + ANGIE installer ==================")
print(f"Repository: {GITHUB_REPO_URL}")
print(f"OS: {OSREL['PRETTY']}")