|
#!/usr/bin/env bash |
|
set -euo pipefail |
|
|
|
# ================================ |
|
# Config (adjust as needed) |
|
# ================================ |
|
NODE_EXPORTER_VERSION="1.9.1" # e.g. 1.9.1 |
|
ALLOY_VERSION="1.10.2" # e.g. 1.10.2 |
|
|
|
# Local ports |
|
NODE_EXPORTER_PORT="9100" |
|
ALLOY_HTTP_PORT="12345" |
|
|
|
# Instance label for metrics |
|
INSTANCE_NAME="$(hostname -f || hostname)" |
|
|
|
# Remote Mimir URL (adjust later in /etc/default/alloy if needed) |
|
MIMIR_URL_DEFAULT="http://your-mimir-host:9009/api/v1/push" |
|
|
|
# Paths |
|
NODE_EXPORTER_DIR="/opt/node_exporter" |
|
ALLOY_ROOT="/opt/alloy" |
|
ALLOY_BIN="${ALLOY_ROOT}/bin/alloy" |
|
ALLOY_CONF_DIR="${ALLOY_ROOT}/config" |
|
ALLOY_DATA_DIR="${ALLOY_ROOT}/data" |
|
ALLOY_LOG_DIR="${ALLOY_ROOT}/logs" |
|
ALLOY_ENV_FILE="/etc/default/alloy" |
|
|
|
# ================================ |
|
# Helpers |
|
# ================================ |
|
log() { printf "\033[1;32m[INFO]\033[0m %s\n" "$*"; } |
|
warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; } |
|
err() { printf "\033[1;31m[ERROR]\033[0m %s\n" "$*" >&2; } |
|
|
|
require_root() { |
|
if [[ "${EUID}" -ne 0 ]]; then |
|
err "This script must be run as root (use sudo)." |
|
exit 1 |
|
fi |
|
} |
|
|
|
detect_arch() { |
|
local m |
|
m="$(uname -m)" |
|
case "$m" in |
|
x86_64) echo "amd64" ;; |
|
aarch64) echo "arm64" ;; |
|
arm64) echo "arm64" ;; |
|
*) |
|
err "Unsupported architecture: $m" |
|
exit 1 |
|
;; |
|
esac |
|
} |
|
|
|
install_deps() { |
|
log "Installing dependencies (dnf unzip wget tar)..." |
|
dnf -y install unzip wget tar >/dev/null |
|
} |
|
|
|
ensure_user() { |
|
local user="alloy" |
|
if id -u "$user" >/dev/null 2>&1; then |
|
log "User '$user' already exists, skipping." |
|
else |
|
log "Creating user '$user'..." |
|
useradd -r -s /usr/sbin/nologin -M -U "$user" |
|
fi |
|
} |
|
|
|
prepare_dirs() { |
|
log "Preparing Alloy directories in ${ALLOY_ROOT}..." |
|
mkdir -p "${ALLOY_ROOT}/bin" "${ALLOY_CONF_DIR}" "${ALLOY_DATA_DIR}" "${ALLOY_LOG_DIR}" |
|
chown -R alloy:alloy "${ALLOY_ROOT}" |
|
chmod -R 755 "${ALLOY_ROOT}" |
|
chmod 750 "${ALLOY_DATA_DIR}" |
|
} |
|
|
|
install_node_exporter() { |
|
local arch="$1" |
|
local ver="$NODE_EXPORTER_VERSION" |
|
local pkg="node_exporter-${ver}.linux-${arch}.tar.gz" |
|
local url="https://github.com/prometheus/node_exporter/releases/download/v${ver}/${pkg}" |
|
|
|
if [[ -x "${NODE_EXPORTER_DIR}/node_exporter" ]]; then |
|
log "Node Exporter already installed in ${NODE_EXPORTER_DIR}, skipping." |
|
else |
|
log "Downloading Node Exporter ${ver} (${arch})..." |
|
cd /tmp |
|
rm -f "${pkg}" |
|
wget -q "${url}" |
|
log "Extracting ${pkg}..." |
|
tar -xzf "${pkg}" |
|
local folder="node_exporter-${ver}.linux-${arch}" |
|
if [[ ! -x "/tmp/${folder}/node_exporter" ]]; then |
|
err "node_exporter binary not found after extraction." |
|
exit 1 |
|
fi |
|
log "Moving to ${NODE_EXPORTER_DIR}..." |
|
mv "/tmp/${folder}" "${NODE_EXPORTER_DIR}" |
|
chown -R alloy:alloy "${NODE_EXPORTER_DIR}" |
|
fi |
|
|
|
log "Creating systemd service for Node Exporter..." |
|
cat >/etc/systemd/system/node_exporter.service <<EOF |
|
[Unit] |
|
Description=Node Exporter |
|
After=network-online.target |
|
Wants=network-online.target |
|
|
|
[Service] |
|
User=alloy |
|
Group=alloy |
|
Type=simple |
|
Restart=on-failure |
|
ExecStart=${NODE_EXPORTER_DIR}/node_exporter --web.listen-address=127.0.0.1:${NODE_EXPORTER_PORT} |
|
|
|
[Install] |
|
WantedBy=multi-user.target |
|
EOF |
|
|
|
systemctl daemon-reload |
|
systemctl enable --now node_exporter |
|
systemctl --no-pager --full status node_exporter || true |
|
} |
|
|
|
install_alloy() { |
|
local arch="$1" |
|
local ver="$ALLOY_VERSION" |
|
local zip="alloy-linux-${arch}.zip" |
|
local url="https://github.com/grafana/alloy/releases/download/v${ver}/${zip}" |
|
|
|
if [[ -x "${ALLOY_BIN}" ]]; then |
|
log "Alloy already installed at ${ALLOY_BIN}, skipping." |
|
else |
|
log "Downloading Grafana Alloy ${ver} (${arch})..." |
|
cd /tmp |
|
rm -f "${zip}" |
|
wget -q "${url}" |
|
log "Extracting ${zip}..." |
|
unzip -q "${zip}" |
|
if [[ ! -x "/tmp/alloy-linux-${arch}" ]]; then |
|
err "Alloy binary not found after extraction." |
|
exit 1 |
|
fi |
|
log "Moving binary to ${ALLOY_BIN}..." |
|
mv "/tmp/alloy-linux-${arch}" "${ALLOY_BIN}" |
|
chown alloy:alloy "${ALLOY_BIN}" |
|
chmod 755 "${ALLOY_BIN}" |
|
fi |
|
} |
|
|
|
write_alloy_envfile() { |
|
log "Writing ${ALLOY_ENV_FILE}..." |
|
cat > "${ALLOY_ENV_FILE}" <<EOF |
|
# ================================ |
|
# /etc/default/alloy |
|
# Edit and restart Alloy if needed |
|
# ================================ |
|
INSTANCE="${INSTANCE_NAME}" |
|
|
|
# Remote write |
|
MIMIR_URL="${MIMIR_URL_DEFAULT}" |
|
|
|
# (Optional) Basic auth |
|
# MIMIR_RW_USERNAME="prom_rw" |
|
# MIMIR_RW_PASSWORD="S3cr3t" |
|
|
|
# (Optional) Multi-tenant |
|
# MIMIR_TENANT_ID="test" |
|
EOF |
|
chmod 0644 "${ALLOY_ENV_FILE}" |
|
} |
|
|
|
write_alloy_config() { |
|
log "Generating ${ALLOY_CONF_DIR}/config.river ..." |
|
source "${ALLOY_ENV_FILE}" || true |
|
|
|
local basic_auth_block="" |
|
if [[ "${MIMIR_RW_USERNAME:-}" != "" && "${MIMIR_RW_PASSWORD:-}" != "" ]]; then |
|
basic_auth_block=$(cat <<'E0' |
|
basic_auth { |
|
username = env("MIMIR_RW_USERNAME") |
|
password = env("MIMIR_RW_PASSWORD") |
|
} |
|
E0 |
|
) |
|
fi |
|
|
|
local headers_block="" |
|
if [[ "${MIMIR_TENANT_ID:-}" != "" ]]; then |
|
headers_block=$(cat <<'E1' |
|
headers = { |
|
"X-Scope-OrgID" = env("MIMIR_TENANT_ID") |
|
} |
|
E1 |
|
) |
|
fi |
|
|
|
cat > "${ALLOY_CONF_DIR}/config.river" <<RIVER |
|
prometheus.scrape "node" { |
|
targets = [ |
|
{ |
|
"__address__" = "127.0.0.1:${NODE_EXPORTER_PORT}", |
|
"job" = "node", |
|
"instance" = env("INSTANCE"), |
|
}, |
|
] |
|
|
|
forward_to = [prometheus.remote_write.to_mimir.receiver] |
|
scrape_interval = "15s" |
|
scrape_timeout = "10s" |
|
} |
|
|
|
prometheus.remote_write "to_mimir" { |
|
endpoint { |
|
url = env("MIMIR_URL") |
|
${basic_auth_block} |
|
${headers_block} |
|
queue_config { |
|
capacity = 10000 |
|
max_shards = 4 |
|
max_samples_per_send = 5000 |
|
batch_send_deadline = "5s" |
|
min_backoff = "100ms" |
|
max_backoff = "5s" |
|
retry_on_http_429 = true |
|
} |
|
} |
|
} |
|
RIVER |
|
|
|
chown -R alloy:alloy "${ALLOY_CONF_DIR}" |
|
chmod 0644 "${ALLOY_CONF_DIR}/config.river" |
|
} |
|
|
|
write_alloy_service() { |
|
log "Creating systemd service for Alloy..." |
|
cat >/etc/systemd/system/alloy.service <<EOF |
|
[Unit] |
|
Description=Grafana Alloy (metrics scraper & remote_write) |
|
After=network-online.target |
|
Wants=network-online.target |
|
|
|
[Service] |
|
User=alloy |
|
Group=alloy |
|
EnvironmentFile=-${ALLOY_ENV_FILE} |
|
WorkingDirectory=${ALLOY_ROOT} |
|
ExecStart=${ALLOY_BIN} run \ |
|
--server.http.listen-addr=127.0.0.1:${ALLOY_HTTP_PORT} \ |
|
--storage.path=${ALLOY_DATA_DIR} \ |
|
${ALLOY_CONF_DIR}/config.river |
|
Restart=always |
|
RestartSec=5 |
|
LimitNOFILE=65535 |
|
|
|
[Install] |
|
WantedBy=multi-user.target |
|
EOF |
|
|
|
systemctl daemon-reload |
|
systemctl enable --now alloy |
|
systemctl --no-pager --full status alloy || true |
|
} |
|
|
|
post_checks() { |
|
log "Checking Node Exporter..." |
|
curl -fsS "http://127.0.0.1:${NODE_EXPORTER_PORT}/metrics" >/dev/null && log "Node Exporter OK" || warn "Could not fetch /metrics." |
|
|
|
log "Checking Alloy metrics..." |
|
curl -fsS "http://127.0.0.1:${ALLOY_HTTP_PORT}/metrics" | egrep -E 'scrape|remote_write|alloy' || warn "No Alloy metrics found yet." |
|
} |
|
|
|
main() { |
|
require_root |
|
local arch |
|
arch="$(detect_arch)" |
|
log "Detected architecture: ${arch}" |
|
|
|
install_deps |
|
ensure_user |
|
prepare_dirs |
|
install_node_exporter "${arch}" |
|
install_alloy "${arch}" |
|
write_alloy_envfile |
|
write_alloy_config |
|
write_alloy_service |
|
post_checks |
|
|
|
cat <<NOTE |
|
|
|
======================================== |
|
INSTALLATION COMPLETE |
|
- Node Exporter: listening on 127.0.0.1:${NODE_EXPORTER_PORT} |
|
- Alloy HTTP/Metrics: 127.0.0.1:${ALLOY_HTTP_PORT} |
|
- Alloy config: ${ALLOY_CONF_DIR}/config.river |
|
- Alloy environment vars: ${ALLOY_ENV_FILE} |
|
|
|
To enable basic auth or multi-tenant: |
|
sudo nano ${ALLOY_ENV_FILE} |
|
# Uncomment/edit MIMIR_RW_USERNAME, MIMIR_RW_PASSWORD, MIMIR_TENANT_ID, MIMIR_URL |
|
sudo systemctl daemon-reload |
|
sudo systemctl restart alloy |
|
|
|
Logs: |
|
journalctl -u node_exporter -f |
|
journalctl -u alloy -f |
|
======================================== |
|
NOTE |
|
} |
|
|
|
main "$@" |