new options
This commit is contained in:
223
npm_install.py
223
npm_install.py
@@ -191,6 +191,40 @@ def comment_x_served_by_step(path="/etc/angie/conf.d/include/proxy.conf"):
|
||||
print(f"✔ Hide X-Served-by header | backup: {backup}")
|
||||
return count
|
||||
|
||||
def set_file_ownership(files: list[str | Path], owner: str, mode: int | None = None):
|
||||
success = []
|
||||
failed = []
|
||||
|
||||
for file_path in files:
|
||||
path = Path(file_path)
|
||||
|
||||
if not path.exists():
|
||||
failed.append((str(path), "File not found"))
|
||||
continue
|
||||
|
||||
try:
|
||||
run(["chown", owner, str(path)])
|
||||
|
||||
if mode is not None:
|
||||
os.chmod(path, mode)
|
||||
|
||||
success.append(str(path))
|
||||
|
||||
except Exception as e:
|
||||
failed.append((str(path), str(e)))
|
||||
|
||||
if success:
|
||||
print(f"✔ Set ownership '{owner}' for {len(success)} file(s)")
|
||||
if DEBUG:
|
||||
for f in success:
|
||||
print(f" - {f}")
|
||||
|
||||
if failed:
|
||||
print(f"⚠ Failed to set ownership for {len(failed)} file(s):")
|
||||
for f, err in failed:
|
||||
print(f" - {f}: {err}")
|
||||
|
||||
return len(failed) == 0
|
||||
|
||||
def download_extract_tar_gz(url: str, dest_dir: Path) -> Path:
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -757,8 +791,94 @@ def adjust_nginx_like_paths_in_tree(root: Path):
|
||||
txt = txt.replace("daemon on;", "#daemon on;")
|
||||
cand.write_text(txt, encoding="utf-8")
|
||||
|
||||
def install_node_and_yarn(node_pkg: str):
|
||||
apt_install([node_pkg])
|
||||
def install_node_from_nodesource(version: str):
|
||||
|
||||
version_map = {
|
||||
'latest': '22',
|
||||
'lts': '22',
|
||||
'current': '23'
|
||||
}
|
||||
|
||||
node_version = version_map.get(version.lower(), version)
|
||||
|
||||
match = re.match(r'(\d+)', node_version)
|
||||
if not match:
|
||||
raise ValueError(f"Invalid Node.js version: {version}")
|
||||
|
||||
major_version = match.group(1)
|
||||
|
||||
with step("Removing old Node.js installations"):
|
||||
run(["apt-get", "remove", "-y", "nodejs", "npm", "libnode-dev", "libnode72"], check=False)
|
||||
run(["apt-get", "purge", "-y", "nodejs", "npm", "libnode-dev", "libnode72"], check=False)
|
||||
run(["apt-get", "autoremove", "-y"], check=False)
|
||||
|
||||
for f in ["/etc/apt/sources.list.d/nodesource.list",
|
||||
"/etc/apt/keyrings/nodesource.gpg",
|
||||
"/usr/share/keyrings/nodesource.gpg",
|
||||
"/etc/apt/trusted.gpg.d/nodesource.gpg"]:
|
||||
if Path(f).exists():
|
||||
Path(f).unlink()
|
||||
|
||||
with step(f"Installing Node.js v{major_version}.x from NodeSource repository"):
|
||||
apt_try_install(["ca-certificates", "curl", "gnupg", "apt-transport-https"])
|
||||
|
||||
setup_url = f"https://deb.nodesource.com/setup_{major_version}.x"
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.sh', delete=False) as tf:
|
||||
script_path = tf.name
|
||||
|
||||
try:
|
||||
run(["curl", "-fsSL", setup_url, "-o", script_path])
|
||||
|
||||
# Make it executable
|
||||
os.chmod(script_path, 0o755)
|
||||
|
||||
if DEBUG:
|
||||
subprocess.run(["bash", script_path], check=True)
|
||||
else:
|
||||
run(["bash", script_path])
|
||||
|
||||
run(["apt-get", "update", "-y"])
|
||||
run(["apt-get", "install", "-y", "nodejs"])
|
||||
|
||||
finally:
|
||||
if Path(script_path).exists():
|
||||
os.unlink(script_path)
|
||||
|
||||
if shutil.which("node"):
|
||||
node_ver = run_out(["node", "--version"], check=False).strip()
|
||||
|
||||
installed_major = re.match(r'v?(\d+)', node_ver)
|
||||
if installed_major and installed_major.group(1) != major_version:
|
||||
print(f"⚠ WARNING: Requested Node.js v{major_version}.x but got {node_ver}")
|
||||
print(f" This likely means NodeSource doesn't support your distribution yet.")
|
||||
|
||||
if shutil.which("npm"):
|
||||
npm_ver = run_out(["npm", "--version"], check=False).strip()
|
||||
print(f"✔ Node.js {node_ver} | npm {npm_ver}")
|
||||
else:
|
||||
print(f"✔ Install npm")
|
||||
|
||||
run(["apt-get", "install", "-y", "npm"], check=False)
|
||||
|
||||
if not shutil.which("npm"):
|
||||
run(["corepack", "enable"], check=False)
|
||||
|
||||
if shutil.which("npm"):
|
||||
npm_ver = run_out(["npm", "--version"], check=False).strip()
|
||||
print(f"✔ npm {npm_ver} installed successfully")
|
||||
else:
|
||||
print(f"✖ npm could not be installed - manual intervention required")
|
||||
else:
|
||||
print("✖ Node.js installation failed")
|
||||
raise RuntimeError("Node.js installation failed")
|
||||
|
||||
def install_node_and_yarn(node_pkg: str = None, node_version: str = None):
|
||||
if node_version:
|
||||
install_node_from_nodesource(node_version)
|
||||
else:
|
||||
apt_install([node_pkg or "nodejs"])
|
||||
|
||||
if shutil.which("yarn") or shutil.which("yarnpkg"):
|
||||
return
|
||||
apt_try_install(["yarn"])
|
||||
@@ -831,65 +951,131 @@ def _prepare_sass(frontend_dir: Path):
|
||||
|
||||
def _build_frontend(src_frontend: Path, dest_frontend: Path):
|
||||
|
||||
|
||||
def _semver(s: str) -> bool:
|
||||
return bool(re.match(r"^\d+(?:\.\d+){1,3}$", (s or "").strip()))
|
||||
|
||||
def _good_yarn(argv: list[str]) -> bool:
|
||||
try:
|
||||
v = (run_out(argv + ["--version"], check=False) or "").strip()
|
||||
return _semver(v)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _pick_yarn_cmd() -> list[str] | None:
|
||||
# Try direct yarn/yarnpkg first
|
||||
for c in (["yarn"], ["yarnpkg"]):
|
||||
if shutil.which(c[0]) and _good_yarn(c):
|
||||
return c
|
||||
if shutil.which("npm") and (run_out(["npm", "--version"], check=False) or "").strip():
|
||||
if _good_yarn(["npm", "exec", "--yes", "yarn@stable"]):
|
||||
return ["npm", "exec", "--yes", "yarn@stable"]
|
||||
if shutil.which("npx") and (run_out(["npx", "--version"], check=False) or "").strip():
|
||||
|
||||
# If npm exists, try to use it to run yarn
|
||||
if shutil.which("npm"):
|
||||
npm_ver = (run_out(["npm", "--version"], check=False) or "").strip()
|
||||
if npm_ver:
|
||||
# Try npm exec yarn@stable
|
||||
if _good_yarn(["npm", "exec", "--yes", "yarn@stable", "--"]):
|
||||
return ["npm", "exec", "--yes", "yarn@stable", "--"]
|
||||
|
||||
# Try npx as fallback
|
||||
if shutil.which("npx"):
|
||||
npx_ver = (run_out(["npx", "--version"], check=False) or "").strip()
|
||||
if npx_ver:
|
||||
if _good_yarn(["npx", "-y", "yarn@stable"]):
|
||||
return ["npx", "-y", "yarn@stable"]
|
||||
|
||||
return None
|
||||
|
||||
def _ensure_yarn_installed():
|
||||
"""Install yarn globally using npm or corepack."""
|
||||
with step("Installing yarn globally"):
|
||||
if not shutil.which("npm"):
|
||||
try:
|
||||
apt_try_install(["npm"])
|
||||
except Exception:
|
||||
run(["apt-get", "update"], check=False)
|
||||
run(["apt-get", "install", "-y", "npm"])
|
||||
run(["npm", "install", "-g", "yarn"], check=False)
|
||||
|
||||
# Try corepack first (modern way)
|
||||
if shutil.which("corepack"):
|
||||
try:
|
||||
run(["corepack", "enable"])
|
||||
run(["corepack", "prepare", "yarn@stable", "--activate"])
|
||||
if shutil.which("yarn"):
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fallback to npm install
|
||||
try:
|
||||
run(["npm", "install", "-g", "yarn@latest"])
|
||||
except Exception:
|
||||
# Last resort - try with --force
|
||||
run(["npm", "install", "-g", "--force", "yarn@latest"], check=False)
|
||||
|
||||
yarn_cmd = _pick_yarn_cmd()
|
||||
if not yarn_cmd:
|
||||
_ensure_yarn_installed()
|
||||
yarn_cmd = _pick_yarn_cmd()
|
||||
|
||||
if not yarn_cmd:
|
||||
raise RuntimeError("Unable to detect or install a valid Yarn. Try: apt-get install -y npm && npm i -g yarn.")
|
||||
raise RuntimeError(
|
||||
"Unable to detect or install a valid Yarn.\n"
|
||||
"Try manually: npm install -g yarn@latest"
|
||||
)
|
||||
|
||||
with step("Installing frontend dependencies (yarn)"):
|
||||
os.environ["NODE_ENV"] = "development"
|
||||
os.chdir(src_frontend)
|
||||
_prepare_sass(src_frontend)
|
||||
|
||||
# Get and create cache directory
|
||||
try:
|
||||
cache_dir = (run_out(yarn_cmd + ["cache", "dir"], check=False) or "").strip()
|
||||
if cache_dir and not Path(cache_dir).exists():
|
||||
Path(cache_dir).mkdir(parents=True, exist_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Clean cache
|
||||
try:
|
||||
run(yarn_cmd + ["cache", "clean"], check=False)
|
||||
run(yarn_cmd + ["install"])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
install_cmd = yarn_cmd + ["install"]
|
||||
|
||||
if install_cmd[-1] == "--":
|
||||
install_cmd = install_cmd[:-1]
|
||||
|
||||
if DEBUG:
|
||||
print(f"Running: {' '.join(install_cmd)}")
|
||||
|
||||
try:
|
||||
run(install_cmd)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"\n✖ Yarn install failed. Trying with --network-timeout and --ignore-engines...")
|
||||
retry_cmd = install_cmd + ["--network-timeout", "100000", "--ignore-engines"]
|
||||
run(retry_cmd)
|
||||
|
||||
with step("Building frontend (yarn build)"):
|
||||
env = os.environ.copy()
|
||||
env["NODE_OPTIONS"] = "--openssl-legacy-provider"
|
||||
run(yarn_cmd + ["build"], env=env)
|
||||
|
||||
build_cmd = yarn_cmd + ["build"]
|
||||
if build_cmd[-1] == "--":
|
||||
build_cmd = build_cmd[:-1]
|
||||
|
||||
try:
|
||||
run(build_cmd, env=env)
|
||||
except subprocess.CalledProcessError:
|
||||
print("\n⚠ Build failed with legacy provider, retrying without...")
|
||||
env.pop("NODE_OPTIONS", None)
|
||||
run(build_cmd, env=env)
|
||||
|
||||
with step("Copying frontend artifacts"):
|
||||
shutil.copytree(src_frontend / "dist", dest_frontend, dirs_exist_ok=True)
|
||||
if (src_frontend / "app-images").exists():
|
||||
shutil.copytree(src_frontend / "app-images", dest_frontend / "images", dirs_exist_ok=True)
|
||||
|
||||
|
||||
def patch_npm_backend_commands():
|
||||
candidates = [
|
||||
Path("/opt/npm/lib/utils.js"),
|
||||
@@ -1163,9 +1349,9 @@ def print_summary(info, ipv6_enabled, dark_enabled, update_mode):
|
||||
|
||||
# ========== UPDATE-ONLY ==========
|
||||
|
||||
def update_only(node_pkg: str, npm_version_override: str | None, apply_dark: bool, dark_env: dict, ipv6_enabled: bool):
|
||||
def update_only(node_pkg: str, node_version: str | None, npm_version_override: str | None, apply_dark: bool, dark_env: dict, ipv6_enabled: bool):
|
||||
apt_update_upgrade()
|
||||
install_node_and_yarn(node_pkg)
|
||||
install_node_and_yarn(node_pkg=node_pkg, node_version=node_version)
|
||||
|
||||
version = github_latest_release_tag("NginxProxyManager/nginx-proxy-manager", npm_version_override)
|
||||
url = f"https://codeload.github.com/NginxProxyManager/nginx-proxy-manager/tar.gz/refs/tags/v{version}"
|
||||
@@ -1278,6 +1464,7 @@ def main():
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||
)
|
||||
parser.add_argument("--nodejs-pkg", default="nodejs", help="APT Node.js package name (e.g. nodejs, nodejs-18).")
|
||||
parser.add_argument("--node-version", default=None, help="Install Node.js from NodeSource repo (e.g. 'latest', '22', '20', '18'). Overrides --nodejs-pkg.")
|
||||
parser.add_argument("--npm-version", default=None, help="Force NPM app version (e.g. 2.12.6). Default: latest release.")
|
||||
parser.add_argument("--motd", choices=["yes","no"], default="yes", help="Update MOTD after completion.")
|
||||
parser.add_argument("--enable-ipv6", action="store_true",
|
||||
@@ -1310,6 +1497,7 @@ def main():
|
||||
fix_logrotate_permissions_and_wrapper()
|
||||
version = update_only(
|
||||
node_pkg=args.nodejs_pkg,
|
||||
node_version=args.node_version,
|
||||
npm_version_override=args.npm_version,
|
||||
apply_dark=args.dark_mode,
|
||||
dark_env=dict(
|
||||
@@ -1333,7 +1521,7 @@ def main():
|
||||
|
||||
setup_angie(ipv6_enabled=args.enable_ipv6)
|
||||
write_metrics_files()
|
||||
install_node_and_yarn(args.nodejs_pkg)
|
||||
install_node_and_yarn(node_pkg=args.nodejs_pkg, node_version=args.node_version)
|
||||
ensure_user_and_dirs()
|
||||
create_sudoers_for_npm()
|
||||
setup_certbot_venv()
|
||||
@@ -1360,6 +1548,11 @@ def main():
|
||||
fix_logrotate_permissions_and_wrapper()
|
||||
sync_backup_nginx_conf()
|
||||
comment_x_served_by_step()
|
||||
set_file_ownership(
|
||||
["/etc/nginx/conf.d/include/ip_ranges.conf"],
|
||||
"npm:npm",
|
||||
0o664
|
||||
)
|
||||
|
||||
with step("Restarting services after installation"):
|
||||
run(["systemctl","restart","angie.service"], check=False)
|
||||
|
||||
Reference in New Issue
Block a user