diff --git a/.env b/.env new file mode 100644 index 0000000..c5a32f8 --- /dev/null +++ b/.env @@ -0,0 +1,9 @@ +# Crumbpages Doktor Configuration +# API Setup +CRUMB_API_URL="http://localhost:8000" +CRUMB_API_TOKEN="changeme" + +# Remote / SSH +CRUMB_SSH_USER="admin" +CRUMB_SCP_TARGET="backup.crumbforest.de:/var/backups" + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78dae5c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Crumbpages Doktor Reports +dns_report_*.txt +system_report_*.txt diff --git a/HANDBUCH.md b/HANDBUCH.md new file mode 100644 index 0000000..afdeccb --- /dev/null +++ b/HANDBUCH.md @@ -0,0 +1,103 @@ +# 🦉 Crumbpages Doktor - Handbuch (v2) + +**Version:** RC1 +**Autor:** Crystal Owl 🦉 +**System:** Linux / macOS + +--- + +## 📖 Einführung + +Der **Crumbpages Doktor** ist ein modulares Skript zur Wartung und Diagnose der Crumbforest-Infrastruktur. Es vereint System-Checks, Git-Workflows und API-Testing in einem einheitlichen Interface. + +> *"Ein aufgeräumter Wald ist ein sicherer Wald."* + +--- + +## ⚙️ Konfiguration (.env) + +Das Skript lädt Variablen aus einer `.env` Datei im gleichen Verzeichnis. + +**Beispiel-Konfiguration:** +```ini +# API Setup +CRUMB_API_URL="http://localhost:8000" +CRUMB_API_TOKEN="changeme" # Setze auf "changeme" oder leer für Public Mode (kein Header) + +# Remote / SSH +CRUMB_SSH_USER="admin" +CRUMB_SCP_TARGET="backup.crumbforest.de:/var/backups" +``` + +--- + +## 🌲 Git Workstation + +Startet eine Subshell speziell für Git-Arbeiten. + +* **Prompt:** Zeigt `(🌲 Git) user@host` an. +* **Aliase:** + * `gst` -> `git status` + * `gd` -> `git diff` + * `gl` -> `git log --graph ...` +* **Funktionen:** + * `check_health`: Prüft Sync-Status mit Remote (automatischer `git remote update`). + +--- + +## 🕸️ Web Tools (API) + +Die Schnittstelle zum Crumbcore. Ideal für RAG-Tests und API-Debugging. + +* **Features:** + * Automatische Authentifizierung (Bearer Token via `.env`). + * JSON-Output Optimierung (stderr piping für `jq`). + * Browser-Integration. +* **Befehle:** + * `api_search [limit]`: + Sucht im Vektor-Speicher. Sendet GET an `/api/documents/search`. + *Beispiel:* `api_search "nullfeld" | jq` + * `api_get `: + Generischer GET Request. + *Beispiel:* `api_get health` oder `api_get /__routes` + * `open_url `: + Öffnet die URL im Standard-Browser des Systems (`open` oder `xdg-open`). + +--- + +## 🔐 Remote Tools + +Helfer für SSH-Verbindungen und Dateitransfers. + +* **Befehle:** + * `my_keys`: Listet lokale Public Keys auf (zur Weitergabe an Admins). + * `ssh_config`: Öffnet die SSH Config im Editor. + * `crumbupload `: Lädt eine Datei via SCP auf das in `.env` definierte Ziel (`CRUMB_SCP_TARGET`). + +--- + +## 🌐 DNS & 🖥️ System Doktor + +Diese Module erstellen **Text-Reports** mit Zeitstempel zur Archivierung. + +* **DNS Doktor:** + * Prüft A-Records (Lokal vs. 8.8.8.8). + * Analysiert MX (Mail) und TXT (SPF/DKIM) Records. + * Führt einen vollständigen Trace (`dig +trace`) durch. +* **System Doktor:** + * Erkennt automatisch Linux oder macOS. + * Sammelt Hostname, Kernel-Version, Disk-Usage (`df -h`) und Memory (`free -h`). + +--- + +## ❓ Troubleshooting + +**Problem: `api_ask` liefert JSON Fehler.** +* Lösung: Stelle sicher, dass du die RC1 Version benutzt. Alte Versionen hatten Probleme mit Anführungszeichen in Fragen. +* Workaround: Einfache Anführungszeichen für den String nutzen: `api_ask 1 'Frage mit "Quotes"'`. + +**Problem: `.env` wird nicht geladen.** +* Lösung: Die Datei muss `.env` heißen und im selben Ordner liegen, in dem du das Skript ausführst. + +--- +*Crumbpages v1 - Documentation* diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..b2aa7db --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,49 @@ +# 🦉 Crumbpages Doktor - Schnellstart + +> *"Der Arztkoffer für den Crumbforest."* + +Der **Crumbpages Doktor** (`crumbpages-doktor.sh`) ist dein Schweizer Taschenmesser für System-Diagnose, Git-Management und API-Tests. + +## 🚀 Installation & Start + +1. **Ausführbar machen:** + ```bash + chmod +x crumbpages-doktor.sh + ``` + +2. **Starten:** + ```bash + ./crumbpages-doktor.sh + ``` + *Beim ersten Start wird automatisch eine `.env` Datei erstellt (Template).* + +3. **Setup (`.env` anpassen):** + Öffne die `.env` Datei und trage ggf. dein API-Token ein: + ```bash + CRUMB_API_URL="http://localhost:8000" + CRUMB_API_TOKEN="dein-token-hier-oder-leer-lassen" + ``` + +## 🛠️ Module im Überblick + +| Modul | Beschreibung | +| :--- | :--- | +| **🌲 Git Workstation** | Interaktive Shell für Git. Mit Status-Anzeige (`gst`, `gd`...). | +| **🌐 DNS Doktor** | Prüft DNS Records (A, MX, SPF) für eine Domain. Erstellt Reports. | +| **🖥️ System Doktor** | Zeigt Host-Vitalwerte (Disk, RAM, OS, Kernel). | +| **🕸️ Web Tools** | API Tests mit `curl` Wrappern (`api_search`, `api_ask`, `api_get`). | +| **🔐 Remote Tools** | SSH Helper und SCP Uploads (`crumbupload`). | +| **🛠️ Werkzeugkasten** | Prüft installierte Tools (`htop`, `tree`, etc.) und bietet Shell-Aliase. | + +## 💡 Top Befehle (One-Liner) + +In der **Web Tools** Shell (Option 4): + +* **RAG durchsuchen (JSON):** + ```bash + api_search "docker" + ``` +* **Browser öffnen:** + ```bash + open_url http://localhost:8000/docs + ``` diff --git a/crumbpages-doktor.sh b/crumbpages-doktor.sh index 79cb17f..682f447 100755 --- a/crumbpages-doktor.sh +++ b/crumbpages-doktor.sh @@ -1,5 +1,6 @@ #!/bin/bash -# 🦉 Crumbpages Doktor - Der Wald-Sanitäter für dein Repo +# 🦉 Crumbpages Doktor v2 - Der Wald-Sanitäter für dein Repo +# Mit .env Support und spezialisierten Shells # Farben für schöne Ausgabe GREEN='\033[0;32m' @@ -10,84 +11,236 @@ CYAN='\033[0;36m' NC='\033[0m' # No Color # ----------------------------------------------------------------------------- -# Modul: Git Doktor +# Infrastruktur: Environment +# ----------------------------------------------------------------------------- +ENV_FILE=".env" + +function check_env() { + if [ ! -f "$ENV_FILE" ]; then + echo -e "${YELLOW}Keine .env Datei gefunden. Erstelle Template...${NC}" + cat > "$ENV_FILE" << EOF +# Crumbpages Doktor Configuration +# API Setup +CRUMB_API_URL="https://api.crumbforest.de" +CRUMB_API_TOKEN="changeme" + +# Remote / SSH +CRUMB_SSH_USER="admin" +CRUMB_SCP_TARGET="backup.crumbforest.de:/var/backups" + +EOF + echo -e "${GREEN}.env Template erstellt. Bitte anpassen!${NC}" + sleep 2 + fi + + # Load Env + source "$ENV_FILE" +} + +# Starte Env Check +check_env + +# ----------------------------------------------------------------------------- +# Modul: Git Doktor (Interactive Shell) # ----------------------------------------------------------------------------- function git_doktor() { - echo -e "${BLUE}--- 🌲 Git Doktor: Vitalwerte prüfen ---${NC}" + echo -e "${BLUE}--- 🌲 Git Workstation ---${NC}" + export GIT_RC="/tmp/git_doktor.rc" - # 1. Check: Sind wir in einem Git Repo? - if [ ! -d ".git" ]; then - echo -e "${RED}❌ Fehler: Wir sind nicht im Wald (kein .git Ordner hier).${NC}" - echo "Bitte führe mich im Hauptverzeichnis deines Projekts aus." - return - fi - - # 2. Check: Remote Status - echo -n "Prüfe Verbindung zum Basislager... " + cat > "$GIT_RC" << 'EOF' +if [ -f /etc/bashrc ]; then source /etc/bashrc; fi +if [ -f ~/.bashrc ]; then source ~/.bashrc; fi + +export PS1="\[\033[0;34m\](🌲 Git) \u@\h:\w$ \[\033[0m\]" + +function check_health() { + echo "Prüfe Repo Gesundheit..." + if [ ! -d ".git" ]; then echo "❌ Kein Git Repo!"; return; fi git remote update > /dev/null 2>&1 - if [ $? -eq 0 ]; then - echo -e "${GREEN}OK${NC}" - - # Check ob wir hinterher hinken - LOCAL=$(git rev-parse @) - REMOTE=$(git rev-parse @{u}) - - if [ $LOCAL = $REMOTE ]; then - echo -e "${GREEN}✅ Alles synchron.${NC}" - else - echo -e "${YELLOW}⚠️ Unterschiede zum Server erkannt.${NC}" - fi + LOCAL=$(git rev-parse @) + REMOTE=$(git rev-parse @{u} 2>/dev/null) + + if [ "$LOCAL" = "$REMOTE" ]; then + echo "✅ Synchron." else - echo -e "${RED}Fehler (Kein Netz?)${NC}" + echo "⚠️ Nicht synchron / Push needed." fi + git status --short +} - # 3. Check: Lokale Änderungen - if [ -z "$(git status --porcelain)" ]; then - echo -e "${GREEN}✅ Der Wald ist ruhig. Keine Änderungen zu speichern.${NC}" - else - echo -e "${YELLOW}📝 Es gibt ungespeicherte Änderungen:${NC}" - git status --short - - # 4. Action: Update durchführen? - echo "" - echo -e "${BLUE}Soll ich ein Update durchführen (add + commit + push)?${NC}" - read -p "[j]a / [n]ein: " -n 1 -r REPLY_UPDATE - echo "" +alias gst="git status" +alias gd="git diff" +alias gl="git log --oneline --graph --decorate --all -n 10" - if [[ $REPLY_UPDATE =~ ^[Jj]$ ]]; then - echo "" - echo -e "${BLUE}💊 Verarzte Dateien... (git add)${NC}" - git add . - - echo -e "${BLUE}✍️ Bitte Commit-Nachricht eingeben (Enter für Standard):${NC}" - read COMMIT_MSG - if [ -z "$COMMIT_MSG" ]; then - COMMIT_MSG="update: crumbpages maintenance 🌲" - fi - - echo -e "${BLUE}📦 Schnüre Paket... (git commit)${NC}" - git commit -m "$COMMIT_MSG" - - echo -e "${BLUE}🚀 Sende zum Mutterschiff... (git push)${NC}" - if git push; then - echo "" - echo -e "${GREEN}✅ Operation erfolgreich! Der Wald ist sicher.${NC}" - echo -e " Commit: $COMMIT_MSG" - else - echo "" - echo -e "${RED}❌ Fehler beim Upload. Prüfe deine Verbindung oder Keys.${NC}" - fi - else - echo -e "${BLUE}👋 Alles klar, bis zum nächsten Mal!${NC}" - fi - fi - # Pause vor Rückkehr zum Menü - echo "" - read -p "Drücke Enter um fortzufahren..." +echo "" +echo "Willkommen in der Git Shell." +echo "Befehle: check_health, gst, gd, gl" +echo "" +check_health +EOF + + bash --rcfile "$GIT_RC" + rm -f "$GIT_RC" } # ----------------------------------------------------------------------------- -# Modul: DNS Doktor +# Modul: Web Tools (API / Curl) +# ----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- +# Modul: Web Tools (API / Curl) +# ----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- +# Modul: Web Tools (API / Curl) +# ----------------------------------------------------------------------------- +function web_doktor() { + echo -e "${BLUE}--- 🕸️ Web Tools (API) ---${NC}" + export WEB_RC="/tmp/web_doktor.rc" + + cat > "$WEB_RC" << EOF +if [ -f /etc/bashrc ]; then source /etc/bashrc; fi +if [ -f ~/.bashrc ]; then source ~/.bashrc; fi + +export PS1="\[\033[0;36m\](🕸️ Web) \u@\h:\w$ \[\033[0m\]" +export API_URL="$CRUMB_API_URL" +export TOKEN="$CRUMB_API_TOKEN" + +function api_help() { + echo "Configured API: \$API_URL" + if [ -n "\$TOKEN" ] && [ "\$TOKEN" != "changeme" ]; then + echo "Token: Set (\${TOKEN:0:5}...)" + else + echo "Token: None (Public Mode)" + fi + echo "" + echo "Endpoints:" + echo " GET /api/hello?lang=admin" + echo " POST /api/diary/index" + echo " POST /api/diary/search" + echo " POST /api/diary/search" + echo " GET /api/diary/{child_id}/status" + echo " GET /health" + echo " GET /__routes" + echo "" + echo "Commands:" + echo " api_search -> Search Diary (JSON)" + echo " api_get -> Generic GET (e.g. 'health')" + echo " api_post -> Generic POST (body as JSON string)" + echo " open_url -> Open in Browser" + echo "" + echo "💡 Tipp: Nutze 'jq' für schönes JSON: api_search 'vpn' | jq" +} + +# Helper to build common curl args +function _build_curl_args() { + CURL_ARGS=(-s) + if [ -n "\$TOKEN" ] && [ "\$TOKEN" != "changeme" ]; then + CURL_ARGS+=(-H "Authorization: Bearer \$TOKEN") + fi +} + +function api_get() { + if [ -z "\$1" ]; then echo "Usage: api_get "; return; fi + ENDPOINT="\${1#/}" + + _build_curl_args + curl "\${CURL_ARGS[@]}" "\$API_URL/\$ENDPOINT" +} + +function api_post() { + if [ -z "\$1" ]; then echo "Usage: api_post "; return; fi + ENDPOINT="\${1#/}" + # Use array for body to avoid quoting hell, or just pass strictly + BODY="\${2:-{}}" + + echo "POST \$API_URL/\$ENDPOINT ..." >&2 + + _build_curl_args + CURL_ARGS+=(-X POST -H "Content-Type: application/json") + CURL_ARGS+=(-d "\$BODY") + + curl "\${CURL_ARGS[@]}" "\$API_URL/\$ENDPOINT" +} + +function api_search() { + if [ -z "\$1" ]; then echo "Usage: api_search [limit]"; return; fi + QUERY="\$1" + LIMIT="\${2:-5}" + + if command -v python3 &>/dev/null; then + ENCODED_QUERY=\$(python3 -c "import urllib.parse; print(urllib.parse.quote('''\$QUERY'''))") + else + ENCODED_QUERY="\$QUERY" + fi + + echo "Suche nach '\$QUERY' (Limit: \$LIMIT)..." >&2 + + _build_curl_args + curl "\${CURL_ARGS[@]}" "\$API_URL/api/documents/search?q=\$ENCODED_QUERY&limit=\$LIMIT" +} + +function open_url() { + if [ -z "\$1" ]; then echo "Usage: open_url "; return; fi + if [[ "\$OSTYPE" == "darwin"* ]]; then + open "\$1" + elif [[ "\$OSTYPE" == "linux-gnu"* ]]; then + xdg-open "\$1" 2>/dev/null || echo "Kein Browser gefunden." + else + echo "OS nicht unterstützt für Browser-Start." + fi +} + +alias wget_clean="wget --hsts-file=/tmp/.wget-hsts" + +echo "" +api_help +EOF + + bash --rcfile "$WEB_RC" + rm -f "$WEB_RC" +} + +# ----------------------------------------------------------------------------- +# Modul: Remote Tools (SSH / SCP) +# ----------------------------------------------------------------------------- +function remote_doktor() { + echo -e "${BLUE}--- 🔐 Remote Tools (SSH) ---${NC}" + export REMOTE_RC="/tmp/remote_doktor.rc" + + cat > "$REMOTE_RC" << EOF +if [ -f /etc/bashrc ]; then source /etc/bashrc; fi +if [ -f ~/.bashrc ]; then source ~/.bashrc; fi + +export PS1="\[\033[0;31m\](🔐 Remote) \u@\h:\w$ \[\033[0m\]" +export SSH_USER="$CRUMB_SSH_USER" +export SCP_TARGET="$CRUMB_SCP_TARGET" + +function remote_help() { + echo "User: \$SSH_USER | Target: \$SCP_TARGET" + echo "" + echo "Commands:" + echo " crumbupload -> SCP file to target" + echo " my_keys -> List local public keys" + echo " ssh_config -> Edit SSH config" +} + +function crumbupload() { + if [ -z "\$1" ]; then echo "Usage: crumbupload "; return; fi + scp "\$1" "\$SSH_USER@\$SCP_TARGET" +} + +alias my_keys="ls -l ~/.ssh/*.pub" +alias ssh_config="nano ~/.ssh/config" + +echo "" +remote_help +EOF + + bash --rcfile "$REMOTE_RC" + rm -f "$REMOTE_RC" +} + +# ----------------------------------------------------------------------------- +# Modul: DNS Doktor (Legacy Logic) # ----------------------------------------------------------------------------- function dns_doktor() { echo -e "${BLUE}--- 🌐 DNS Doktor: Namensauflösung ---${NC}" @@ -98,61 +251,81 @@ function dns_doktor() { TIMESTAMP=$(date +"%Y-%m-%d_%H-%M") REPORT_FILE="dns_report_${DOMAIN}_${TIMESTAMP}.txt" - # Hilfsfunktion für Checks check_record() { local type=$1 - local server=$2 # Optional: @8.8.8.8 + local server=$2 local desc=$3 - echo -n "Prüfe $type ($desc)... " - if [ -n "$server" ]; then - RESULT=$(dig $server +short -t $type $DOMAIN 2>&1) - else - RESULT=$(dig +short -t $type $DOMAIN 2>&1) - fi - - if [ -n "$RESULT" ]; then - echo -e "${GREEN}OK${NC}" - echo "$type Records:" >> $REPORT_FILE - echo "$RESULT" >> $REPORT_FILE - echo "---" >> $REPORT_FILE - else - echo -e "${YELLOW}LEER${NC}" - echo "$type Records: LEER" >> $REPORT_FILE - echo "---" >> $REPORT_FILE - fi + if [ -n "$server" ]; then RESULT=$(dig $server +short -t $type $DOMAIN 2>&1); else RESULT=$(dig +short -t $type $DOMAIN 2>&1); fi + if [ -n "$RESULT" ]; then echo -e "${GREEN}OK${NC}"; echo "$type Records:" >> $REPORT_FILE; echo "$RESULT" >> $REPORT_FILE; echo "---" >> $REPORT_FILE; else echo -e "${YELLOW}LEER${NC}"; echo "$type Records: LEER" >> $REPORT_FILE; echo "---" >> $REPORT_FILE; fi } echo "Erstelle Bericht in: $REPORT_FILE" echo "DNS Report für $DOMAIN ($TIMESTAMP)" > $REPORT_FILE - echo "========================================" >> $REPORT_FILE - echo "" - - # 1. Lokale Auflösung (A Record) - check_record "A" "" "Lokal via System Resolver" - # 2. Externe Auflösung (Google DNS) - check_record "A" "@8.8.8.8" "Extern via 8.8.8.8" + check_record "A" "" "Lokal" + check_record "A" "@8.8.8.8" "Google" + check_record "MX" "" "MX" + check_record "SOA" "" "SOA" + check_record "TXT" "" "SPF" - # 3. MX Records (Mail) - check_record "MX" "" "Mail Exchange" - - # 4. SOA Record (Zone Authority) - check_record "SOA" "" "Start of Authority" - - # 5. TXT Records (SPF/DKIM) - check_record "TXT" "" "Text / SPF" - - echo -e "\n${BLUE}🔍 Starte Trace (kann dauern)...${NC}" - echo "TRACE:" >> $REPORT_FILE + echo -e "\n${BLUE}🔍 Starte Trace...${NC}" dig +trace +short $DOMAIN >> $REPORT_FILE 2>&1 - echo "Trace abgeschlossen." + echo -e "${GREEN}✅ Fertig.${NC} Bericht: $REPORT_FILE" + read -p "Enter..." +} + +# ----------------------------------------------------------------------------- +# Modul: System Doktor +# ----------------------------------------------------------------------------- +function system_doktor() { + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo -e "${BLUE}--- 🐧 Linux Doktor ---${NC}" + hostnamectl 2>/dev/null || hostname; uname -r; free -h; df -h / + else + echo -e "${BLUE}--- 🍏 Mac Doktor ---${NC}" + hostname; sw_vers; vm_stat | head -n 5; df -h / + fi + echo "" + read -p "Enter..." +} + +# ----------------------------------------------------------------------------- +# Modul: Werkzeugkasten (General) +# ----------------------------------------------------------------------------- +function tools_doktor() { + export TOOL_RC="/tmp/general_doktor.rc" + cat > "$TOOL_RC" << 'EOF' +if [ -f /etc/bashrc ]; then source /etc/bashrc; fi +if [ -f ~/.bashrc ]; then source ~/.bashrc; fi + +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' + +export PS1="\[\033[0;33m\](🛠️ Tools) \u@\h:\w$ \[\033[0m\]" + +function doktor_help() { + echo "Verfügbare Werkzeuge im Wald:" + echo "-----------------------------" + TOOLS="htop ncdu tree mc nano vim curl git ip" - echo "" - echo -e "${GREEN}✅ Diagnose abgeschlossen.${NC}" - echo -e "Bericht gespeichert unter: ${CYAN}$REPORT_FILE${NC}" - echo "" - read -p "Drücke Enter um fortzufahren..." + for tool in $TOOLS; do + if command -v $tool &> /dev/null; then + echo -e "${GREEN}✅ $tool${NC}" + else + echo -e "${RED}❌ $tool (Nicht installiert)${NC}" + fi + done +} + +echo "" +echo "Willkommen im Allgemeinen Werkzeugkasten!" +doktor_help +echo "" +EOF + bash --rcfile "$TOOL_RC" + rm -f "$TOOL_RC" } # ----------------------------------------------------------------------------- @@ -160,28 +333,26 @@ function dns_doktor() { # ----------------------------------------------------------------------------- while true; do clear - echo -e "${BLUE}🦉 Crumbpages Doktor${NC}" + echo -e "${BLUE}🦉 Crumbpages Doktor v2${NC}" echo "---------------------------" - echo "1) 🌲 Git Doktor (Repo Check)" - echo "2) 🌐 DNS Doktor (Domain Check)" - echo "3) 👋 Beenden" + echo "1) 🌲 Git Workstation (Shell)" + echo "2) 🌐 DNS Doktor (Diagnose)" + echo "3) 🖥️ System Doktor (Diagnose)" + echo "4) 🕸️ Web Tools (API / Curl)" + echo "5) 🔐 Remote Tools (SSH / SCP)" + echo "6) 🛠️ General Tools (Shell)" + echo "7) 👋 Beenden" echo "" - read -p "Auswahl [1-3]: " CHOICE + read -p "Auswahl [1-7]: " CHOICE case $CHOICE in - 1) - git_doktor - ;; - 2) - dns_doktor - ;; - 3) - echo "Ahoi!" - exit 0 - ;; - *) - echo "Bitte 1, 2 oder 3 wählen." - sleep 1 - ;; + 1) git_doktor ;; + 2) dns_doktor ;; + 3) system_doktor ;; + 4) web_doktor ;; + 5) remote_doktor ;; + 6) tools_doktor ;; + 7) echo "Ahoi!"; exit 0 ;; + *) echo "Bitte 1-7 wählen."; sleep 1 ;; esac done