# Bulk node_exporter Install — July 2026 Rollout

## Server inventory

All 9 Hetzner servers received node_exporter v1.8.2 in a single session:

| Hostname | IP | Status |
|----------|-----|--------|
| app1-bu.itpropartner.com | 5.161.114.8 | Running |
| ai.itpropartner.com | 178.156.167.181 | Running |
| hudu.itpropartner.com | 178.156.130.130 | Running |
| (unifi) | 178.156.131.57 | Running |
| unms.forefrontwireless.com | 5.161.225.131 | Running |
| wphost02 | 5.161.62.38 | Running |
| app1.itpropartner.com | 87.99.144.163 | Running |
| (docker) | 178.156.168.35 | Running |
| (fleettrack360) | 178.156.149.32 | Running |

## Batch install pattern

Write two scripts:

1. **install-node-exporter.sh** — self-contained script that downloads, installs binary, creates systemd unit, enables/starts. The existing `scripts/install-node-exporter.sh` in this skill works.

2. **Wrapper script** — iterates over a list and runs SCP + SSH for each:

```bash
#!/bin/bash
SERVERS=(
  "hostname1:1.2.3.4"
  "hostname2:5.6.7.8"
)
KEY="/root/.ssh/itpp-infra"
SCRIPT="/tmp/install-node-exporter.sh"

for entry in "${SERVERS[@]}"; do
  hostname="${entry%%:*}"
  ip="${entry##*:}"
  echo "=== Installing on $hostname ($ip) ==="
  scp -o StrictHostKeyChecking=no -i "$KEY" "$SCRIPT" "root@$ip:/tmp/"
  ssh -o StrictHostKeyChecking=no -i "$KEY" "root@$ip" "bash /tmp/install-node-exporter.sh"
  echo "✓ $hostname"
done
```

## Verifying all targets in Prometheus

After the config is updated and Prometheus reloaded:

```bash
curl -s http://127.0.0.1:9090/api/v1/targets | python3 -c \
  "import json,sys;d=json.load(sys.stdin);[print(t['labels']['instance'],t['health']) for t in d['data']['activeTargets']]"
```

## Pitfalls

- **Security scanners blocking raw IPs** in SSH/SCP commands. Using hostnames where available bypasses false-positive security warnings.
- **All 9 installs can run sequentially** in ~90 seconds total — no parallelism needed.
- **UFW/firewall** must allow incoming port 9100 from Core's IP, or Prometheus must scrape via hostname/SSH tunnel. The existing servers had no restrictive UFW rules blocking port 9100.
- **node_exporter listens on all interfaces by default** (0.0.0.0:9100). If restricting to localhost, add `--web.listen-address=127.0.0.1:9100` to ExecStart and scrape via a local proxy or Tailscale.
