File size: 3,752 Bytes
e2b893f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"""Provision Node.js and the shared nerimity.js install inside the Space container.

Gradio Space images ship Python, not Node. Rather than depend on whatever `apt` would give
us, we download the official Node LTS tarball on cold start (~25 MB compressed, ~20 s) into
/home/user/.node and install @nerimity/nerimity.js once into a shared node_modules that all
bots resolve through NODE_PATH.

(The plan called this bootstrap_node.sh; it's Python so failures surface in the same log
stream as everything else instead of vanishing into a subshell.)
"""
from __future__ import annotations

import os
import shutil
import subprocess
import tarfile
import urllib.request
from pathlib import Path

NODE_VERSION = os.environ.get("BB_NODE_VERSION", "v22.14.0")
NODE_HOME = Path(os.environ.get("BB_NODE_HOME", "/home/user/.node"))
DATA_DIR = Path(os.environ.get("BB_DATA_DIR", "/home/user/data"))
SHARED_MODULES = DATA_DIR / "node_modules"

NERIMITY_PKG = "@nerimity/nerimity.js"


def _log(msg: str) -> None:
    print(f"[node] {msg}", flush=True)


def ensure_node() -> str:
    """Return a usable `node` binary path, downloading the runtime if needed."""
    existing = shutil.which("node")
    if existing:
        _log(f"using system node: {existing}")
        return existing

    node_bin = NODE_HOME / "bin" / "node"
    if node_bin.exists():
        _log(f"using cached node: {node_bin}")
        os.environ["PATH"] = f"{NODE_HOME / 'bin'}:{os.environ.get('PATH', '')}"
        return str(node_bin)

    name = f"node-{NODE_VERSION}-linux-x64"
    url = f"https://nodejs.org/dist/{NODE_VERSION}/{name}.tar.xz"
    tmp = Path("/tmp") / f"{name}.tar.xz"
    _log(f"downloading {url}")
    urllib.request.urlretrieve(url, tmp)  # noqa: S310 - fixed, trusted URL

    _log("extracting")
    NODE_HOME.parent.mkdir(parents=True, exist_ok=True)
    with tarfile.open(tmp) as tf:
        tf.extractall("/tmp")  # noqa: S202 - official tarball
    extracted = Path("/tmp") / name
    if NODE_HOME.exists():
        shutil.rmtree(NODE_HOME, ignore_errors=True)
    shutil.move(str(extracted), str(NODE_HOME))
    tmp.unlink(missing_ok=True)

    os.environ["PATH"] = f"{NODE_HOME / 'bin'}:{os.environ.get('PATH', '')}"
    _log(f"node ready: {node_bin}")
    return str(node_bin)


def ensure_nerimity(node_bin: str) -> bool:
    """Install the Nerimity SDK once into a shared node_modules. Returns True if usable."""
    marker = SHARED_MODULES / "@nerimity" / "nerimity.js" / "package.json"
    if marker.exists():
        _log("nerimity.js already installed")
        return True

    DATA_DIR.mkdir(parents=True, exist_ok=True)
    pkg = DATA_DIR / "package.json"
    if not pkg.exists():
        pkg.write_text('{"name":"bbuilder-host-deps","private":true}\n')

    npm = Path(node_bin).parent / "npm"
    npm_cmd = [str(npm)] if npm.exists() else ["npm"]
    _log(f"installing {NERIMITY_PKG}")
    try:
        res = subprocess.run(
            npm_cmd + ["install", "--no-audit", "--no-fund", NERIMITY_PKG],
            cwd=str(DATA_DIR),
            capture_output=True,
            text=True,
            timeout=300,
            env={**os.environ, "PATH": f"{Path(node_bin).parent}:{os.environ.get('PATH','')}"},
        )
    except Exception as exc:  # noqa: BLE001
        _log(f"install failed: {exc}")
        return False

    if res.returncode != 0:
        _log(f"npm install failed:\n{res.stderr[-1500:]}")
        return False
    _log("nerimity.js installed")
    return marker.exists()


def version(node_bin: str) -> str:
    try:
        return subprocess.run([node_bin, "--version"], capture_output=True, text=True,
                              timeout=15).stdout.strip()
    except Exception:  # noqa: BLE001
        return "unknown"