diff --git a/system/default.nix b/system/default.nix index 4ab14ac..4593917 100644 --- a/system/default.nix +++ b/system/default.nix @@ -9,6 +9,7 @@ ./motd.nix ./packages.nix ./nix.nix + ./pipe-to-loki.nix ./root-user.nix ./pki/root-ca.nix ./sshd.nix diff --git a/system/pipe-to-loki.nix b/system/pipe-to-loki.nix new file mode 100644 index 0000000..cb90e1c --- /dev/null +++ b/system/pipe-to-loki.nix @@ -0,0 +1,140 @@ +{ + config, + pkgs, + lib, + ... +}: +let + pipe-to-loki = pkgs.writeShellApplication { + name = "pipe-to-loki"; + runtimeInputs = with pkgs; [ + curl + jq + util-linux + coreutils + ]; + text = '' + set -euo pipefail + + LOKI_URL="http://monitoring01.home.2rjus.net:3100/loki/api/v1/push" + HOSTNAME=$(hostname) + SESSION_ID="" + RECORD_MODE=false + + usage() { + echo "Usage: pipe-to-loki [--id ID] [--record]" + echo "" + echo "Send command output or interactive sessions to Loki." + echo "" + echo "Options:" + echo " --id ID Set custom session ID (default: auto-generated)" + echo " --record Start interactive recording session" + echo "" + echo "Examples:" + echo " command | pipe-to-loki # Pipe command output" + echo " command | pipe-to-loki --id foo # Pipe with custom ID" + echo " pipe-to-loki --record # Start recording session" + exit 1 + } + + generate_id() { + local random_chars + random_chars=$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' \n') + echo "''${HOSTNAME}-$(date +%s)-''${random_chars}" + } + + send_to_loki() { + local content="$1" + local type="$2" + local timestamp_ns + timestamp_ns=$(date +%s%N) + + local payload + payload=$(jq -n \ + --arg job "pipe-to-loki" \ + --arg host "$HOSTNAME" \ + --arg type "$type" \ + --arg id "$SESSION_ID" \ + --arg ts "$timestamp_ns" \ + --arg content "$content" \ + '{ + streams: [{ + stream: { + job: $job, + host: $host, + type: $type, + id: $id + }, + values: [[$ts, $content]] + }] + }') + + if curl -s -X POST "$LOKI_URL" \ + -H "Content-Type: application/json" \ + -d "$payload" > /dev/null; then + return 0 + else + echo "Error: Failed to send to Loki" >&2 + return 1 + fi + } + + # Parse arguments + while [[ $# -gt 0 ]]; do + case $1 in + --id) + SESSION_ID="$2" + shift 2 + ;; + --record) + RECORD_MODE=true + shift + ;; + --help|-h) + usage + ;; + *) + echo "Unknown option: $1" >&2 + usage + ;; + esac + done + + # Generate ID if not provided + if [[ -z "$SESSION_ID" ]]; then + SESSION_ID=$(generate_id) + fi + + if $RECORD_MODE; then + # Session recording mode + SCRIPT_FILE=$(mktemp) + trap 'rm -f "$SCRIPT_FILE"' EXIT + + echo "Recording session $SESSION_ID... (exit to send)" + + # Use script to record the session + script -q "$SCRIPT_FILE" + + # Read the transcript and send to Loki + content=$(cat "$SCRIPT_FILE") + if send_to_loki "$content" "session"; then + echo "Session $SESSION_ID sent to Loki" + fi + else + # Pipe mode - read from stdin + if [[ -t 0 ]]; then + echo "Error: No input provided. Pipe a command or use --record for interactive mode." >&2 + exit 1 + fi + + content=$(cat) + if send_to_loki "$content" "command"; then + echo "Sent to Loki with id: $SESSION_ID" + fi + fi + ''; + }; +in +{ + environment.systemPackages = [ pipe-to-loki ]; +}