#!/usr/bin/env bash # check-npm-sudo-config.sh # Audits npm configuration on this VM for sudo-related issues and recommends fixes. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=config.sh source "$SCRIPT_DIR/config.sh" send_telegram() { curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -d chat_id="${TELEGRAM_CHAT_ID}" \ -d text="$1" \ -d parse_mode="HTML" > /dev/null || true } HOSTNAME=$(hostname) DATE=$(date) RED='\033[0;31m' YELLOW='\033[1;33m' GREEN='\033[0;32m' BOLD='\033[1m' RESET='\033[0m' ISSUES=0 WARNINGS=0 log() { echo "$*"; } ok() { printf "${GREEN}✓${RESET} %s\n" "$*"; } warn() { printf "${YELLOW}⚠${RESET} %s\n" "$*"; (( WARNINGS++ )) || true; } fail() { printf "${RED}✗${RESET} %s\n" "$*"; (( ISSUES++ )) || true; } rec() { printf " ${YELLOW}→${RESET} %s\n" "$*"; } header() { echo ""; echo "=========================================="; echo "$*"; echo "=========================================="; } log "==========================================" log " npm sudo config audit" log "==========================================" log "Hostname : $HOSTNAME" log "Date : $DATE" # ── 1. npm present? ─────────────────────────────────────────────────────────── header "1. npm availability" if ! command -v npm &>/dev/null; then warn "npm not found in PATH — skipping remaining checks" echo "" echo "RESULT: 0 issue(s), 1 warning(s)" exit 0 fi NPM_PATH=$(command -v npm) ok "npm found: $NPM_PATH" # ── 2. npm prefix ───────────────────────────────────────────────────────────── header "2. npm prefix" PREFIX=$(npm config get prefix 2>/dev/null || echo "unknown") log "Current prefix: $PREFIX" if [[ "$PREFIX" == "/usr" || "$PREFIX" == "/usr/local" ]]; then fail "npm prefix is $PREFIX (system-wide) — global installs require sudo" rec "npm config set prefix ~/.npm-global" rec "Add to ~/.profile: export PATH=\"\$HOME/.npm-global/bin:\$PATH\"" elif [[ "$PREFIX" == "unknown" ]]; then warn "Could not determine npm prefix" else PREFIX_OWNER=$(stat -c "%U" "$PREFIX" 2>/dev/null || echo "unknown") if [[ "$PREFIX_OWNER" == "root" ]]; then fail "npm prefix $PREFIX is owned by root — global installs require sudo" rec "sudo chown -R \$(whoami) $PREFIX" rec "Or set a user-owned prefix: npm config set prefix ~/.npm-global" else ok "npm prefix is $PREFIX (owned by $PREFIX_OWNER)" fi fi # ── 3. .npmrc ──────────────────────────────────────────────────────────────── header "3. ~/.npmrc" if [[ -f "$HOME/.npmrc" ]]; then log "$(cat "$HOME/.npmrc")" NPM_PREFIX_LINE=$(grep "^prefix=" "$HOME/.npmrc" 2>/dev/null || true) if [[ -n "$NPM_PREFIX_LINE" ]]; then ok ".npmrc explicitly sets: $NPM_PREFIX_LINE" else warn ".npmrc exists but does not pin the prefix" rec "npm config set prefix ~/.npm-global" fi else warn "No ~/.npmrc — prefix is not pinned to a user directory" rec "npm config set prefix ~/.npm-global" fi # ── 4. prefix/bin in PATH ───────────────────────────────────────────────────── header "4. npm prefix bin in PATH" if [[ "$PREFIX" != "unknown" ]]; then PREFIX_BIN="${PREFIX}/bin" if echo "$PATH" | tr ':' '\n' | grep -qxF "$PREFIX_BIN"; then ok "$PREFIX_BIN is in PATH" else warn "$PREFIX_BIN is NOT in PATH — globally installed binaries won't run" PROFILE_FILE="$HOME/.profile" [[ -f "$HOME/.zshrc" ]] && PROFILE_FILE="$HOME/.zshrc" rec "Add to $PROFILE_FILE: export PATH=\"$PREFIX_BIN:\$PATH\"" rec "Then reload: source $PROFILE_FILE" fi fi # ── 5. Root-owned files in npm prefix ──────────────────────────────────────── header "5. Root-owned files in npm prefix" if [[ -d "$PREFIX" ]]; then ROOT_FILES=$(find "$PREFIX" -maxdepth 3 -user root 2>/dev/null | head -5 || true) if [[ -n "$ROOT_FILES" ]]; then fail "Root-owned files found in npm prefix (past sudo npm usage):" echo "$ROOT_FILES" rec "sudo chown -R \$(whoami) $PREFIX" else ok "No root-owned files in $PREFIX" fi else ok "npm prefix directory does not exist yet (no global installs made)" fi # ── 6. sudo npm in shell history ───────────────────────────────────────────── header "6. Shell history — sudo npm usage" SUDO_NPM_FOUND=false for hfile in "$HOME/.bash_history" "$HOME/.zsh_history"; do if [[ -f "$hfile" ]]; then HITS=$(grep -c "sudo npm" "$hfile" 2>/dev/null || true) if [[ "$HITS" -gt 0 ]]; then warn "Found $HITS occurrence(s) of \"sudo npm\" in $hfile" SUDO_NPM_FOUND=true fi fi done $SUDO_NPM_FOUND || ok "No \"sudo npm\" in shell history" # ── 7. npm cache ownership ─────────────────────────────────────────────────── header "7. npm cache ownership" CACHE_DIR=$(npm config get cache 2>/dev/null || echo "$HOME/.npm") if [[ -d "$CACHE_DIR" ]]; then ROOT_CACHE=$(find "$CACHE_DIR" -maxdepth 2 -user root 2>/dev/null | head -3 || true) if [[ -n "$ROOT_CACHE" ]]; then fail "Root-owned files in npm cache ($CACHE_DIR) — will cause EACCES errors" rec "sudo chown -R \$(whoami) $CACHE_DIR" else ok "npm cache ($CACHE_DIR) is user-owned" fi else ok "npm cache directory does not exist yet" fi # ── 8. Node version manager ─────────────────────────────────────────────────── header "8. Node version manager" if command -v n &>/dev/null; then N_PREFIX_VAL="${N_PREFIX:-}" if [[ -z "$N_PREFIX_VAL" ]]; then warn "n is installed but N_PREFIX is not set — n defaults to /usr/local (requires sudo)" rec "Add to ~/.profile: export N_PREFIX=\$HOME/.n" rec "Add to ~/.profile: export PATH=\$PATH:\$N_PREFIX/bin" else ok "n is installed, N_PREFIX=$N_PREFIX_VAL" fi elif [[ -s "$HOME/.nvm/nvm.sh" ]] || command -v nvm &>/dev/null 2>&1; then ok "nvm is managing Node (sudo-free by design)" elif command -v fnm &>/dev/null; then ok "fnm is managing Node (sudo-free by design)" else ok "No Node version manager detected" fi # ── Summary ─────────────────────────────────────────────────────────────────── header "SUMMARY" log "Scan completed at: $(date)" log "" if [[ $ISSUES -gt 0 ]]; then printf "${RED}✗ %d issue(s) and %d warning(s) — see recommendations above${RESET}\n" "$ISSUES" "$WARNINGS" send_telegram "⚠️ npm sudo config issues Host: ${HOSTNAME} Issues: ${ISSUES} | Warnings: ${WARNINGS} Run manually: bash check-npm-sudo-config.sh" exit 1 elif [[ $WARNINGS -gt 0 ]]; then printf "${YELLOW}⚠ Clean but %d warning(s) — see recommendations above${RESET}\n" "$WARNINGS" exit 0 else printf "${GREEN}✓ npm is correctly configured on %s${RESET}\n" "$HOSTNAME" exit 0 fi