From 4cc204dd67f2a44a617a6c5ec8c32ef2110dfc80 Mon Sep 17 00:00:00 2001 From: alf Date: Sun, 23 Aug 2026 18:49:53 +0000 Subject: [PATCH] =?UTF-8?q?tailscale-toggle.sh=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tailscale-toggle.sh | 147 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 tailscale-toggle.sh diff --git a/tailscale-toggle.sh b/tailscale-toggle.sh new file mode 100644 index 0000000..aa1e837 --- /dev/null +++ b/tailscale-toggle.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# +# ts-menu.sh — Interaktives Menü zum Sperren/Entsperren von Tailscale-Geräten +# +# Nutzt `gum` für die Menüführung und die Tailscale-API (authorized-Flag), +# um einzelne Geräte vom Tailnet zu trennen bzw. wieder zuzulassen. +# +# Voraussetzungen: +# - gum (https://github.com/charmbracelet/gum) +# - curl, jq +# - API-Access-Token: https://login.tailscale.com/admin/settings/keys +# +# Konfiguration in ~/.ts-toggle.conf: +# TAILSCALE_API_KEY="tskey-api-xxxxx" +# TAILSCALE_TAILNET="deine-org.ts.net" +# +# Verwendung: +# ./ts-menu.sh + +set -euo pipefail + +CONF_FILE="${HOME}/.ts-toggle.conf" +[[ -f "$CONF_FILE" ]] && source "$CONF_FILE" + +API_KEY="${TAILSCALE_API_KEY:-}" +TAILNET="${TAILSCALE_TAILNET:-}" +API_BASE="https://api.tailscale.com/api/v2" + +die() { + gum style --foreground 1 "Fehler: $*" >&2 2>/dev/null || echo "Fehler: $*" >&2 + exit 1 +} + +command -v gum >/dev/null 2>&1 || { echo "Fehler: gum wird benötigt (https://github.com/charmbracelet/gum)."; exit 1; } +command -v curl >/dev/null 2>&1 || die "curl wird benötigt." +command -v jq >/dev/null 2>&1 || die "jq wird benötigt." +[[ -n "$API_KEY" ]] || die "TAILSCALE_API_KEY ist nicht gesetzt (siehe ~/.ts-toggle.conf)." +[[ -n "$TAILNET" ]] || die "TAILSCALE_TAILNET ist nicht gesetzt (siehe ~/.ts-toggle.conf)." + +api_get() { + curl -sf -u "${API_KEY}:" "${API_BASE}/$1" +} + +api_post() { + curl -sf -u "${API_KEY}:" -H "Content-Type: application/json" \ + -X POST -d "$2" "${API_BASE}/$1" +} + +fetch_devices() { + api_get "tailnet/${TAILNET}/devices" +} + +# Zeigt eine Auswahlliste der Geräte und gibt die gewählte Device-ID aus +choose_device() { + local devices="$1" + local selection id + + selection=$(echo "$devices" | jq -r ' + .devices[] | [.id, .name, (if .authorized then "✅ aktiv" else "⛔ gesperrt" end)] | @tsv' \ + | awk -F'\t' '{printf "%-35s %s\n", $2, $3, $1}' \ + | gum choose --header "Gerät auswählen" --height 15) || return 1 + + [[ -z "$selection" ]] && return 1 + + local name + name=$(echo "$selection" | awk '{print $1}') + id=$(echo "$devices" | jq -r --arg n "$name" '.devices[] | select(.name==$n) | .id') + echo "$id" +} + +action_toggle() { + local devices id name authorized new_value action_label + + devices=$(gum spin --title "Lade Geräteliste..." --show-output -- bash -c "curl -sf -u '${API_KEY}:' '${API_BASE}/tailnet/${TAILNET}/devices'") \ + || { gum style --foreground 1 "Konnte Geräteliste nicht laden."; return; } + + id=$(choose_device "$devices") || return + [[ -z "$id" ]] && return + + name=$(echo "$devices" | jq -r --arg id "$id" '.devices[] | select(.id==$id) | .name') + authorized=$(echo "$devices" | jq -r --arg id "$id" '.devices[] | select(.id==$id) | .authorized') + + if [[ "$authorized" == "true" ]]; then + new_value="false" + action_label="sperren" + else + new_value="true" + action_label="entsperren" + fi + + if gum confirm "Gerät '$name' jetzt $action_label?"; then + gum spin --title "Setze Status..." -- \ + curl -sf -u "${API_KEY}:" -H "Content-Type: application/json" \ + -X POST -d "{\"authorized\": ${new_value}}" \ + "${API_BASE}/device/${id}/authorized" >/dev/null + + if [[ "$new_value" == "false" ]]; then + gum style --foreground 1 --bold "⛔ '$name' wurde gesperrt." + else + gum style --foreground 2 --bold "✅ '$name' wurde entsperrt." + fi + else + gum style --foreground 3 "Abgebrochen." + fi +} + +action_list() { + local devices + devices=$(gum spin --title "Lade Geräteliste..." --show-output -- bash -c "curl -sf -u '${API_KEY}:' '${API_BASE}/tailnet/${TAILNET}/devices'") \ + || { gum style --foreground 1 "Konnte Geräteliste nicht laden."; return; } + + echo "$devices" | jq -r ' + ["NAME","OS","STATUS"], + (.devices[] | [.name, .os, (if .authorized then "aktiv" else "GESPERRT" end)]) + | @tsv' | gum table --separator=$'\t' --columns "Name,OS,Status" +} + +main_menu() { + while true; do + clear + gum style --border rounded --padding "1 2" --border-foreground 6 \ + "🔒 Tailscale Geräteverwaltung — Tailnet: ${TAILNET}" + + choice=$(gum choose \ + "Gerät sperren/entsperren" \ + "Geräteliste anzeigen" \ + "Beenden" \ + --header "Was möchtest du tun?") + + case "$choice" in + "Gerät sperren/entsperren") + action_toggle + gum input --placeholder "Enter drücken zum Fortfahren..." >/dev/null || true + ;; + "Geräteliste anzeigen") + action_list + gum input --placeholder "Enter drücken zum Fortfahren..." >/dev/null || true + ;; + "Beenden"|"") + echo "Bis dann 👋" + exit 0 + ;; + esac + done +} + +main_menu \ No newline at end of file