Files
dejanandClaude Opus 5.5 61c238b38d Freigabe fuer Kollegen, Kopf auf schmalen Fenstern bleibt oben
freigabe.sh startet freigabe.py (Passwort, nur lesen, nichts mit Punkt
am Anfang) und einen Cloudflare Quick Tunnel. Anleitung fuer Agents in
.docs/Freigabe.md, Hinweis darauf in .claude/CLAUDE.md. Das Passwort
selbst bleibt in .freigabe-passwort ausserhalb von git.

Dashboard: Unter 900 px bleibt die Navigation beim Scrollen stehen,
nur das Logo scrollt weg.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:15:44 +02:00

97 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Freigabe des Marketing-Dashboards für Kollegen.
Derselbe Server wie serve.py, aber für den Weg nach draußen über einen
Cloudflare-Tunnel. Gestartet wird alles zusammen mit ./freigabe.sh.
- jede Anfrage braucht das Passwort, der Name ist egal
- nur lesen: kein Status setzen, kein Export, nichts öffnen
- nichts, was mit einem Punkt beginnt, also weder .git noch .DS_Store
- keine Ordnerlisten
Das Passwort steht in .freigabe-passwort. Fehlt die Datei, wird beim ersten
Start eins erzeugt. Zum Wechseln die Datei löschen und neu starten.
Anleitung zum Starten, Prüfen und Beenden: .docs/Freigabe.md
"""
import base64
import hmac
import secrets
import sys
import urllib.parse
import serve
PORT = 4330
PASSWORTDATEI = serve.HUB / ".freigabe-passwort"
def passwort():
if PASSWORTDATEI.exists():
return PASSWORTDATEI.read_text(encoding="utf-8").strip()
neu = secrets.token_urlsafe(12)
PASSWORTDATEI.write_text(neu + "\n", encoding="utf-8")
PASSWORTDATEI.chmod(0o600)
return neu
PASSWORT = passwort()
class Handler(serve.Handler):
def _erlaubt(self):
kopf = self.headers.get("Authorization", "")
if kopf.startswith("Basic "):
try:
_, _, pw = base64.b64decode(kopf[6:]).decode("utf-8").partition(":")
except Exception:
pw = ""
if hmac.compare_digest(pw.encode("utf-8"), PASSWORT.encode("utf-8")):
return True
self.send_response(401)
self.send_header("WWW-Authenticate", 'Basic realm="HiveGrid Marketing", charset="UTF-8"')
self.send_header("Content-Length", "0")
self.end_headers()
return False
def _versteckt(self):
pfad = urllib.parse.unquote(urllib.parse.urlparse(self.path).path)
if any(t.startswith(".") for t in pfad.split("/") if t):
self.send_error(404)
return True
return False
def do_GET(self):
if self._erlaubt() and not self._versteckt():
super().do_GET()
def do_HEAD(self):
if self._erlaubt() and not self._versteckt():
super().do_HEAD()
def do_POST(self):
# Das Dashboard zeigt "fehler" unten als Meldung an.
if self._erlaubt():
self._json(403, {"fehler": "In der Freigabe lässt sich nichts ändern."})
def list_directory(self, path):
# Ordner ohne index.html. Eine Liste würde auch versteckte Dateien und
# alles zeigen, was nicht für die Kollegen gedacht ist.
self.send_error(404)
return None
if __name__ == "__main__":
try:
server = serve.Server(("127.0.0.1", PORT), Handler)
except OSError:
print("Port %d ist belegt. Läuft die Freigabe schon?" % PORT, flush=True)
sys.exit(1)
print("Freigabe läuft auf http://127.0.0.1:%d/" % PORT, flush=True)
print("Passwort für die Kollegen: %s (Name egal)" % PASSWORT, flush=True)
with server:
try:
server.serve_forever()
except KeyboardInterrupt:
pass