a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
577 lines
14 KiB
Bash
Executable File
577 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Alloy Config Generator
|
|
#
|
|
# Interactive script that generates a Grafana Alloy configuration
|
|
# file based on your environment. Asks what backends you use, what
|
|
# signals to collect, and what services to monitor, then outputs
|
|
# a working config.alloy ready to deploy.
|
|
#
|
|
# Usage:
|
|
# ./alloy-config-generator.sh
|
|
# ./alloy-config-generator.sh -o /etc/alloy/config.alloy
|
|
# ./alloy-config-generator.sh --non-interactive --metrics --logs --prometheus-url http://mimir:9009
|
|
#
|
|
# Parameters:
|
|
# -o, --output FILE Write config to file (default: stdout)
|
|
# --non-interactive Skip prompts, use flags and defaults
|
|
# --metrics Enable host metrics collection
|
|
# --logs Enable log collection
|
|
# --traces Enable OTLP trace collection
|
|
# --journald Enable journald log collection
|
|
# --docker Enable Docker container log collection
|
|
# --nginx Enable nginx log collection
|
|
# --prometheus-url URL Prometheus/Mimir remote_write URL
|
|
# --loki-url URL Loki push URL
|
|
# --tempo-url URL Tempo OTLP endpoint (host:port)
|
|
# --hostname NAME Hostname label for metrics and logs
|
|
# --scrape-targets LIST Comma-separated host:port targets to scrape
|
|
# --help Show usage
|
|
#
|
|
# Author: Phil Connor
|
|
# Contact: contact@mylinux.work
|
|
# Website: https://mylinux.work
|
|
# License: MIT
|
|
# Version: 1.0
|
|
|
|
set -euo pipefail
|
|
|
|
# --- Configuration ---
|
|
readonly VERSION="1.0"
|
|
readonly SCRIPT_NAME="$(basename "$0")"
|
|
|
|
# Defaults
|
|
OUTPUT=""
|
|
NON_INTERACTIVE=false
|
|
ENABLE_METRICS=false
|
|
ENABLE_LOGS=false
|
|
ENABLE_TRACES=false
|
|
ENABLE_JOURNALD=false
|
|
ENABLE_DOCKER=false
|
|
ENABLE_NGINX=false
|
|
PROMETHEUS_URL=""
|
|
LOKI_URL=""
|
|
TEMPO_URL=""
|
|
HOSTNAME=""
|
|
SCRAPE_TARGETS=""
|
|
|
|
# --- Functions ---
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $SCRIPT_NAME [OPTIONS]
|
|
|
|
Alloy Config Generator v${VERSION}
|
|
|
|
Generates a Grafana Alloy configuration file interactively or from flags.
|
|
|
|
Options:
|
|
-o, --output FILE Write config to file (default: stdout)
|
|
--non-interactive Skip prompts, use flags and defaults
|
|
--metrics Enable host metrics collection
|
|
--logs Enable log collection
|
|
--traces Enable OTLP trace collection
|
|
--journald Enable journald log collection
|
|
--docker Enable Docker container log collection
|
|
--nginx Enable nginx log collection
|
|
--prometheus-url URL Prometheus/Mimir remote_write URL
|
|
--loki-url URL Loki push URL
|
|
--tempo-url URL Tempo OTLP endpoint (host:port)
|
|
--hostname NAME Hostname label
|
|
--scrape-targets LIST Comma-separated host:port targets to scrape
|
|
--help Show this help
|
|
|
|
Examples:
|
|
$SCRIPT_NAME
|
|
$SCRIPT_NAME -o /etc/alloy/config.alloy
|
|
$SCRIPT_NAME --non-interactive --metrics --logs --prometheus-url http://mimir:9009/api/v1/push --loki-url http://loki:3100/loki/api/v1/push
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
ask() {
|
|
local prompt="$1"
|
|
local default="$2"
|
|
local var_name="$3"
|
|
local response
|
|
|
|
if [[ "$NON_INTERACTIVE" == true ]]; then
|
|
eval "$var_name=\"$default\""
|
|
return
|
|
fi
|
|
|
|
if [[ -n "$default" ]]; then
|
|
read -r -p "$prompt [$default]: " response
|
|
eval "$var_name=\"${response:-$default}\""
|
|
else
|
|
read -r -p "$prompt: " response
|
|
eval "$var_name=\"$response\""
|
|
fi
|
|
}
|
|
|
|
ask_yn() {
|
|
local prompt="$1"
|
|
local default="$2"
|
|
local var_name="$3"
|
|
|
|
if [[ "$NON_INTERACTIVE" == true ]]; then
|
|
return
|
|
fi
|
|
|
|
local yn_hint="y/n"
|
|
[[ "$default" == "y" ]] && yn_hint="Y/n"
|
|
[[ "$default" == "n" ]] && yn_hint="y/N"
|
|
|
|
local response
|
|
read -r -p "$prompt ($yn_hint): " response
|
|
response="${response:-$default}"
|
|
|
|
case "$response" in
|
|
[Yy]*) eval "$var_name=true" ;;
|
|
*) eval "$var_name=false" ;;
|
|
esac
|
|
}
|
|
|
|
interactive_setup() {
|
|
echo ""
|
|
echo "Alloy Config Generator v${VERSION}"
|
|
echo "================================="
|
|
echo ""
|
|
echo "This generates a Grafana Alloy config.alloy file."
|
|
echo ""
|
|
|
|
# Hostname
|
|
local detected_hostname
|
|
detected_hostname=$(hostname -s 2>/dev/null || echo "server")
|
|
ask "Hostname label" "$detected_hostname" HOSTNAME
|
|
echo ""
|
|
|
|
# --- Backends ---
|
|
echo "== Backends =="
|
|
echo ""
|
|
|
|
ask "Prometheus/Mimir remote_write URL (leave empty to skip metrics)" "http://prometheus:9090/api/v1/write" PROMETHEUS_URL
|
|
if [[ -n "$PROMETHEUS_URL" ]]; then
|
|
ENABLE_METRICS=true
|
|
fi
|
|
|
|
ask "Loki push URL (leave empty to skip logs)" "http://loki:3100/loki/api/v1/push" LOKI_URL
|
|
if [[ -n "$LOKI_URL" ]]; then
|
|
ENABLE_LOGS=true
|
|
fi
|
|
|
|
ask "Tempo OTLP endpoint host:port (leave empty to skip traces)" "" TEMPO_URL
|
|
if [[ -n "$TEMPO_URL" ]]; then
|
|
ENABLE_TRACES=true
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# --- Metrics options ---
|
|
if [[ "$ENABLE_METRICS" == true ]]; then
|
|
echo "== Metrics =="
|
|
echo ""
|
|
|
|
local extra_targets=""
|
|
ask "Additional Prometheus scrape targets (comma-separated host:port, or empty)" "" extra_targets
|
|
if [[ -n "$extra_targets" ]]; then
|
|
SCRAPE_TARGETS="$extra_targets"
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
# --- Log options ---
|
|
if [[ "$ENABLE_LOGS" == true ]]; then
|
|
echo "== Logs =="
|
|
echo ""
|
|
ask_yn "Collect journald logs?" "y" ENABLE_JOURNALD
|
|
ask_yn "Collect Docker container logs?" "n" ENABLE_DOCKER
|
|
ask_yn "Collect nginx logs?" "n" ENABLE_NGINX
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# --- Config generation functions ---
|
|
|
|
generate_header() {
|
|
cat <<EOF
|
|
// Grafana Alloy Configuration
|
|
// Generated by alloy-config-generator.sh v${VERSION}
|
|
// $(date +"%Y-%m-%d %H:%M:%S")
|
|
//
|
|
// Hostname: ${HOSTNAME}
|
|
// Docs: https://mylinux.work/guides/grafana-alloy-setup/
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_metrics() {
|
|
if [[ "$ENABLE_METRICS" != true ]]; then
|
|
return
|
|
fi
|
|
|
|
cat <<EOF
|
|
// =========================================
|
|
// Metrics: host metrics → ${PROMETHEUS_URL}
|
|
// =========================================
|
|
|
|
prometheus.exporter.unix "default" {
|
|
set_collectors = ["cpu", "diskstats", "filesystem", "loadavg", "meminfo", "netdev", "uname"]
|
|
filesystem {
|
|
mount_points_exclude = "^/(sys|proc|dev|run)(\$|/)"
|
|
}
|
|
}
|
|
|
|
prometheus.scrape "node" {
|
|
targets = prometheus.exporter.unix.default.targets
|
|
scrape_interval = "15s"
|
|
forward_to = [prometheus.remote_write.default.receiver]
|
|
}
|
|
|
|
// Scrape Alloy internal metrics
|
|
prometheus.scrape "alloy_self" {
|
|
targets = [
|
|
{"__address__" = "localhost:12345"},
|
|
]
|
|
scrape_interval = "30s"
|
|
forward_to = [prometheus.remote_write.default.receiver]
|
|
}
|
|
|
|
EOF
|
|
|
|
# Additional scrape targets
|
|
if [[ -n "$SCRAPE_TARGETS" ]]; then
|
|
cat <<EOF
|
|
// Additional scrape targets
|
|
prometheus.scrape "extra" {
|
|
targets = [
|
|
EOF
|
|
IFS=',' read -ra targets <<< "$SCRAPE_TARGETS"
|
|
for target in "${targets[@]}"; do
|
|
target=$(echo "$target" | xargs) # trim whitespace
|
|
echo " {\"__address__\" = \"${target}\"},"
|
|
done
|
|
cat <<EOF
|
|
]
|
|
scrape_interval = "15s"
|
|
forward_to = [prometheus.remote_write.default.receiver]
|
|
}
|
|
|
|
EOF
|
|
fi
|
|
|
|
cat <<EOF
|
|
prometheus.remote_write "default" {
|
|
endpoint {
|
|
url = "${PROMETHEUS_URL}"
|
|
}
|
|
external_labels = {
|
|
host = "${HOSTNAME}",
|
|
}
|
|
}
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_logs_header() {
|
|
if [[ "$ENABLE_LOGS" != true ]]; then
|
|
return
|
|
fi
|
|
|
|
cat <<EOF
|
|
// =========================================
|
|
// Logs → ${LOKI_URL}
|
|
// =========================================
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_logs_system() {
|
|
if [[ "$ENABLE_LOGS" != true ]]; then
|
|
return
|
|
fi
|
|
|
|
# Detect OS family for log paths
|
|
local syslog_path="/var/log/syslog"
|
|
local auth_path="/var/log/auth.log"
|
|
if [[ -f /etc/redhat-release ]] || [[ -f /etc/rocky-release ]] || [[ -f /etc/almalinux-release ]]; then
|
|
syslog_path="/var/log/messages"
|
|
auth_path="/var/log/secure"
|
|
fi
|
|
|
|
cat <<EOF
|
|
// System log files
|
|
local.file_match "system_logs" {
|
|
path_targets = [
|
|
{__path__ = "${syslog_path}", job = "syslog"},
|
|
{__path__ = "${auth_path}", job = "auth"},
|
|
]
|
|
}
|
|
|
|
loki.source.file "system" {
|
|
targets = local.file_match.system_logs.targets
|
|
forward_to = [loki.process.add_host.receiver]
|
|
}
|
|
|
|
loki.process "add_host" {
|
|
forward_to = [loki.write.default.receiver]
|
|
|
|
stage.static_labels {
|
|
values = {
|
|
host = "${HOSTNAME}",
|
|
}
|
|
}
|
|
}
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_logs_journald() {
|
|
if [[ "$ENABLE_JOURNALD" != true ]]; then
|
|
return
|
|
fi
|
|
|
|
cat <<EOF
|
|
// Journald
|
|
loki.source.journal "default" {
|
|
max_age = "12h"
|
|
relabel_rules = loki.relabel.journal.rules
|
|
forward_to = [loki.write.default.receiver]
|
|
}
|
|
|
|
loki.relabel "journal" {
|
|
forward_to = []
|
|
rule {
|
|
source_labels = ["__journal__systemd_unit"]
|
|
target_label = "unit"
|
|
}
|
|
rule {
|
|
source_labels = ["__journal__hostname"]
|
|
target_label = "host"
|
|
}
|
|
rule {
|
|
source_labels = ["__journal__priority_keyword"]
|
|
target_label = "level"
|
|
}
|
|
}
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_logs_docker() {
|
|
if [[ "$ENABLE_DOCKER" != true ]]; then
|
|
return
|
|
fi
|
|
|
|
cat <<EOF
|
|
// Docker container logs
|
|
discovery.docker "containers" {
|
|
host = "unix:///var/run/docker.sock"
|
|
}
|
|
|
|
discovery.relabel "docker" {
|
|
targets = discovery.docker.containers.targets
|
|
|
|
rule {
|
|
source_labels = ["__meta_docker_container_name"]
|
|
target_label = "container"
|
|
regex = "/(.*)"
|
|
}
|
|
rule {
|
|
source_labels = ["__meta_docker_container_log_stream"]
|
|
target_label = "stream"
|
|
}
|
|
}
|
|
|
|
loki.source.docker "default" {
|
|
host = "unix:///var/run/docker.sock"
|
|
targets = discovery.docker.containers.targets
|
|
labels = {"host" = "${HOSTNAME}", "job" = "docker"}
|
|
forward_to = [loki.write.default.receiver]
|
|
relabel_rules = discovery.relabel.docker.rules
|
|
}
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_logs_nginx() {
|
|
if [[ "$ENABLE_NGINX" != true ]]; then
|
|
return
|
|
fi
|
|
|
|
cat <<EOF
|
|
// Nginx logs
|
|
local.file_match "nginx" {
|
|
path_targets = [
|
|
{__path__ = "/var/log/nginx/access.log", job = "nginx_access"},
|
|
{__path__ = "/var/log/nginx/error.log", job = "nginx_error"},
|
|
]
|
|
}
|
|
|
|
loki.source.file "nginx" {
|
|
targets = local.file_match.nginx.targets
|
|
forward_to = [loki.process.nginx.receiver]
|
|
}
|
|
|
|
loki.process "nginx" {
|
|
forward_to = [loki.write.default.receiver]
|
|
|
|
stage.static_labels {
|
|
values = {
|
|
host = "${HOSTNAME}",
|
|
}
|
|
}
|
|
|
|
stage.regex {
|
|
expression = "^(?P<remote_addr>\\\\S+) - (?P<remote_user>\\\\S+) \\\\[(?P<timestamp>.+?)\\\\] \"(?P<method>\\\\S+) (?P<path>\\\\S+) (?P<protocol>\\\\S+)\" (?P<status>\\\\d+) (?P<bytes>\\\\d+)"
|
|
}
|
|
|
|
stage.labels {
|
|
values = {
|
|
method = "",
|
|
status = "",
|
|
}
|
|
}
|
|
}
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_logs_write() {
|
|
if [[ "$ENABLE_LOGS" != true ]]; then
|
|
return
|
|
fi
|
|
|
|
cat <<EOF
|
|
loki.write "default" {
|
|
endpoint {
|
|
url = "${LOKI_URL}"
|
|
}
|
|
}
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_traces() {
|
|
if [[ "$ENABLE_TRACES" != true ]]; then
|
|
return
|
|
fi
|
|
|
|
cat <<EOF
|
|
// =========================================
|
|
// Traces: OTLP → ${TEMPO_URL}
|
|
// =========================================
|
|
|
|
otelcol.receiver.otlp "default" {
|
|
grpc {
|
|
endpoint = "0.0.0.0:4317"
|
|
}
|
|
http {
|
|
endpoint = "0.0.0.0:4318"
|
|
}
|
|
output {
|
|
traces = [otelcol.processor.batch.default.input]
|
|
}
|
|
}
|
|
|
|
otelcol.processor.batch "default" {
|
|
timeout = "5s"
|
|
output {
|
|
traces = [otelcol.exporter.otlp.tempo.input]
|
|
}
|
|
}
|
|
|
|
otelcol.exporter.otlp "tempo" {
|
|
client {
|
|
endpoint = "${TEMPO_URL}"
|
|
tls {
|
|
insecure = true
|
|
}
|
|
}
|
|
}
|
|
|
|
EOF
|
|
}
|
|
|
|
generate_config() {
|
|
generate_header
|
|
generate_metrics
|
|
generate_logs_header
|
|
generate_logs_system
|
|
generate_logs_journald
|
|
generate_logs_docker
|
|
generate_logs_nginx
|
|
generate_logs_write
|
|
generate_traces
|
|
}
|
|
|
|
# --- Main ---
|
|
|
|
main() {
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-o|--output) OUTPUT="$2"; shift 2 ;;
|
|
--non-interactive) NON_INTERACTIVE=true; shift ;;
|
|
--metrics) ENABLE_METRICS=true; shift ;;
|
|
--logs) ENABLE_LOGS=true; shift ;;
|
|
--traces) ENABLE_TRACES=true; shift ;;
|
|
--journald) ENABLE_JOURNALD=true; shift ;;
|
|
--docker) ENABLE_DOCKER=true; shift ;;
|
|
--nginx) ENABLE_NGINX=true; shift ;;
|
|
--prometheus-url) PROMETHEUS_URL="$2"; ENABLE_METRICS=true; shift 2 ;;
|
|
--loki-url) LOKI_URL="$2"; ENABLE_LOGS=true; shift 2 ;;
|
|
--tempo-url) TEMPO_URL="$2"; ENABLE_TRACES=true; shift 2 ;;
|
|
--hostname) HOSTNAME="$2"; shift 2 ;;
|
|
--scrape-targets) SCRAPE_TARGETS="$2"; shift 2 ;;
|
|
--help|-h) usage ;;
|
|
*) echo "Unknown option: $1" >&2; usage ;;
|
|
esac
|
|
done
|
|
|
|
# Set hostname default
|
|
if [[ -z "$HOSTNAME" ]]; then
|
|
HOSTNAME=$(hostname -s 2>/dev/null || echo "server")
|
|
fi
|
|
|
|
# Set backend URL defaults for non-interactive mode
|
|
if [[ "$NON_INTERACTIVE" == true ]]; then
|
|
[[ "$ENABLE_METRICS" == true && -z "$PROMETHEUS_URL" ]] && PROMETHEUS_URL="http://prometheus:9090/api/v1/write"
|
|
[[ "$ENABLE_LOGS" == true && -z "$LOKI_URL" ]] && LOKI_URL="http://loki:3100/loki/api/v1/push"
|
|
[[ "$ENABLE_TRACES" == true && -z "$TEMPO_URL" ]] && TEMPO_URL="tempo:4317"
|
|
fi
|
|
|
|
# Interactive mode
|
|
if [[ "$NON_INTERACTIVE" != true ]]; then
|
|
interactive_setup
|
|
fi
|
|
|
|
# Check at least one signal is enabled
|
|
if [[ "$ENABLE_METRICS" != true && "$ENABLE_LOGS" != true && "$ENABLE_TRACES" != true ]]; then
|
|
echo "ERROR: No signals enabled. Enable at least one of: metrics, logs, traces" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Generate config
|
|
if [[ -n "$OUTPUT" ]]; then
|
|
generate_config > "$OUTPUT"
|
|
echo ""
|
|
echo "Config written to: $OUTPUT"
|
|
echo ""
|
|
echo "Signals enabled:"
|
|
[[ "$ENABLE_METRICS" == true ]] && echo " ✓ Metrics → $PROMETHEUS_URL"
|
|
[[ "$ENABLE_LOGS" == true ]] && echo " ✓ Logs → $LOKI_URL"
|
|
[[ "$ENABLE_TRACES" == true ]] && echo " ✓ Traces → $TEMPO_URL"
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1. Review the config: cat $OUTPUT"
|
|
echo " 2. Validate syntax: alloy fmt $OUTPUT"
|
|
echo " 3. Test it: alloy run $OUTPUT"
|
|
echo " 4. Deploy: sudo cp $OUTPUT /etc/alloy/config.alloy && sudo systemctl restart alloy"
|
|
else
|
|
generate_config
|
|
fi
|
|
}
|
|
|
|
main "$@"
|