From 2aac1371b294fd3fa8e3d9b3b1ad6af8612562c5 Mon Sep 17 00:00:00 2001 From: Azat Date: Mon, 2 Feb 2026 22:35:36 +0100 Subject: [PATCH] Initial loki skill with promtail auto-discovery --- SKILL.md | 87 +++++++++++++++++ scripts/autorun.sh | 226 +++++++++++++++++++++++++++++++++++++++++++++ scripts/run.sh | 50 ++++++++++ 3 files changed, 363 insertions(+) create mode 100644 SKILL.md create mode 100644 scripts/autorun.sh create mode 100644 scripts/run.sh diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..ce0b345 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,87 @@ +--- +name: loki +description: Log aggregation with Loki and Promtail +metadata: + version: "1.0.0" + vibestack: + main: false + metrics-port: 3100 +--- + +# Loki Skill + +Log aggregation using [Grafana Loki](https://grafana.com/oss/loki/) with Promtail for log shipping. + +## Features + +- Lightweight log storage (indexes labels, not content) +- Auto-discovers skill logs from supervisor +- Prometheus-compatible metrics +- LogQL query language +- Auto-registers with Caddy if present + +## Architecture + +``` +supervisor logs ──► promtail ──► loki ──► /loki/api/v1/query + /var/log/supervisor/*.log :3100 +``` + +## Configuration + +| Variable | Description | Default | +|----------|-------------|---------| +| `LOKI_PORT` | Loki API port | `3100` | +| `LOKI_DATA_DIR` | Data storage directory | `/data/loki` | +| `LOKI_RETENTION` | Log retention period | `168h` (7 days) | +| `LOKI_DOMAIN` | Domain for Caddy auto-config | (none) | +| `SUPERVISOR_LOG_DIR` | Where to find skill logs | `/var/log/supervisor` | + +## Querying Logs + +### Via API + +```bash +# All logs from last hour +curl -G "http://localhost:3100/loki/api/v1/query_range" \ + --data-urlencode 'query={job="skills"}' \ + --data-urlencode 'start=1h' + +# Logs from specific skill +curl -G "http://localhost:3100/loki/api/v1/query_range" \ + --data-urlencode 'query={skill="caddy"}' + +# Search for errors +curl -G "http://localhost:3100/loki/api/v1/query_range" \ + --data-urlencode 'query={job="skills"} |= "error"' +``` + +### LogQL Examples + +```logql +# All skill logs +{job="skills"} + +# Specific skill +{skill="ttyd"} + +# Filter by content +{job="skills"} |= "error" +{job="skills"} |~ "error|warn" + +# JSON parsing (if logs are JSON) +{job="skills"} | json | level="error" +``` + +## Integration with Grafana + +Add Loki as a data source: +- URL: `http://loki:3100` (or via Caddy proxy) +- Access: Server + +## Metrics + +Loki exposes Prometheus metrics at `:3100/metrics`: +- `loki_ingester_streams_created_total` +- `loki_distributor_bytes_received_total` +- `loki_querier_query_duration_seconds` diff --git a/scripts/autorun.sh b/scripts/autorun.sh new file mode 100644 index 0000000..9e69d54 --- /dev/null +++ b/scripts/autorun.sh @@ -0,0 +1,226 @@ +#!/bin/bash +set -e + +LOKI_VERSION="${LOKI_VERSION:-3.0.0}" +SKILLS_DIR="${SKILLS_DIR:-/skills}" +LOKI_DATA_DIR="${LOKI_DATA_DIR:-/data/loki}" +SUPERVISOR_LOG_DIR="${SUPERVISOR_LOG_DIR:-/var/log/supervisor}" +SKILL_DIR="$(dirname "$(dirname "$0")")" + +# Detect architecture +get_arch() { + case "$(uname -m)" in + x86_64) echo "amd64" ;; + aarch64) echo "arm64" ;; + armv7l) echo "arm" ;; + *) + echo "Unsupported architecture: $(uname -m)" >&2 + exit 1 + ;; + esac +} + +# Install Loki +install_loki() { + if command -v loki &>/dev/null; then + echo "loki already installed" + return 0 + fi + + echo "Installing Loki v${LOKI_VERSION}..." + local arch=$(get_arch) + local url="https://github.com/grafana/loki/releases/download/v${LOKI_VERSION}/loki-linux-${arch}.zip" + + apt-get update && apt-get install -y unzip + + curl -sSL "$url" -o /tmp/loki.zip + unzip -o /tmp/loki.zip -d /tmp + mv /tmp/loki-linux-${arch} /usr/local/bin/loki + chmod +x /usr/local/bin/loki + rm /tmp/loki.zip + + echo "Loki installed" +} + +# Install Promtail +install_promtail() { + if command -v promtail &>/dev/null; then + echo "promtail already installed" + return 0 + fi + + echo "Installing Promtail v${LOKI_VERSION}..." + local arch=$(get_arch) + local url="https://github.com/grafana/loki/releases/download/v${LOKI_VERSION}/promtail-linux-${arch}.zip" + + curl -sSL "$url" -o /tmp/promtail.zip + unzip -o /tmp/promtail.zip -d /tmp + mv /tmp/promtail-linux-${arch} /usr/local/bin/promtail + chmod +x /usr/local/bin/promtail + rm /tmp/promtail.zip + + echo "Promtail installed" +} + +# Setup directories +setup_dirs() { + mkdir -p "$LOKI_DATA_DIR" + mkdir -p "$SKILL_DIR/config" + echo "Data directory: $LOKI_DATA_DIR" +} + +# Generate Loki config +generate_loki_config() { + local retention="${LOKI_RETENTION:-168h}" + + cat > "$SKILL_DIR/config/loki.yaml" << EOF +auth_enabled: false + +server: + http_listen_port: 3100 + grpc_listen_port: 9096 + +common: + instance_addr: 127.0.0.1 + path_prefix: $LOKI_DATA_DIR + storage: + filesystem: + chunks_directory: $LOKI_DATA_DIR/chunks + rules_directory: $LOKI_DATA_DIR/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + +query_range: + results_cache: + cache: + embedded_cache: + enabled: true + max_size_mb: 100 + +schema_config: + configs: + - from: 2020-10-24 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + +limits_config: + retention_period: $retention + +ruler: + alertmanager_url: http://localhost:9093 + +analytics: + reporting_enabled: false +EOF + + echo "Loki config written to $SKILL_DIR/config/loki.yaml" +} + +# Generate Promtail config with auto-discovered log paths +generate_promtail_config() { + cat > "$SKILL_DIR/config/promtail.yaml" << EOF +server: + http_listen_port: 9080 + grpc_listen_port: 0 + +positions: + filename: /tmp/positions.yaml + +clients: + - url: http://localhost:3100/loki/api/v1/push + +scrape_configs: + # Supervisor logs (all skills) + - job_name: skills + static_configs: + - targets: + - localhost + labels: + job: skills + __path__: $SUPERVISOR_LOG_DIR/*.log + + pipeline_stages: + # Extract skill name from filename + - match: + selector: '{job="skills"}' + stages: + - regex: + source: filename + expression: '.*/(?P[^/]+)\\.log$' + - labels: + skill: + + # Supervisor error logs + - job_name: skills_errors + static_configs: + - targets: + - localhost + labels: + job: skills + level: error + __path__: $SUPERVISOR_LOG_DIR/*.err + + pipeline_stages: + - match: + selector: '{job="skills"}' + stages: + - regex: + source: filename + expression: '.*/(?P[^/]+)\\.err$' + - labels: + skill: +EOF + + echo "Promtail config written to $SKILL_DIR/config/promtail.yaml" +} + +# Configure Caddy if present +configure_caddy() { + local caddy_dir="$SKILLS_DIR/caddy" + local loki_port="${LOKI_PORT:-3100}" + local loki_domain="${LOKI_DOMAIN:-}" + + if [ ! -d "$caddy_dir" ]; then + echo "Caddy not found - Loki API on port $loki_port" + return 0 + fi + + echo "Caddy detected - configuring reverse proxy..." + mkdir -p "$caddy_dir/snippets.d" + + local snippet="$caddy_dir/snippets.d/loki.caddy" + + if [ -n "$loki_domain" ]; then + cat > "$snippet" << EOF +# Auto-generated by loki skill +$loki_domain { + reverse_proxy localhost:$loki_port +} +EOF + echo "Caddy config: $loki_domain -> localhost:$loki_port" + else + cat > "$snippet" << EOF +# Auto-generated by loki skill +# Add to your site block: +# handle /loki/* { +# reverse_proxy localhost:$loki_port +# } +EOF + echo "Caddy snippet created (manual config needed)" + fi +} + +install_loki +install_promtail +setup_dirs +generate_loki_config +generate_promtail_config +configure_caddy + +echo "Loki setup complete" diff --git a/scripts/run.sh b/scripts/run.sh new file mode 100644 index 0000000..33df07b --- /dev/null +++ b/scripts/run.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -e + +SKILL_DIR="$(dirname "$(dirname "$0")")" +LOKI_PORT="${LOKI_PORT:-3100}" + +# Start Loki in background +start_loki() { + echo "Starting Loki on port $LOKI_PORT..." + loki -config.file="$SKILL_DIR/config/loki.yaml" & + LOKI_PID=$! + sleep 3 + + if ! kill -0 $LOKI_PID 2>/dev/null; then + echo "Failed to start Loki" + exit 1 + fi + echo "Loki running (PID $LOKI_PID)" +} + +# Start Promtail in background +start_promtail() { + echo "Starting Promtail..." + promtail -config.file="$SKILL_DIR/config/promtail.yaml" & + PROMTAIL_PID=$! + sleep 2 + + if ! kill -0 $PROMTAIL_PID 2>/dev/null; then + echo "Failed to start Promtail" + exit 1 + fi + echo "Promtail running (PID $PROMTAIL_PID)" +} + +# Cleanup on exit +cleanup() { + echo "Shutting down..." + [ -n "$PROMTAIL_PID" ] && kill $PROMTAIL_PID 2>/dev/null + [ -n "$LOKI_PID" ] && kill $LOKI_PID 2>/dev/null + exit 0 +} +trap cleanup SIGTERM SIGINT + +start_loki +start_promtail + +echo "Loki stack running. Query at http://localhost:$LOKI_PORT/loki/api/v1/query" + +# Wait for either process to exit +wait