Dateien nach "/" hochladen

This commit is contained in:
Holger Linz
2026-06-08 21:26:17 +00:00
parent 22b879120a
commit 834145eda1
3 changed files with 1693 additions and 1 deletions
+833
View File
@@ -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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.geoturtl</string>
<key>ProgramArguments</key>
<array>
<string>$PYTHON_BIN</string>
<string>$SCRIPT</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
<key>StandardOutPath</key>
<string>$LOGFILE</string>
<key>StandardErrorPath</key>
<string>$LOGFILE</string>
</dict>
</plist>
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"
+666
View File
@@ -0,0 +1,666 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🐢 GeoTurtl v0.9.7-beta – Dokumentation</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--color-canvas-default: #ffffff;
--color-canvas-subtle: #f6f8fa;
--color-border-default: #d0d7de;
--color-border-muted: #d8dee4;
--color-fg-default: #1f2328;
--color-fg-muted: #656d76;
--color-fg-subtle: #6e7781;
--color-accent: #2da44e;
--color-accent-subtle: #dafbe1;
--color-accent-emphasis: #1a7f37;
--color-turtle: #2da44e;
--color-link: #0969da;
--color-warning-subtle: #fff8c5;
--color-warning-border: #d4a72c;
--color-danger-subtle: #ffebe9;
--color-danger-border: #ff8182;
--radius: 6px;
--font-mono: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 1.6;
color: var(--color-fg-default);
background: var(--color-canvas-default);
}
/* ── Header ── */
.gh-header {
background: #24292f;
padding: 16px 0;
border-bottom: 1px solid #30363d;
position: sticky;
top: 0;
z-index: 100;
}
.gh-header-inner {
max-width: 1012px;
margin: 0 auto;
padding: 0 32px;
display: flex;
align-items: center;
gap: 16px;
}
.gh-logo { font-size: 24px; }
.gh-repo {
font-size: 20px;
color: #ffffff;
font-weight: 600;
letter-spacing: -0.3px;
}
.gh-repo span { color: #848d97; font-weight: 400; }
.gh-badge {
background: var(--color-accent-subtle);
color: var(--color-accent-emphasis);
border: 1px solid #82e0a0;
border-radius: 2em;
font-size: 12px;
font-weight: 600;
padding: 2px 10px;
margin-left: 4px;
}
/* ── Layout ── */
.layout {
max-width: 1012px;
margin: 0 auto;
padding: 32px 32px;
display: grid;
grid-template-columns: 1fr 260px;
gap: 32px;
align-items: start;
}
/* ── Sidebar ── */
.sidebar {
position: sticky;
top: 72px;
background: var(--color-canvas-subtle);
border: 1px solid var(--color-border-default);
border-radius: var(--radius);
padding: 16px;
font-size: 14px;
}
.sidebar h3 {
font-size: 12px;
font-weight: 600;
color: var(--color-fg-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 12px;
}
.sidebar ul { list-style: none; }
.sidebar ul li { margin: 4px 0; }
.sidebar ul li a {
color: var(--color-link);
text-decoration: none;
display: block;
padding: 3px 0;
border-radius: 4px;
transition: color 0.15s;
}
.sidebar ul li a:hover { color: var(--color-accent-emphasis); text-decoration: underline; }
.sidebar-meta {
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid var(--color-border-default);
}
.sidebar-meta p {
font-size: 12px;
color: var(--color-fg-muted);
margin: 4px 0;
display: flex;
align-items: center;
gap: 6px;
}
.sidebar-meta a { color: var(--color-link); text-decoration: none; }
.sidebar-meta a:hover { text-decoration: underline; }
/* ── Markdown Body ── */
.markdown-body h1 {
font-size: 2em;
font-weight: 700;
border-bottom: 1px solid var(--color-border-muted);
padding-bottom: 10px;
margin-bottom: 20px;
margin-top: 0;
display: flex;
align-items: center;
gap: 12px;
}
.markdown-body h1 .turtle-badge {
font-size: 0.6em;
background: var(--color-accent-subtle);
color: var(--color-accent-emphasis);
border: 1px solid #82e0a0;
border-radius: 2em;
padding: 2px 12px;
font-weight: 600;
vertical-align: middle;
}
.markdown-body h2 {
font-size: 1.4em;
font-weight: 600;
border-bottom: 1px solid var(--color-border-muted);
padding-bottom: 8px;
margin-top: 36px;
margin-bottom: 16px;
scroll-margin-top: 80px;
display: flex;
align-items: center;
gap: 10px;
}
.markdown-body h2 .anchor {
color: var(--color-turtle);
font-size: 0.8em;
opacity: 0;
transition: opacity 0.2s;
}
.markdown-body h2:hover .anchor { opacity: 1; }
.markdown-body h3 {
font-size: 1.1em;
font-weight: 600;
margin-top: 24px;
margin-bottom: 10px;
}
.markdown-body p { margin-bottom: 16px; }
.markdown-body ul, .markdown-body ol {
padding-left: 2em;
margin-bottom: 16px;
}
.markdown-body li { margin: 4px 0; }
.markdown-body strong { font-weight: 600; }
.markdown-body a { color: var(--color-link); text-decoration: none; }
.markdown-body a:hover { text-decoration: underline; }
.markdown-body hr {
border: none;
border-top: 1px solid var(--color-border-muted);
margin: 32px 0;
}
/* ── Code ── */
.markdown-body code {
font-family: var(--font-mono);
font-size: 85%;
background: var(--color-canvas-subtle);
border: 1px solid var(--color-border-default);
border-radius: 4px;
padding: 2px 6px;
color: #cf222e;
}
.markdown-body pre {
background: #f6f8fa;
border: 1px solid var(--color-border-default);
border-radius: var(--radius);
padding: 16px;
overflow-x: auto;
margin-bottom: 16px;
position: relative;
}
.markdown-body pre code {
background: none;
border: none;
padding: 0;
color: var(--color-fg-default);
font-size: 14px;
line-height: 1.5;
}
.copy-btn {
position: absolute;
top: 8px;
right: 8px;
background: var(--color-canvas-default);
border: 1px solid var(--color-border-default);
border-radius: 4px;
padding: 4px 10px;
font-size: 12px;
cursor: pointer;
color: var(--color-fg-muted);
font-family: var(--font-mono);
transition: all 0.15s;
}
.copy-btn:hover {
background: var(--color-accent-subtle);
color: var(--color-accent-emphasis);
border-color: var(--color-accent);
}
.copy-btn.copied {
background: var(--color-accent-subtle);
color: var(--color-accent-emphasis);
border-color: var(--color-accent);
}
/* ── Callouts ── */
.callout {
border-radius: var(--radius);
padding: 16px 20px;
margin-bottom: 20px;
border-left: 4px solid;
font-size: 15px;
}
.callout-note {
background: #ddf4ff;
border-color: #54aeff;
color: #0550ae;
}
.callout-warning {
background: var(--color-warning-subtle);
border-color: var(--color-warning-border);
color: #633c01;
}
.callout-danger {
background: var(--color-danger-subtle);
border-color: var(--color-danger-border);
color: #82071e;
}
.callout-success {
background: var(--color-accent-subtle);
border-color: var(--color-accent);
color: var(--color-accent-emphasis);
}
.callout strong { display: block; margin-bottom: 4px; font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; }
/* ── Steps ── */
.steps { counter-reset: step; margin-bottom: 20px; }
.step {
counter-increment: step;
display: flex;
gap: 16px;
margin-bottom: 20px;
align-items: flex-start;
}
.step-num {
flex-shrink: 0;
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--color-turtle);
color: white;
font-weight: 700;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 2px;
}
.step-body { flex: 1; }
.step-body p { margin-bottom: 8px; }
/* ── Disclaimer ── */
.disclaimer-box {
background: var(--color-danger-subtle);
border: 1px solid var(--color-danger-border);
border-radius: var(--radius);
padding: 20px 24px;
margin-bottom: 16px;
}
.disclaimer-box h3 {
color: #82071e;
margin-top: 0;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.disclaimer-box p, .disclaimer-box li {
font-size: 13px;
color: #57071e;
line-height: 1.6;
}
/* ── Footer ── */
.gh-footer {
background: var(--color-canvas-subtle);
border-top: 1px solid var(--color-border-default);
padding: 24px 32px;
text-align: center;
font-size: 13px;
color: var(--color-fg-muted);
margin-top: 48px;
}
.gh-footer a { color: var(--color-link); text-decoration: none; }
.gh-footer a:hover { text-decoration: underline; }
/* ── Responsive ── */
@media (max-width: 768px) {
.layout { grid-template-columns: 1fr; }
.sidebar { position: static; }
.gh-header-inner, .layout { padding: 16px; }
}
</style>
</head>
<body>
<!-- Header -->
<header class="gh-header">
<div class="gh-header-inner">
<span class="gh-logo">🐢</span>
<span class="gh-repo"><span>H. Linz /</span> GeoTurtl</span>
<span class="gh-badge">v0.9.7-beta</span>
</div>
</header>
<div class="layout">
<!-- Main Content -->
<main class="markdown-body">
<h1>🐢 GeoTurtl <span class="turtle-badge">v0.9.7-beta</span></h1>
<p>
<strong>GeoTurtl</strong> 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.
</p>
<div class="callout callout-note">
<strong>📋 Beta-Hinweis</strong>
Dies ist eine Beta-Version (v0.9.0). Sie ist funktionsfähig, kann aber noch
kleinere Fehler enthalten. Rückmeldungen sind herzlich willkommen!
</div>
<hr>
<!-- Was ist GeoTurtl -->
<h2 id="was-ist">Was ist GeoTurtl und was macht es?</h2>
<p>GeoTurtl zeigt dir zwei Dinge auf einen Blick:</p>
<h3>📍 Deinen Standort</h3>
<p>
GeoTurtl nutzt die eingebaute Ortungsfunktion deines Macs – dieselbe Technik,
die auch Apple Maps verwendet. Es zeigt dir:
</p>
<ul>
<li>Deine aktuelle <strong>Adresse</strong></li>
<li><strong>Koordinaten</strong> (Breiten- und Längengrad)</li>
<li><strong>Höhe</strong> über dem Meeresspiegel</li>
<li><strong>Genauigkeit</strong> der Ortung</li>
</ul>
<p>
Mit einem Klick öffnest du deinen Standort direkt in
Google Maps, Apple Maps oder OpenStreetMap.
</p>
<h3>🌐 Deine IP-Adressen</h3>
<p>
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.
</p>
<div class="callout callout-success">
<strong>⏱ Auto-Update</strong>
GeoTurtl aktualisiert sich automatisch – du kannst selbst einstellen,
wie oft: alle 1, 5 oder 15 Minuten, oder manuell auf Knopfdruck.
</div>
<hr>
<!-- Voraussetzungen -->
<h2 id="voraussetzungen">Welche Installations- und Systemvoraussetzungen gibt es?</h2>
<ul>
<li>Einen <strong>Mac mit macOS</strong> (ab macOS Ventura, optimiert für macOS Tahoe)</li>
<li><strong>Python 3</strong> – auf modernen Macs in der Regel bereits vorinstalliert</li>
<li>Eine aktive <strong>Internetverbindung</strong> für die Installation</li>
<li><strong>Aktivierte Ortungsdienste</strong> auf deinem Mac</li>
</ul>
<p>Die Installation erledigt alles Weitere automatisch für dich.</p>
<hr>
<!-- Sicherheit -->
<h2 id="sicherheit">Ist die Software sicher?</h2>
<p>Ja. GeoTurtl ist quelloffen (MIT-Lizenz) – du kannst den kompletten
Programmcode jederzeit selbst einsehen. Das Programm:</p>
<ul>
<li>sendet <strong>keine</strong> Daten an Dritte außer den öffentlichen Ortungs- und IP-Diensten (ipinfo.io, ipify.org)</li>
<li>speichert <strong>keine</strong> persönlichen Daten dauerhaft</li>
<li>läuft ausschließlich <strong>lokal</strong> auf deinem Mac</li>
<li>ist <strong>werbefrei</strong> und kostenlos</li>
</ul>
<p>
Die Ortungsdienste werden nur auf ausdrückliche Anfrage genutzt –
macOS fragt beim ersten Start nach deiner Erlaubnis.
</p>
<hr>
<!-- Installation -->
<h2 id="installation">Wie installiere ich GeoTurtl?</h2>
<p>
Du benötigst nur die Datei <code>GeoTurtl_install_v0.9.7-beta.sh</code>
aus dem heruntergeladenen ZIP-Paket. Öffne das Terminal
(Programme → Dienstprogramme → Terminal) und führe diese zwei Befehle aus:
</p>
<div class="steps">
<div class="step">
<div class="step-num">1</div>
<div class="step-body">
<p><strong>Datei ausführbar machen</strong></p>
<pre><code>chmod +x ~/Downloads/GeoTurtl_v0.9.7-beta/GeoTurtl_install_v0.9.7-beta.sh</code><button class="copy-btn" onclick="copyCode(this)">copy</button></pre>
</div>
</div>
<div class="step">
<div class="step-num">2</div>
<div class="step-body">
<p><strong>Installation starten</strong></p>
<pre><code>~/Downloads/GeoTurtl_v0.9.7-beta/GeoTurtl_install_v0.9.7-beta.sh</code><button class="copy-btn" onclick="copyCode(this)">copy</button></pre>
</div>
</div>
<div class="step">
<div class="step-num">3</div>
<div class="step-body">
<p><strong>Ortungsdienste erlauben</strong></p>
<p>Beim ersten Start erscheint eine macOS-Meldung. Klicke auf
<strong>„Beim Verwenden erlauben"</strong>. Falls die Meldung ausbleibt:</p>
<p>Systemeinstellungen → Datenschutz &amp; Sicherheit → Ortungsdienste → Terminal → „Beim Verwenden"</p>
</div>
</div>
</div>
<div class="callout callout-note">
<strong>ℹ️ Was passiert bei der Installation?</strong>
Das Script installiert die benötigten Python-Pakete, legt den Programmordner an,
richtet den automatischen Start beim Mac-Login ein und startet GeoTurtl sofort.
</div>
<hr>
<!-- Starten & Stoppen -->
<h2 id="starten-stoppen">Wie starte und stoppe ich GeoTurtl?</h2>
<p>Nach der Installation erscheint das 🐢-Symbol automatisch in der Menüleiste.
Bei Bedarf kannst du GeoTurtl auch manuell über das Terminal steuern:</p>
<h3>▶ Starten</h3>
<pre><code>geoturtl_start</code><button class="copy-btn" onclick="copyCode(this)">copy</button></pre>
<h3>■ Stoppen</h3>
<pre><code>geoturtl_stop</code><button class="copy-btn" onclick="copyCode(this)">copy</button></pre>
<div class="callout callout-success">
<strong>🔄 Autostart</strong>
GeoTurtl startet nach der Installation automatisch bei jedem Mac-Login –
du musst nichts weiter tun.
</div>
<hr>
<!-- Log -->
<h2 id="log">Wo finde ich die Log-Dateien?</h2>
<p>GeoTurtl schreibt alle wichtigen Ereignisse in eine Log-Datei:</p>
<pre><code>~/Library/Logs/GeoTurtl.log</code><button class="copy-btn" onclick="copyCode(this)">copy</button></pre>
<h3>Log live verfolgen</h3>
<pre><code>tail -f ~/Library/Logs/GeoTurtl.log</code><button class="copy-btn" onclick="copyCode(this)">copy</button></pre>
<h3>Letzte 50 Zeilen anzeigen</h3>
<pre><code>tail -50 ~/Library/Logs/GeoTurtl.log</code><button class="copy-btn" onclick="copyCode(this)">copy</button></pre>
<h3>Log im Texteditor öffnen</h3>
<pre><code>open ~/Library/Logs/GeoTurtl.log</code><button class="copy-btn" onclick="copyCode(this)">copy</button></pre>
<hr>
<!-- Deinstallation -->
<h2 id="deinstallation">Wie deinstalliere ich GeoTurtl restlos?</h2>
<p>Alles mit einem einzigen Befehl entfernen – einfach ins Terminal kopieren:</p>
<pre><code>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."</code><button class="copy-btn" onclick="copyCode(this)">copy</button></pre>
<p>Dieser Befehl:</p>
<ul>
<li>Beendet GeoTurtl, falls es gerade läuft</li>
<li>Entfernt den Autostart beim Login</li>
<li>Löscht alle Programmdateien</li>
<li>Löscht die Log-Datei</li>
<li>Entfernt die Terminal-Kürzel</li>
</ul>
<hr>
<!-- Haftungsausschluss -->
<h2 id="haftung">Haftungsausschluss</h2>
<div class="disclaimer-box">
<h3>⚠️ Wichtiger Hinweis – Bitte vor der Installation lesen</h3>
<p>
GeoTurtl wurde von <strong>H. Linz</strong> nach bestem Wissen und Gewissen
entwickelt und kostenlos zur Verfügung gestellt. Dennoch gilt:
</p>
<ul style="margin: 12px 0 12px 20px;">
<li style="margin-bottom: 6px;">
Die Software wird <strong>„so wie sie ist" (as is)</strong> bereitgestellt –
ohne Garantie auf Fehlerfreiheit, ununterbrochenen Betrieb oder
Eignung für einen bestimmten Zweck.
</li>
<li style="margin-bottom: 6px;">
<strong>H. Linz übernimmt keine Haftung</strong> für direkte oder indirekte
Schäden, Datenverluste oder sonstige Beeinträchtigungen, die durch die
Installation, Nutzung oder Deinstallation dieser Software entstehen könnten.
</li>
<li style="margin-bottom: 6px;">
Die Nutzung von Drittdiensten wie <strong>ipinfo.io</strong> und
<strong>ipify.org</strong> zur IP-Ermittlung unterliegt den jeweiligen
Nutzungsbedingungen dieser Anbieter und liegt außerhalb der Verantwortung
des Autors.
</li>
<li style="margin-bottom: 6px;">
Die Ortungsfunktion verwendet ausschließlich die von Apple bereitgestellte
<strong>CoreLocation-API</strong>. Die Genauigkeit der Standortangaben
hängt von der Verfügbarkeit von WLAN-Signalen ab und kann variieren.
</li>
<li style="margin-bottom: 6px;">
<strong>Jede Person installiert und nutzt diese Software auf eigene
Gefahr und eigene Verantwortung.</strong>
</li>
<li>
Durch die Installation und Nutzung von GeoTurtl erkennst du diese
Bedingungen an.
</li>
</ul>
<p style="margin-top: 12px; font-size: 12px; color: #82071e;">
Diese Software steht unter der <strong>MIT-Lizenz</strong>.
Der vollständige Lizenztext ist im Programmordner enthalten.
</p>
</div>
<hr>
<!-- Kontakt -->
<h2 id="kontakt">Fragen, Anregungen oder konstruktive Kritik?</h2>
<p>
Du schickst mir eine Nachricht an
<a href="mailto:linz.on.tour@gmail.com?subject=GeoTurtl%20v0.9.7-beta">linz.on.tour@gmail.com</a>.
Ich beantworte sie dann, so schnell es mir möglich ist.
</p>
<p><strong>H. Linz</strong></p>
</main>
<!-- Sidebar -->
<aside class="sidebar">
<h3>Inhalt</h3>
<ul>
<li><a href="#was-ist">Was ist GeoTurtl?</a></li>
<li><a href="#voraussetzungen">Voraussetzungen</a></li>
<li><a href="#sicherheit">Sicherheit</a></li>
<li><a href="#installation">Installation</a></li>
<li><a href="#starten-stoppen">Starten &amp; Stoppen</a></li>
<li><a href="#log">Log-Dateien</a></li>
<li><a href="#deinstallation">Deinstallation</a></li>
<li><a href="#haftung">Haftungsausschluss</a></li>
<li><a href="#kontakt">Kontakt</a></li>
</ul>
<div class="sidebar-meta">
<p>🐢 GeoTurtl</p>
<p>📦 v0.9.7-beta</p>
<p>📅 Juni 2026</p>
<p>👤 H. Linz</p>
<p>⚖️ MIT License</p>
<p>✉️ <a href="mailto:linz.on.tour@gmail.com?subject=GeoTurtl%20v0.9.7-beta">linz.on.tour@gmail.com</a></p>
</div>
</aside>
</div>
<!-- Footer -->
<footer class="gh-footer">
🐢 GeoTurtl v0.9.7-beta &nbsp;·&nbsp;
MIT License &nbsp;·&nbsp;
© 2026 H. Linz &nbsp;·&nbsp;
<a href="mailto:linz.on.tour@gmail.com?subject=GeoTurtl%20v0.9.7-beta">linz.on.tour@gmail.com</a>
</footer>
<script>
function copyCode(btn) {
const code = btn.previousElementSibling || btn.parentElement.querySelector('code');
navigator.clipboard.writeText(code.innerText.trim()).then(() => {
btn.textContent = '✓ copied';
btn.classList.add('copied');
setTimeout(() => {
btn.textContent = 'copy';
btn.classList.remove('copied');
}, 2000);
});
}
</script>
</body>
</html>
+194 -1
View File
@@ -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*