From 87979ef8966c2b11146155200de237ce43260545 Mon Sep 17 00:00:00 2001 From: gru <1+gru@gitea.linuxiarz.pl> Date: Thu, 10 Sep 2026 10:46:07 +0200 Subject: [PATCH] Update npm_install.py --- npm_install.py | 175 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 144 insertions(+), 31 deletions(-) diff --git a/npm_install.py b/npm_install.py index 99114a3..c37715b 100644 --- a/npm_install.py +++ b/npm_install.py @@ -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']}")