diff --git a/GeoTurtl_install_v0.9.7-beta.sh b/GeoTurtl_install_v0.9.7-beta.sh
new file mode 100644
index 0000000..af0940f
--- /dev/null
+++ b/GeoTurtl_install_v0.9.7-beta.sh
@@ -0,0 +1,833 @@
+#!/bin/zsh
+# =============================================================================
+# GeoTurtl_install_v0.9.7-beta.sh
+# Selbstentpackendes Installationsscript für GeoTurtl v0.9.7-beta
+#
+# VERWENDUNG:
+# chmod +x ~/Downloads/GeoTurtl_v0.9.7-beta/GeoTurtl_install_v0.9.7-beta.sh
+# ~/Downloads/GeoTurtl_v0.9.7-beta/GeoTurtl_install_v0.9.7-beta.sh
+# =============================================================================
+
+set -e
+
+VERSION="0.9.7-beta"
+TARGET="/Applications/Scripte/python/GeoTurtl"
+SCRIPT="$TARGET/GeoTurtl.py"
+README_HTML="$TARGET/README.html"
+README_MD="$TARGET/README.md"
+STARTER="$TARGET/geoturtl_start.sh"
+STOPPER="$TARGET/geoturtl_stop.sh"
+PLIST_DIR="$HOME/Library/LaunchAgents"
+PLIST="$PLIST_DIR/com.user.geoturtl.plist"
+LOGFILE="$HOME/Library/Logs/GeoTurtl.log"
+PIDFILE="$HOME/Library/Application Support/GeoTurtl.pid"
+
+# Ordner des Installationsscripts (hier liegen die README-Dateien)
+INSTALL_DIR="$(cd "$(dirname "$0")" && pwd)"
+
+echo ""
+echo "╔══════════════════════════════════════════════════╗"
+echo "║ 🐢 GeoTurtl v$VERSION – Installation ║"
+echo "╚══════════════════════════════════════════════════╝"
+echo ""
+
+# ── Haftungsausschluss ────────────────────────────────────────────────────────
+echo "┌─────────────────────────────────────────────────────┐"
+echo "│ ⚠️ HAFTUNGSAUSSCHLUSS │"
+echo "├─────────────────────────────────────────────────────┤"
+echo "│ GeoTurtl wurde nach bestem Wissen und Gewissen │"
+echo "│ entwickelt und kostenlos zur Verfügung gestellt. │"
+echo "│ │"
+echo "│ • Die Software wird 'so wie sie ist' (as is) │"
+echo "│ bereitgestellt – ohne jegliche Garantie. │"
+echo "│ • H. Linz übernimmt keine Haftung für Schäden, │"
+echo "│ Datenverluste oder Beeinträchtigungen jeglicher │"
+echo "│ Art durch Nutzung dieser Software. │"
+echo "│ • Drittdienste (ipinfo.io, ipify.org) liegen │"
+echo "│ außerhalb der Verantwortung des Autors. │"
+echo "│ • Jede Person nutzt diese Software auf eigene │"
+echo "│ Gefahr und eigene Verantwortung. │"
+echo "│ │"
+echo "│ Durch Fortfahren erkennst du diese Bedingungen an. │"
+echo "└─────────────────────────────────────────────────────┘"
+echo ""
+echo -n " Installation fortsetzen? [j/N] "
+read CONFIRM
+if [[ "$CONFIRM" != "j" && "$CONFIRM" != "J" ]]; then
+ echo " Installation abgebrochen."
+ exit 0
+fi
+echo ""
+
+# ── Alte Versionen entfernen ──────────────────────────────────────────────────
+echo "→ Prüfe auf alte Versionen …"
+
+# Alle laufenden ddIP/GeoTurtl-Prozesse beenden
+for PROCNAME in "ddIP.py" "GeoTurtl.py"; do
+ PIDS=$(pgrep -f "$PROCNAME" 2>/dev/null || true)
+ if [ -n "$PIDS" ]; then
+ echo " ✓ Beende laufende Instanz: $PROCNAME (PID $PIDS)"
+ pkill -f "$PROCNAME" 2>/dev/null || true
+ sleep 1
+ fi
+done
+
+# Alte PID-Files entfernen
+for PIDFILE in "$HOME/Library/Application Support/ddIP.pid" "$HOME/Library/Application Support/GeoTurtl.pid"; do
+ [ -f "$PIDFILE" ] && rm -f "$PIDFILE" && echo " ✓ PID-File entfernt: $PIDFILE"
+done
+
+# Alte Lock-Files entfernen
+for LOCKFILE in "$HOME/Library/Application Support/GeoTurtl.lock"; do
+ [ -f "$LOCKFILE" ] && rm -f "$LOCKFILE" && echo " ✓ Lock-File entfernt: $LOCKFILE"
+done
+
+# Alte launchd-Agenten entfernen
+for PLIST in "$HOME/Library/LaunchAgents/com.user.ddip.plist" "$HOME/Library/LaunchAgents/com.user.geoturtl.plist"; do
+ if [ -f "$PLIST" ]; then
+ launchctl unload "$PLIST" 2>/dev/null || true
+ rm -f "$PLIST"
+ echo " ✓ launchd-Agent entfernt: $(basename $PLIST)"
+ fi
+done
+
+# Alte Programmordner entfernen
+for DIR in "/Applications/Scripte/python/ddIP" "/Applications/Scripte/python/GeoTurtl"; do
+ if [ -d "$DIR" ] && [ "$DIR" != "$TARGET" ]; then
+ rm -rf "$DIR"
+ echo " ✓ Alter Programmordner entfernt: $DIR"
+ fi
+done
+
+# Alte Einzeldateien entfernen (ddIP ohne Unterordner)
+for FILE in "/Applications/Scripte/python/ddIP.py" "/Applications/Scripte/python/ddip_start.sh" "/Applications/Scripte/python/ddip_stop.sh" "/Applications/Scripte/python/geoturtl_start.sh" "/Applications/Scripte/python/geoturtl_stop.sh"; do
+ if [ -f "$FILE" ]; then
+ rm -f "$FILE"
+ echo " ✓ Alte Datei entfernt: $FILE"
+ fi
+done
+
+# Alte Aliases aus ~/.zshrc entfernen
+if grep -qE "alias (ddip|geoturtl)" ~/.zshrc 2>/dev/null; then
+ sed -i '' '/alias ddip/d' ~/.zshrc
+ sed -i '' '/alias geoturtl/d' ~/.zshrc
+ echo " ✓ Alte Aliases aus ~/.zshrc entfernt"
+fi
+
+echo " ✓ Aufräumen abgeschlossen"
+echo ""
+
+# ── Python-Pakete ─────────────────────────────────────────────────────────────
+echo "→ Installiere Python-Pakete …"
+pip3 install --quiet rumps pyobjc-framework-CoreLocation
+echo " ✓ Pakete installiert"
+
+# ── Zielordner ────────────────────────────────────────────────────────────────
+if [ ! -d "$TARGET" ]; then
+ echo "→ Lege Ordner an: $TARGET"
+ mkdir -p "$TARGET"
+else
+ echo "→ Ordner existiert bereits: $TARGET"
+fi
+
+# ── README-Dateien kopieren (liegen neben dem Installationsscript) ────────────
+echo "→ Kopiere Dokumentation …"
+if [ -f "$INSTALL_DIR/README.html" ]; then
+ cp "$INSTALL_DIR/README.html" "$README_HTML"
+ echo " ✓ README.html kopiert"
+else
+ echo " ⚠️ README.html nicht gefunden – wird übersprungen"
+fi
+if [ -f "$INSTALL_DIR/README.md" ]; then
+ cp "$INSTALL_DIR/README.md" "$README_MD"
+ echo " ✓ README.md kopiert"
+else
+ echo " ⚠️ README.md nicht gefunden – wird übersprungen"
+fi
+
+# ── GeoTurtl.py erzeugen ─────────────────────────────────────────────────────
+echo "→ Erzeuge GeoTurtl.py …"
+cat > "$SCRIPT" << 'PYEOF'
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# =============================================================================
+# yaml
+# name: GeoTurtl
+# version: 0.9.7-beta
+# description: macOS Menüleisten-App – Standort + öffentliche IP-Adressen
+# author: H. Linz
+# contact: linz.on.tour@gmail.com
+# license: MIT
+# requires_python: ">=3.6"
+# dependencies:
+# - rumps
+# - pyobjc-framework-CoreLocation
+# platform: macOS (Apple Silicon & Intel)
+# created: 2026-06-07
+# disclaimer: >
+# Diese Software wird ohne jegliche Garantie bereitgestellt.
+# Nutzung auf eigene Gefahr. Keine Haftung für Schäden.
+# =============================================================================
+
+import json
+import os
+import socket
+import subprocess
+import sys
+import threading
+import time
+import urllib.request
+from datetime import datetime
+
+import objc
+import rumps
+from CoreLocation import CLGeocoder, CLLocation, CLLocationManager
+from Foundation import NSBundle, NSDate, NSRunLoop
+
+APP_TITLE = "🐢"
+APP_VERSION = "0.9.7-beta"
+AUTHOR = "H. Linz"
+CONTACT = "linz.on.tour@gmail.com"
+UPDATE_INTERVALS = {"Aus": 0, "1 Minute": 60, "5 Minuten": 300, "15 Minuten": 900}
+DEFAULT_INTERVAL = "5 Minuten"
+TIMEOUT = 8
+LOC_TIMEOUT = 15
+README_HTML = os.path.join(os.path.dirname(os.path.abspath(__file__)), "README.html")
+
+
+# ── CoreLocation Delegate ──────────────────────────────────────────────────────
+
+class LocationDelegate(objc.lookUpClass("NSObject")):
+ def init(self):
+ self = objc.super(LocationDelegate, self).init()
+ if self is None:
+ return None
+ self.location = None
+ self.error = None
+ self.done = threading.Event()
+ return self
+
+ def locationManager_didUpdateLocations_(self, manager, locations):
+ if locations:
+ self.location = locations[-1]
+ manager.stopUpdatingLocation()
+ self.done.set()
+
+ def locationManager_didFailWithError_(self, manager, error):
+ self.error = str(error)
+ manager.stopUpdatingLocation()
+ self.done.set()
+
+ def locationManagerDidChangeAuthorization_(self, manager):
+ status = manager.authorizationStatus()
+ if status in (1, 2):
+ self.error = "Ortungsdienste verweigert"
+ self.done.set()
+
+
+def get_location(retries=4, delay=3):
+ """
+ Versucht den Standort zu ermitteln. Bei kCLErrorLocationUnknown (Code=0)
+ wird bis zu `retries`-mal neu gestartet – typisch wenn WLAN-Ortung
+ kurz nach dem Login noch nicht bereit ist.
+
+ Fehlercodes:
+ Code=0 LocationUnknown – kurz warten, nochmal versuchen
+ Code=1 Denied – Ortungsdienste verweigert
+ Code=2 Network – Netzwerkfehler bei Ortung
+ """
+ for attempt in range(1, retries + 1):
+ delegate = LocationDelegate.alloc().init()
+ manager = CLLocationManager.alloc().init()
+ manager.setDelegate_(delegate)
+ manager.setDesiredAccuracy_(10)
+ manager.requestWhenInUseAuthorization()
+ manager.startUpdatingLocation()
+ deadline = time.time() + LOC_TIMEOUT
+ while not delegate.done.is_set() and time.time() < deadline:
+ NSRunLoop.currentRunLoop().runUntilDate_(NSDate.dateWithTimeIntervalSinceNow_(0.2))
+
+ if not delegate.done.is_set():
+ raise TimeoutError(
+ f"Standort-Timeout nach {LOC_TIMEOUT}s (Versuch {attempt}/{retries})")
+
+ if delegate.error:
+ err = delegate.error
+ # Code=0: vorübergehend unbekannt → retry
+ if "Code=0" in err:
+ if attempt < retries:
+ _log(f"Standort: kCLErrorLocationUnknown (Versuch {attempt}/{retries}) "
+ f"– warte {delay}s …", "WARN")
+ time.sleep(delay)
+ continue
+ raise RuntimeError(
+ "Standort nach mehreren Versuchen nicht ermittelbar "
+ "(WLAN-Ortung liefert kein Signal)")
+ # Code=1: Berechtigung verweigert
+ elif "Code=1" in err:
+ raise RuntimeError(
+ "Ortungsdienste verweigert – bitte in Systemeinstellungen "
+ "→ Datenschutz & Sicherheit → Ortungsdienste → Terminal aktivieren")
+ # Code=2: Netzwerkfehler
+ elif "Code=2" in err:
+ raise RuntimeError(
+ "Netzwerkfehler bei Standortermittlung (kCLErrorNetwork)")
+ else:
+ raise RuntimeError(f"CoreLocation-Fehler: {err}")
+
+ return delegate.location
+ raise RuntimeError("Standort nach mehreren Versuchen nicht ermittelbar")
+
+
+def reverse_geocode(lat, lon):
+ result = {"address": None, "done": threading.Event()}
+ cl_loc = CLLocation.alloc().initWithLatitude_longitude_(lat, lon)
+ geocoder = CLGeocoder.alloc().init()
+
+ def handler(placemarks, error):
+ if placemarks:
+ pm = placemarks[0]
+ parts = []
+ if pm.thoroughfare():
+ street = pm.thoroughfare()
+ if pm.subThoroughfare():
+ street += f" {pm.subThoroughfare()}"
+ parts.append(street)
+ if pm.postalCode():
+ parts.append(pm.postalCode())
+ if pm.locality():
+ parts.append(pm.locality())
+ result["address"] = ", ".join(parts) if parts else "Unbekannte Adresse"
+ else:
+ result["address"] = "Adresse nicht verfügbar"
+ result["done"].set()
+
+ geocoder.reverseGeocodeLocation_completionHandler_(cl_loc, handler)
+ deadline = time.time() + 10
+ while not result["done"].is_set() and time.time() < deadline:
+ NSRunLoop.currentRunLoop().runUntilDate_(NSDate.dateWithTimeIntervalSinceNow_(0.2))
+ return result.get("address") or "—"
+
+
+# ── IP-Dienste ─────────────────────────────────────────────────────────────────
+
+IPV4_SERVICES = [
+ {"url": "https://ipinfo.io/json",
+ "parse": lambda d: {"ip": d.get("ip"), "org": d.get("org"),
+ "city": d.get("city"), "region": d.get("region"),
+ "country": d.get("country")}},
+ {"url": "https://api4.ipify.org?format=json",
+ "parse": lambda d: {"ip": d.get("ip"), "org": None,
+ "city": None, "region": None, "country": None}},
+]
+IPV6_SERVICES = [
+ {"url": "https://api6.ipify.org?format=json",
+ "parse": lambda d: {"ip": d.get("ip")}},
+ {"url": "https://ipv6.icanhazip.com", "raw": True,
+ "parse": lambda d: {"ip": d.get("ip")}},
+]
+
+
+def fetch_ip(service):
+ try:
+ req = urllib.request.Request(
+ service["url"],
+ headers={"User-Agent": "GeoTurtl/0.9.7-beta (macOS; python3)"})
+ with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
+ body = resp.read().decode().strip()
+ return service["parse"]({"ip": body} if service.get("raw") else json.loads(body))
+ except Exception:
+ return None
+
+
+def get_ips():
+ v4, v6 = None, None
+
+ def do_v4():
+ nonlocal v4
+ for svc in IPV4_SERVICES:
+ r = fetch_ip(svc)
+ if r and r.get("ip"):
+ v4 = r; break
+
+ def do_v6():
+ nonlocal v6
+ for svc in IPV6_SERVICES:
+ r = fetch_ip(svc)
+ if r and r.get("ip"):
+ v6 = r; break
+
+ t4, t6 = threading.Thread(target=do_v4), threading.Thread(target=do_v6)
+ t4.start(); t6.start()
+ t4.join(); t6.join()
+ return v4, v6
+
+
+# ── Logging ───────────────────────────────────────────────────────────────────
+
+def _log(msg, level="INFO"):
+ """Schreibt eine Zeile mit Zeitstempel und Level nach stdout (→ Log-Datei)."""
+ ts = datetime.now().strftime("%d.%m.%Y %H:%M:%S")
+ print(f"[{ts}] [{level}] {msg}")
+ sys.stdout.flush()
+
+
+# ── Netzwerk-Diagnose ──────────────────────────────────────────────────────────
+
+def check_internet():
+ """
+ Gestaffelte Netzwerkprüfung:
+ 1. TCP-Verbindung zu 8.8.8.8:53 → grundlegende IP-Konnektivität
+ 2. DNS-Auflösung von google.com → DNS funktioniert
+ 3. HTTP-Request zu ipinfo.io → Internet + Firewall OK
+ Gibt (ok: bool, detail: str) zurück.
+ """
+ # Stufe 1: TCP
+ try:
+ sock = socket.create_connection(("8.8.8.8", 53), timeout=3)
+ sock.close()
+ except OSError:
+ return False, "Keine Netzwerkverbindung (TCP zu 8.8.8.8:53 fehlgeschlagen)"
+
+ # Stufe 2: DNS
+ try:
+ socket.getaddrinfo("google.com", 80)
+ except socket.gaierror:
+ return False, "Netzwerk erreichbar, aber DNS nicht funktionsfähig"
+
+ # Stufe 3: HTTP
+ try:
+ req = urllib.request.Request(
+ "https://ipinfo.io/json",
+ headers={"User-Agent": "GeoTurtl/0.9.7-beta (macOS; python3)"})
+ with urllib.request.urlopen(req, timeout=5):
+ pass
+ except Exception as e:
+ return False, f"DNS OK, aber HTTP-Anfragen blockiert oder kein Internet ({e})"
+
+ return True, "OK"
+
+
+def diagnose_network():
+ """Führt Netzwerkdiagnose durch und gibt lesbaren Status zurück."""
+ ok, detail = check_internet()
+ if ok:
+ _log("Netzwerk: Verbindung OK")
+ else:
+ _log(f"Netzwerk: {detail}", "WARN")
+ return ok
+
+
+# ── Menüleisten-App ────────────────────────────────────────────────────────────
+
+class GeoTurtlApp(rumps.App):
+ def __init__(self):
+ super().__init__(APP_TITLE, quit_button=None)
+ self._loc_data = None
+ self._ip_data = None
+ self._updating = False
+ self._interval = DEFAULT_INTERVAL
+ self._timer = None
+ self._v4_str = None
+ self._v6_str = None
+ self._build_menu()
+ threading.Thread(target=self._update_all, daemon=True).start()
+
+ def _build_menu(self):
+ self.menu.clear()
+
+ self.menu.add(rumps.MenuItem("🔄 Alles aktualisieren", callback=self._on_refresh))
+ self.menu.add(rumps.separator)
+
+ # ── Standort ──────────────────────────────────────────────────────────
+ self.menu.add(rumps.MenuItem("📍 STANDORT", callback=None))
+ self._item_address = rumps.MenuItem(" Adresse …")
+ self._item_coords = rumps.MenuItem(" Koordinaten …")
+ self._item_altitude = rumps.MenuItem(" Höhe …")
+ self._item_accuracy = rumps.MenuItem(" Genauigkeit …")
+ self.menu.add(self._item_address)
+ self.menu.add(self._item_coords)
+ self.menu.add(self._item_altitude)
+ self.menu.add(self._item_accuracy)
+ self.menu.add(rumps.separator)
+
+ self._item_gmaps = rumps.MenuItem(" 🗺 Google Maps öffnen", callback=self._open_gmaps)
+ self._item_amaps = rumps.MenuItem(" 🍎 Apple Maps öffnen", callback=self._open_amaps)
+ self._item_osm = rumps.MenuItem(" 🌍 OpenStreetMap öffnen", callback=self._open_osm)
+ self.menu.add(self._item_gmaps)
+ self.menu.add(self._item_amaps)
+ self.menu.add(self._item_osm)
+ self.menu.add(rumps.separator)
+
+ # ── IP-Adressen ───────────────────────────────────────────────────────
+ self.menu.add(rumps.MenuItem("🌐 IP-ADRESSEN", callback=None))
+ self._item_v4 = rumps.MenuItem(" IPv4 …")
+ self._item_v4_loc = rumps.MenuItem(" Standort …")
+ self._item_v4_org = rumps.MenuItem(" Provider …")
+ self._item_copy_v4 = rumps.MenuItem(" 📋 IPv4 kopieren", callback=self._copy_v4)
+ self._item_v6 = rumps.MenuItem(" IPv6 …")
+ self._item_copy_v6 = rumps.MenuItem(" 📋 IPv6 kopieren", callback=self._copy_v6)
+ self.menu.add(self._item_v4)
+ self.menu.add(self._item_v4_loc)
+ self.menu.add(self._item_v4_org)
+ self.menu.add(self._item_copy_v4)
+ self.menu.add(rumps.separator)
+ self.menu.add(self._item_v6)
+ self.menu.add(self._item_copy_v6)
+ self.menu.add(rumps.separator)
+
+ # ── Auto-Update ───────────────────────────────────────────────────────
+ auto_menu = rumps.MenuItem("⏱ Auto-Update")
+ for label in UPDATE_INTERVALS:
+ item = rumps.MenuItem(
+ f" {label}" + (" ✓" if label == self._interval else ""),
+ callback=self._on_interval)
+ auto_menu.add(item)
+ self.menu.add(auto_menu)
+ self.menu.add(rumps.separator)
+
+ # ── Hilfe ─────────────────────────────────────────────────────────────
+ hilfe_menu = rumps.MenuItem("❓ Hilfe")
+ hilfe_menu.add(rumps.MenuItem(f" GeoTurtl v{APP_VERSION}"))
+ hilfe_menu.add(rumps.separator)
+ hilfe_menu.add(rumps.MenuItem(" 📖 Dokumentation öffnen", callback=self._open_readme))
+ hilfe_menu.add(rumps.separator)
+ hilfe_menu.add(rumps.MenuItem(" TERMINAL-BEFEHLE"))
+ hilfe_menu.add(rumps.MenuItem(" ▶ Starten: geoturtl_start"))
+ hilfe_menu.add(rumps.MenuItem(" ■ Stoppen: geoturtl_stop"))
+ hilfe_menu.add(rumps.MenuItem(" 📄 Log: tail -f ~/Library/Logs/GeoTurtl.log"))
+ hilfe_menu.add(rumps.MenuItem(" 📝 Log öffnen", callback=self._open_log))
+ hilfe_menu.add(rumps.separator)
+ hilfe_menu.add(rumps.MenuItem(" KONTAKT"))
+ hilfe_menu.add(rumps.MenuItem(f" {AUTHOR}"))
+ hilfe_menu.add(rumps.MenuItem(f" ✉️ {CONTACT}", callback=self._open_mail))
+ self.menu.add(hilfe_menu)
+ self.menu.add(rumps.separator)
+
+ # ── Zeitstempel + Beenden ─────────────────────────────────────────────
+ self._item_updated = rumps.MenuItem(" Noch nicht aktualisiert")
+ self.menu.add(self._item_updated)
+ self.menu.add(rumps.separator)
+ self.menu.add(rumps.MenuItem("✖ Beenden", callback=self._on_quit))
+
+ # ── Callbacks ─────────────────────────────────────────────────────────────
+
+ def _on_refresh(self, _):
+ if not self._updating:
+ threading.Thread(target=self._update_all, daemon=True).start()
+
+ def _on_interval(self, sender):
+ label = sender.title.strip().rstrip(" ✓").strip()
+ self._interval = label
+ for item in self.menu["⏱ Auto-Update"].values():
+ clean = item.title.strip().rstrip(" ✓").strip()
+ item.title = f" {clean}" + (" ✓" if clean == label else "")
+ self._restart_timer()
+
+ def _on_quit(self, _):
+ rumps.quit_application()
+
+ def _open_readme(self, _):
+ if os.path.exists(README_HTML):
+ os.system(f"open '{README_HTML}'")
+ else:
+ rumps.notification("GeoTurtl", "Dokumentation nicht gefunden",
+ README_HTML)
+
+ def _open_gmaps(self, _):
+ if self._loc_data and "lat" in self._loc_data:
+ lat, lon = self._loc_data["lat"], self._loc_data["lon"]
+ os.system(f"open 'https://maps.google.com/?q={lat},{lon}'")
+
+ def _open_amaps(self, _):
+ if self._loc_data and "lat" in self._loc_data:
+ lat, lon = self._loc_data["lat"], self._loc_data["lon"]
+ os.system(f"open 'https://maps.apple.com/?ll={lat},{lon}&q=GeoTurtl'")
+
+ def _open_osm(self, _):
+ if self._loc_data and "lat" in self._loc_data:
+ lat, lon = self._loc_data["lat"], self._loc_data["lon"]
+ os.system(f"open 'https://www.openstreetmap.org/?mlat={lat}&mlon={lon}&zoom=16'")
+
+ def _open_log(self, _):
+ log = os.path.expanduser("~/Library/Logs/GeoTurtl.log")
+ if not os.path.exists(log):
+ # Leere Log-Datei anlegen damit der Editor sie öffnen kann
+ open(log, "a").close()
+ os.system(f"open -e '{log}'") # -e = immer TextEdit
+
+ def _open_mail(self, _):
+ os.system(f"open 'mailto:{CONTACT}?subject=GeoTurtl%20v{APP_VERSION}'")
+
+ def _copy_v4(self, _):
+ if self._v4_str:
+ subprocess.run("pbcopy", input=self._v4_str.encode(), check=True)
+ rumps.notification("GeoTurtl", "IPv4 kopiert", self._v4_str)
+
+ def _copy_v6(self, _):
+ if self._v6_str:
+ subprocess.run("pbcopy", input=self._v6_str.encode(), check=True)
+ rumps.notification("GeoTurtl", "IPv6 kopiert", self._v6_str)
+
+ # ── Timer ──────────────────────────────────────────────────────────────────
+
+ def _restart_timer(self):
+ if self._timer:
+ self._timer.stop()
+ self._timer = None
+ secs = UPDATE_INTERVALS.get(self._interval, 0)
+ if secs > 0:
+ self._timer = rumps.Timer(self._timer_tick, secs)
+ self._timer.start()
+
+ def _timer_tick(self, _):
+ if not self._updating:
+ threading.Thread(target=self._update_all, daemon=True).start()
+
+ # ── Daten aktualisieren ────────────────────────────────────────────────────
+
+ def _update_all(self):
+ self._updating = True
+ self.title = "🐢 ⟳"
+ loc_thread = threading.Thread(target=self._fetch_location, daemon=True)
+ ip_thread = threading.Thread(target=self._fetch_ips, daemon=True)
+ loc_thread.start(); ip_thread.start()
+ loc_thread.join(); ip_thread.join()
+ self._refresh_menu()
+ self._updating = False
+ self._restart_timer()
+
+ def _fetch_location(self):
+ try:
+ loc = get_location()
+ lat = loc.coordinate().latitude
+ lon = loc.coordinate().longitude
+ self._loc_data = {
+ "lat": lat,
+ "lon": lon,
+ "alt": loc.altitude(),
+ "acc": loc.horizontalAccuracy(),
+ "address": reverse_geocode(lat, lon),
+ }
+ _log(f"Standort: {self._loc_data['address']} "
+ f"({lat:.6f}, {lon:.6f}), "
+ f"Genauigkeit ±{loc.horizontalAccuracy():.0f}m")
+ except Exception as e:
+ _log(f"Standort: {e}", "ERROR")
+ self._loc_data = {"error": str(e)}
+
+ def _fetch_ips(self):
+ v4, v6 = get_ips()
+ self._ip_data = {"v4": v4, "v6": v6}
+ self._v4_str = v4["ip"] if v4 else None
+ self._v6_str = v6["ip"] if v6 else None
+
+ if v4:
+ _log(f"IPv4: {v4['ip']} – {v4.get('org') or '—'}")
+ else:
+ # Gestaffelte Diagnose nur wenn IP-Abruf fehlschlug
+ _log("IPv4: nicht ermittelbar – starte Netzwerkdiagnose …", "WARN")
+ net_ok = diagnose_network()
+ if not net_ok:
+ self._ip_data["net_error"] = "Keine Internetverbindung"
+ _log("Kein Internet – IP-Lokalisierung nicht möglich", "ERROR")
+ else:
+ self._ip_data["net_error"] = "IP-Dienste nicht erreichbar"
+ _log("Internet OK, aber alle IP-Dienste nicht erreichbar", "WARN")
+
+ if v6:
+ _log(f"IPv6: {v6['ip']}")
+ else:
+ _log("IPv6: nicht verfügbar (Provider unterstützt kein IPv6)")
+
+ # ── Menü aktualisieren ─────────────────────────────────────────────────────
+
+ def _refresh_menu(self):
+ loc = self._loc_data or {}
+ ips = self._ip_data or {}
+ v4 = ips.get("v4") or {}
+ v6 = ips.get("v6") or {}
+
+ loc_error = "error" in (self._loc_data or {})
+ ip_error = not (self._ip_data or {}).get("v4") and not (self._ip_data or {}).get("v6")
+ self.title = "🐢 ⚠️" if (loc_error or ip_error) else "🐢"
+
+ if "error" in loc:
+ self._item_address.title = f" ⚠️ {loc['error']}"
+ self._item_coords.title = " Koordinaten: —"
+ self._item_altitude.title = " Höhe: —"
+ self._item_accuracy.title = " Genauigkeit: —"
+ else:
+ self._item_address.title = f" {loc.get('address', '—')}"
+ self._item_coords.title = (f" {loc['lat']:.6f}°, {loc['lon']:.6f}°"
+ if "lat" in loc else " —")
+ alt = loc.get('alt', 0)
+ alt_str = f"{alt:.0f} m ü. NN" if alt and abs(alt) > 0.5 else "nicht verfügbar (WLAN-Ortung)"
+ self._item_altitude.title = f" Höhe: {alt_str}"
+ self._item_accuracy.title = f" Genauigkeit: ± {loc.get('acc', 0):.0f} m"
+
+ if v4.get("ip"):
+ self._item_v4.title = f" IPv4: {v4['ip']}"
+ loc_parts = [p for p in [v4.get("city"), v4.get("region"), v4.get("country")] if p]
+ self._item_v4_loc.title = f" Standort: {', '.join(loc_parts) if loc_parts else '—'}"
+ self._item_v4_org.title = f" Provider: {v4.get('org') or '—'}"
+ else:
+ net_err = ips.get("net_error", "")
+ if "Kein Internet" in net_err or "Keine Internet" in net_err:
+ self._item_v4.title = " ⚠️ Kein Internet"
+ self._item_v4_loc.title = " Prüfe deine Netzwerkverbindung"
+ self._item_v4_org.title = " —"
+ elif net_err:
+ self._item_v4.title = " ⚠️ IP-Dienste nicht erreichbar"
+ self._item_v4_loc.title = " Internet OK, Dienste blockiert?"
+ self._item_v4_org.title = " —"
+ else:
+ self._item_v4.title = " IPv4: nicht verfügbar"
+ self._item_v4_loc.title = " Standort: —"
+ self._item_v4_org.title = " Provider: —"
+
+ if v6.get("ip"):
+ ip6 = v6["ip"]
+ self._item_v6.title = f" IPv6: {ip6 if len(ip6) <= 30 else ip6[:27] + '…'}"
+ else:
+ self._item_v6.title = " IPv6: nicht verfügbar"
+
+ self._item_updated.title = f" Aktualisiert: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}"
+
+
+# ── Start ──────────────────────────────────────────────────────────────────────
+
+if __name__ == "__main__":
+ import platform
+ import fcntl
+
+ # ── Single-Instance-Lock: verhindert doppelte Prozesse ────────────────────
+ LOCK_FILE = os.path.expanduser("~/Library/Application Support/GeoTurtl.lock")
+ os.makedirs(os.path.dirname(LOCK_FILE), exist_ok=True)
+ try:
+ _lock_fh = open(LOCK_FILE, "w")
+ fcntl.flock(_lock_fh, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except BlockingIOError:
+ print(f"[{datetime.now().strftime('%d.%m.%Y %H:%M:%S')}] [WARN] "
+ f"GeoTurtl läuft bereits – zweite Instanz wird beendet.")
+ sys.exit(0)
+
+ # ── Dock-Icon unterdrücken ────────────────────────────────────────────────
+ info = NSBundle.mainBundle().infoDictionary()
+ info["LSUIElement"] = "1"
+
+ _log(f"GeoTurtl v{APP_VERSION} gestartet "
+ f"(PID {os.getpid()}, Python {platform.python_version()}, "
+ f"macOS {platform.mac_ver()[0]})")
+ app = GeoTurtlApp()
+ app.run()
+PYEOF
+chmod +x "$SCRIPT"
+echo " ✓ GeoTurtl.py erzeugt"
+
+# ── Starter ───────────────────────────────────────────────────────────────────
+echo "→ Erzeuge Starter …"
+cat > "$STARTER" << STARTEOF
+#!/bin/zsh
+LOGFILE="$LOGFILE"
+PIDFILE="$PIDFILE"
+mkdir -p "\$(dirname \$PIDFILE)"
+if [ -f "\$PIDFILE" ]; then
+ OLD_PID=\$(cat "\$PIDFILE")
+ if kill -0 "\$OLD_PID" 2>/dev/null; then
+ echo "Beende laufende GeoTurtl-Instanz (PID \$OLD_PID) …"
+ kill "\$OLD_PID" && sleep 1
+ fi
+ rm -f "\$PIDFILE"
+fi
+nohup $(which python3) "$SCRIPT" > "\$LOGFILE" 2>&1 &
+echo \$! > "\$PIDFILE"
+echo "GeoTurtl gestartet (PID \$!)"
+echo "Log: \$LOGFILE"
+STARTEOF
+chmod +x "$STARTER"
+echo " ✓ Starter erzeugt"
+
+# ── Stopper ───────────────────────────────────────────────────────────────────
+echo "→ Erzeuge Stopper …"
+cat > "$STOPPER" << STOPEOF
+#!/bin/zsh
+PIDFILE="$PIDFILE"
+if [ -f "\$PIDFILE" ]; then
+ PID=\$(cat "\$PIDFILE")
+ if kill -0 "\$PID" 2>/dev/null; then
+ kill "\$PID" && echo "GeoTurtl beendet (PID \$PID)."
+ else
+ echo "GeoTurtl lief nicht mehr."
+ fi
+ rm -f "\$PIDFILE"
+else
+ echo "Kein PID-File — GeoTurtl läuft nicht."
+fi
+STOPEOF
+chmod +x "$STOPPER"
+echo " ✓ Stopper erzeugt"
+
+# ── launchd-Autostart ─────────────────────────────────────────────────────────
+echo "→ Richte Autostart via launchd ein …"
+mkdir -p "$PLIST_DIR"
+PYTHON_BIN=$(which python3)
+cat > "$PLIST" << PLISTEOF
+
+
+
+
+ Label
+ com.user.geoturtl
+ ProgramArguments
+
+ $PYTHON_BIN
+ $SCRIPT
+
+ RunAtLoad
+
+ KeepAlive
+
+ StandardOutPath
+ $LOGFILE
+ StandardErrorPath
+ $LOGFILE
+
+
+PLISTEOF
+launchctl unload "$PLIST" 2>/dev/null || true
+launchctl load "$PLIST"
+echo " ✓ Autostart eingerichtet"
+
+# ── Aliases ───────────────────────────────────────────────────────────────────
+for ENTRY in "geoturtl_start=$STARTER" "geoturtl_stop=$STOPPER"; do
+ ANAME="${ENTRY%%=*}"
+ AVAL="${ENTRY#*=}"
+ ALINE="alias $ANAME='$AVAL'"
+ if grep -q "alias $ANAME=" ~/.zshrc 2>/dev/null; then
+ echo "→ Alias '$ANAME' bereits vorhanden, wird übersprungen."
+ else
+ echo "$ALINE" >> ~/.zshrc
+ echo "→ Alias '$ANAME' eingetragen."
+ fi
+done
+source ~/.zshrc 2>/dev/null || true
+
+# ── Fertig ────────────────────────────────────────────────────────────────────
+echo ""
+echo "╔══════════════════════════════════════════════════╗"
+echo "║ ✓ GeoTurtl v$VERSION – Installation fertig! ║"
+echo "║ ║"
+echo "║ 🐢 Starten: geoturtl_start ║"
+echo "║ ■ Stoppen: geoturtl_stop ║"
+echo "║ 📄 Log: ~/Library/Logs/GeoTurtl.log ║"
+echo "║ 📖 Doku: $TARGET/README.html ║"
+echo "╚══════════════════════════════════════════════════╝"
+echo ""
+echo "HINWEIS: Beim ersten Start Ortungsdienste erlauben:"
+echo " Systemeinstellungen → Datenschutz & Sicherheit"
+echo " → Ortungsdienste → Terminal → Beim Verwenden"
+echo ""
+echo "→ Starte GeoTurtl jetzt im Hintergrund …"
+"$STARTER"
diff --git a/README.html b/README.html
new file mode 100644
index 0000000..4eaa095
--- /dev/null
+++ b/README.html
@@ -0,0 +1,666 @@
+
+
+
+
+
+🐢 GeoTurtl v0.9.7-beta – Dokumentation
+
+
+
+
+
+
+
+
+
+
+
+
+ 🐢 GeoTurtl v0.9.7-beta
+
+
+ GeoTurtl ist eine kleine, unauffällige App für deinen Mac.
+ Sie sitzt als Schildkröten-Symbol 🐢 in der Menüleiste und zeigt dir jederzeit
+ auf Knopfdruck, wo du gerade bist – und wie deine Verbindung ins Internet aussieht.
+
+
+
+ 📋 Beta-Hinweis
+ Dies ist eine Beta-Version (v0.9.0). Sie ist funktionsfähig, kann aber noch
+ kleinere Fehler enthalten. Rückmeldungen sind herzlich willkommen!
+
+
+
+
+
+ Was ist GeoTurtl und was macht es?
+
+ GeoTurtl zeigt dir zwei Dinge auf einen Blick:
+
+ 📍 Deinen Standort
+
+ GeoTurtl nutzt die eingebaute Ortungsfunktion deines Macs – dieselbe Technik,
+ die auch Apple Maps verwendet. Es zeigt dir:
+
+
+ Deine aktuelle Adresse
+ Koordinaten (Breiten- und Längengrad)
+ Höhe über dem Meeresspiegel
+ Genauigkeit der Ortung
+
+
+ Mit einem Klick öffnest du deinen Standort direkt in
+ Google Maps, Apple Maps oder OpenStreetMap.
+
+
+ 🌐 Deine IP-Adressen
+
+ GeoTurtl ermittelt deine öffentliche IPv4- und (falls vorhanden) IPv6-Adresse –
+ also die Adresse, unter der du im Internet sichtbar bist. Zusätzlich siehst du
+ den ungefähren Standort dieser Adresse und deinen Internetanbieter.
+ Per Klick kannst du beide Adressen direkt in die Zwischenablage kopieren.
+
+
+
+ ⏱ Auto-Update
+ GeoTurtl aktualisiert sich automatisch – du kannst selbst einstellen,
+ wie oft: alle 1, 5 oder 15 Minuten, oder manuell auf Knopfdruck.
+
+
+
+
+
+ Welche Installations- und Systemvoraussetzungen gibt es?
+
+
+ Einen Mac mit macOS (ab macOS Ventura, optimiert für macOS Tahoe)
+ Python 3 – auf modernen Macs in der Regel bereits vorinstalliert
+ Eine aktive Internetverbindung für die Installation
+ Aktivierte Ortungsdienste auf deinem Mac
+
+
+ Die Installation erledigt alles Weitere automatisch für dich.
+
+
+
+
+ Ist die Software sicher?
+
+ Ja. GeoTurtl ist quelloffen (MIT-Lizenz) – du kannst den kompletten
+ Programmcode jederzeit selbst einsehen. Das Programm:
+
+
+ sendet keine Daten an Dritte außer den öffentlichen Ortungs- und IP-Diensten (ipinfo.io, ipify.org)
+ speichert keine persönlichen Daten dauerhaft
+ läuft ausschließlich lokal auf deinem Mac
+ ist werbefrei und kostenlos
+
+
+
+ Die Ortungsdienste werden nur auf ausdrückliche Anfrage genutzt –
+ macOS fragt beim ersten Start nach deiner Erlaubnis.
+
+
+
+
+
+ Wie installiere ich GeoTurtl?
+
+
+ Du benötigst nur die Datei GeoTurtl_install_v0.9.7-beta.sh
+ aus dem heruntergeladenen ZIP-Paket. Öffne das Terminal
+ (Programme → Dienstprogramme → Terminal) und führe diese zwei Befehle aus:
+
+
+
+
+
1
+
+
Datei ausführbar machen
+
chmod +x ~/Downloads/GeoTurtl_v0.9.7-beta/GeoTurtl_install_v0.9.7-beta.shcopy
+
+
+
+
2
+
+
Installation starten
+
~/Downloads/GeoTurtl_v0.9.7-beta/GeoTurtl_install_v0.9.7-beta.shcopy
+
+
+
+
3
+
+
Ortungsdienste erlauben
+
Beim ersten Start erscheint eine macOS-Meldung. Klicke auf
+ „Beim Verwenden erlauben" . Falls die Meldung ausbleibt:
+
Systemeinstellungen → Datenschutz & Sicherheit → Ortungsdienste → Terminal → „Beim Verwenden"
+
+
+
+
+
+ ℹ️ Was passiert bei der Installation?
+ Das Script installiert die benötigten Python-Pakete, legt den Programmordner an,
+ richtet den automatischen Start beim Mac-Login ein und startet GeoTurtl sofort.
+
+
+
+
+
+ Wie starte und stoppe ich GeoTurtl?
+
+ Nach der Installation erscheint das 🐢-Symbol automatisch in der Menüleiste.
+ Bei Bedarf kannst du GeoTurtl auch manuell über das Terminal steuern:
+
+ ▶ Starten
+ geoturtl_startcopy
+
+ ■ Stoppen
+ geoturtl_stopcopy
+
+
+ 🔄 Autostart
+ GeoTurtl startet nach der Installation automatisch bei jedem Mac-Login –
+ du musst nichts weiter tun.
+
+
+
+
+
+ Wo finde ich die Log-Dateien?
+
+ GeoTurtl schreibt alle wichtigen Ereignisse in eine Log-Datei:
+ ~/Library/Logs/GeoTurtl.logcopy
+
+ Log live verfolgen
+ tail -f ~/Library/Logs/GeoTurtl.logcopy
+
+ Letzte 50 Zeilen anzeigen
+ tail -50 ~/Library/Logs/GeoTurtl.logcopy
+
+ Log im Texteditor öffnen
+ open ~/Library/Logs/GeoTurtl.logcopy
+
+
+
+
+ Wie deinstalliere ich GeoTurtl restlos?
+
+ Alles mit einem einzigen Befehl entfernen – einfach ins Terminal kopieren:
+
+ geoturtl_stop 2>/dev/null; \
+launchctl unload ~/Library/LaunchAgents/com.user.geoturtl.plist 2>/dev/null; \
+rm -f ~/Library/LaunchAgents/com.user.geoturtl.plist; \
+rm -rf /Applications/Scripte/python/GeoTurtl; \
+rm -f ~/Library/Logs/GeoTurtl.log; \
+rm -f ~/Library/Application\ Support/GeoTurtl.pid; \
+sed -i '' '/geoturtl/d' ~/.zshrc; \
+source ~/.zshrc; \
+echo "✓ GeoTurtl vollständig entfernt."copy
+
+ Dieser Befehl:
+
+ Beendet GeoTurtl, falls es gerade läuft
+ Entfernt den Autostart beim Login
+ Löscht alle Programmdateien
+ Löscht die Log-Datei
+ Entfernt die Terminal-Kürzel
+
+
+
+
+
+ Haftungsausschluss
+
+
+
⚠️ Wichtiger Hinweis – Bitte vor der Installation lesen
+
+
+ GeoTurtl wurde von H. Linz nach bestem Wissen und Gewissen
+ entwickelt und kostenlos zur Verfügung gestellt. Dennoch gilt:
+
+
+
+
+ Die Software wird „so wie sie ist" (as is) bereitgestellt –
+ ohne Garantie auf Fehlerfreiheit, ununterbrochenen Betrieb oder
+ Eignung für einen bestimmten Zweck.
+
+
+ H. Linz übernimmt keine Haftung für direkte oder indirekte
+ Schäden, Datenverluste oder sonstige Beeinträchtigungen, die durch die
+ Installation, Nutzung oder Deinstallation dieser Software entstehen könnten.
+
+
+ Die Nutzung von Drittdiensten wie ipinfo.io und
+ ipify.org zur IP-Ermittlung unterliegt den jeweiligen
+ Nutzungsbedingungen dieser Anbieter und liegt außerhalb der Verantwortung
+ des Autors.
+
+
+ Die Ortungsfunktion verwendet ausschließlich die von Apple bereitgestellte
+ CoreLocation-API . Die Genauigkeit der Standortangaben
+ hängt von der Verfügbarkeit von WLAN-Signalen ab und kann variieren.
+
+
+ Jede Person installiert und nutzt diese Software auf eigene
+ Gefahr und eigene Verantwortung.
+
+
+ Durch die Installation und Nutzung von GeoTurtl erkennst du diese
+ Bedingungen an.
+
+
+
+
+ Diese Software steht unter der MIT-Lizenz .
+ Der vollständige Lizenztext ist im Programmordner enthalten.
+
+
+
+
+
+
+ Fragen, Anregungen oder konstruktive Kritik?
+
+
+ Du schickst mir eine Nachricht an
+ linz.on.tour@gmail.com .
+ Ich beantworte sie dann, so schnell es mir möglich ist.
+
+
+ H. Linz
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 00e22f5..d229c1a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,195 @@
-# GeoTurtle
+---
+title: "GeoTurtl README"
+version: "0.9.7-beta"
+date: 2026-06-07
+author: "H. Linz"
+contact: "linz.on.tour@gmail.com"
+license: "MIT"
+tags: [geoturtl, ip, geolocation, standort, macOS, menubar]
+draft: false
+---
+# 🐢 GeoTurtl v0.9.7-beta
+
+**GeoTurtl** ist eine kleine, unauffällige App für deinen Mac. Sie sitzt als
+Schildkröten-Symbol 🐢 in der Menüleiste und zeigt dir jederzeit auf Knopfdruck,
+wo du gerade bist – und wie deine Verbindung ins Internet aussieht.
+
+> **Beta-Hinweis:** Dies ist eine Beta-Version (v0.9.6). Sie ist funktionsfähig,
+> kann aber noch kleinere Fehler enthalten. Rückmeldungen sind willkommen!
+
+---
+
+## Inhaltsverzeichnis
+
+- [Was ist GeoTurtl und was macht es?](#was-ist-geoturtl-und-was-macht-es)
+- [Welche Installations- und Systemvoraussetzungen gibt es?](#welche-installations--und-systemvoraussetzungen-gibt-es)
+- [Ist die Software sicher?](#ist-die-software-sicher)
+- [Wie installiere ich GeoTurtl?](#wie-installiere-ich-geoturtl)
+- [Wie starte und stoppe ich GeoTurtl?](#wie-starte-und-stoppe-ich-geoturtl)
+- [Wo finde ich die Log-Dateien und wie rufe ich sie auf?](#wo-finde-ich-die-log-dateien-und-wie-rufe-ich-sie-auf)
+- [Wie deinstalliere ich GeoTurtl restlos?](#wie-deinstalliere-ich-geoturtl-restlos)
+- [Haftungsausschluss](#haftungsausschluss)
+- [Was mache ich, wenn ich Fragen, Anregungen oder konstruktive Kritik habe?](#was-mache-ich-wenn-ich-fragen-anregungen-oder-konstruktive-kritik-habe)
+
+---
+
+## Was ist GeoTurtl und was macht es?
+
+GeoTurtl zeigt dir zwei Dinge auf einen Blick:
+
+**Deinen Standort**
+GeoTurtl nutzt die eingebaute Ortungsfunktion deines Macs (dieselbe Technik,
+die auch Apple Maps verwendet) und zeigt dir deine aktuelle Adresse, die genauen
+Koordinaten (Breiten- und Längengrad), deine Höhe über dem Meeresspiegel sowie
+die Genauigkeit der Ortung an. Mit einem Klick öffnest du deinen Standort direkt
+in Google Maps, Apple Maps oder OpenStreetMap.
+
+**Deine IP-Adressen**
+GeoTurtl ermittelt deine öffentliche IPv4- und (falls vorhanden) IPv6-Adresse –
+also die Adresse, unter der du im Internet sichtbar bist. Dazu zeigt es dir
+den ungefähren Standort dieser Adresse und deinen Internetanbieter an.
+Per Klick kannst du beide Adressen direkt in die Zwischenablage kopieren.
+
+GeoTurtl aktualisiert diese Informationen automatisch – du kannst selbst
+einstellen, wie oft (alle 1, 5 oder 15 Minuten, oder gar nicht).
+
+---
+
+## Welche Installations- und Systemvoraussetzungen gibt es?
+
+- Einen **Mac mit macOS** (ab macOS Ventura, optimiert für macOS Tahoe)
+- **Python 3** – auf modernen Macs in der Regel bereits vorinstalliert
+- Eine aktive **Internetverbindung** für die Installation
+- **Aktivierte Ortungsdienste** auf deinem Mac
+
+Die Installation erledigt alles Weitere automatisch für dich.
+
+---
+
+## Ist die Software sicher?
+
+Ja. GeoTurtl ist quelloffen (MIT-Lizenz) – du kannst den kompletten
+Programmcode jederzeit selbst einsehen. Das Programm:
+
+- sendet **keine** Daten an Dritte außer den öffentlichen Ortungs- und
+ IP-Diensten (ipinfo.io, ipify.org)
+- speichert **keine** persönlichen Daten dauerhaft
+- läuft ausschließlich **lokal** auf deinem Mac
+- ist **werbefrei** und kostenlos
+
+Die Ortungsdienste werden nur auf ausdrückliche Anfrage genutzt –
+macOS fragt beim ersten Start nach deiner Erlaubnis.
+
+---
+
+## Wie installiere ich GeoTurtl?
+
+Öffne das Terminal (Programme → Dienstprogramme → Terminal) und führe
+diese zwei Befehle aus:
+
+**Schritt 1** – Datei ausführbar machen:
+```
+chmod +x ~/Downloads/GeoTurtl_v0.9.7-beta/GeoTurtl_install_v0.9.7-beta.sh
+```
+
+**Schritt 2** – Installation starten:
+```
+~/Downloads/GeoTurtl_v0.9.7-beta/GeoTurtl_install_v0.9.7-beta.sh
+```
+
+**Schritt 3** – Ortungsdienste erlauben:
+Beim ersten Start erscheint eine macOS-Meldung. Klicke auf
+**„Beim Verwenden erlauben"**. Falls die Meldung ausbleibt:
+> Systemeinstellungen → Datenschutz & Sicherheit → Ortungsdienste
+> → Terminal → „Beim Verwenden"
+
+---
+
+## Wie starte und stoppe ich GeoTurtl?
+
+**Starten** – im Terminal:
+```
+geoturtl_start
+```
+
+**Stoppen** – im Terminal:
+```
+geoturtl_stop
+```
+
+GeoTurtl startet nach der Installation **automatisch** bei jedem Mac-Login.
+
+---
+
+## Wo finde ich die Log-Dateien und wie rufe ich sie auf?
+
+GeoTurtl schreibt alle wichtigen Ereignisse in eine Log-Datei:
+```
+~/Library/Logs/GeoTurtl.log
+```
+
+**Log live verfolgen:**
+```
+tail -f ~/Library/Logs/GeoTurtl.log
+```
+
+**Letzte 50 Zeilen anzeigen:**
+```
+tail -50 ~/Library/Logs/GeoTurtl.log
+```
+
+**Log im Texteditor öffnen:**
+```
+open ~/Library/Logs/GeoTurtl.log
+```
+
+---
+
+## Wie deinstalliere ich GeoTurtl restlos?
+
+Alles auf einmal entfernen – diesen Befehl ins Terminal kopieren:
+
+```
+geoturtl_stop 2>/dev/null; \
+launchctl unload ~/Library/LaunchAgents/com.user.geoturtl.plist 2>/dev/null; \
+rm -f ~/Library/LaunchAgents/com.user.geoturtl.plist; \
+rm -rf /Applications/Scripte/python/GeoTurtl; \
+rm -f ~/Library/Logs/GeoTurtl.log; \
+rm -f ~/Library/Application\ Support/GeoTurtl.pid; \
+sed -i '' '/geoturtl/d' ~/.zshrc; \
+source ~/.zshrc; \
+echo "✓ GeoTurtl vollständig entfernt."
+```
+
+---
+
+## Haftungsausschluss
+
+GeoTurtl wurde von **H. Linz** nach bestem Wissen und Gewissen entwickelt
+und kostenlos zur Verfügung gestellt. Dennoch gilt:
+
+- Die Software wird **„so wie sie ist" (as is)** bereitgestellt – ohne Garantie
+ auf Fehlerfreiheit, ununterbrochenen Betrieb oder Eignung für einen bestimmten Zweck.
+- **H. Linz übernimmt keine Haftung** für direkte oder indirekte Schäden,
+ Datenverluste oder sonstige Beeinträchtigungen durch Installation, Nutzung
+ oder Deinstallation dieser Software.
+- Die Nutzung von Drittdiensten (ipinfo.io, ipify.org) unterliegt den jeweiligen
+ Nutzungsbedingungen dieser Anbieter und liegt außerhalb der Verantwortung des Autors.
+- **Jede Person installiert und nutzt diese Software auf eigene Gefahr.**
+- Durch die Installation erkennst du diese Bedingungen an.
+
+Diese Software steht unter der **MIT-Lizenz**.
+
+---
+
+## Was mache ich, wenn ich Fragen, Anregungen oder konstruktive Kritik habe?
+
+Du schickst mir eine Nachricht an **linz.on.tour@gmail.com**. Ich beantworte
+sie dann, so schnell es mir möglich ist.
+
+**H. Linz**
+
+---
+
+*GeoTurtl v0.9.7-beta · MIT License · © 2026 H. Linz*