Skripte nach bin/ und lib/ umstrukturiert + WSL-Log-Analyse

Alle .bat-Skripte liegen jetzt in bin/, alle .py in lib/, .env(.example)
in bin/. setenv.bat setzt IWZ_* (inkl. IWZ_ENV) und PYTHONPATH; die
Launcher rufen setenv, aktivieren die .venv falls vorhanden und starten
das zugehoerige lib/-Skript. Die Python-Skripte lesen die .env ueber
IWZ_ENV (Fallback bin/.env).

Neu: Nutzungsanalyse per GoAccess (analyze.py/.bat) - laedt die
IONOS-Access-Logs per SFTP (paramiko) und erzeugt einen HTML-Report.
GoAccess laeuft ueber WSL (GOACCESS_MODE=wsl, Pfad-Uebersetzung nach
/mnt/c). install_py.bat prueft WSL+GoAccess und zeigt sonst den
Installationsweg. Doku in doc/analyse.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
This commit is contained in:
Michael Stangl
2026-08-28 16:44:56 +02:00
parent 025e68b3b9
commit 2379f4ac9f
21 changed files with 760 additions and 59 deletions
-16
View File
@@ -1,16 +0,0 @@
# Kopieren nach .env und mit echten Werten befuellen.
# .env ist in .gitignore eingetragen und wird NICHT eingecheckt.
# Storage Zone -> Access-Tab im Bunny.net Dashboard
BUNNY_STORAGE_ZONE=iwz-ferienwohnungen
BUNNY_STORAGE_HOST=storage.bunnycdn.com
BUNNY_STORAGE_PASSWORD=
# Pull Zone -> CDN -> Security-Tab im Bunny.net Dashboard (Token Authentication)
BUNNY_PULL_ZONE_URL=https://iwz-ferienwohnungen.b-cdn.net
BUNNY_AUTH_KEY=
# Account-API-Key fuer den CDN-Cache-Purge nach dem Deploy.
# Dashboard -> oben rechts auf den Namen -> Account -> API -> "API Key".
# ACHTUNG: nicht der BUNNY_AUTH_KEY oben, sondern der Account-weite API-Key!
BUNNY_API_KEY=
+7
View File
@@ -4,3 +4,10 @@ resources/
.env
.deploy_manifest.json
__pycache__/
# WordPress-Export der alten Seite (nur temporär, enthält DB-Zugangsdaten im Klartext)
app816341166/
# Nutzungsanalyse (analyze.py): heruntergeladene Rohlogs + erzeugte Reports
logs/
results/
+34 -6
View File
@@ -14,14 +14,35 @@ Drei Ferienwohnungen in Kaufbeuren-Hirschzell / Allgäu.
- **Buchungskalender**: webplanner.de (externer Dienst, per Link eingebunden)
- **Git**: eigene Gitea-Instanz (kein GitHub/GitLab, kein CI/CD-Workflow)
- **Deploy**: manuell per `deploy.bat` oder `deploy.py` → `hugo --gc --minify` + Upload nach Bunny.net Storage Zone (per Bunny-API/FTP, analog zum Ansatz in `.github/workflows/deploy.yml`-Vorlage, aber lokal statt als GitHub Action)
- **Nutzungsanalyse**: Server-Log-Auswertung mit GoAccess (kein JS-Tracking, keine Cookies, kein Consent-Banner). Skript `analyze.bat` / `analyze.py` lädt die Access-Logs per SFTP und erzeugt einen HTML-Report. Siehe `doc/analyse.md`.
## Hosting-Roadmap (Stand aktuell)
- **Jetzt**: Deploy läuft auf **Bunny.net** – für das erste Ausrollen der Instanz zur Kontrolle und Absprache. Die vorhandene Bunny-Konfiguration (Storage Zone, Pull Zone, `.env`) ist in Ordnung und bleibt vorerst so. `deploy.bat` / `deploy.py` deployen weiterhin nach Bunny.
- **Später**: Die Instanz wird auf **IONOS-Webspace** ausgerollt (Deploy-Ziel wird dann umgestellt).
- **Nutzungsanalyse jetzt schon erprobbar**: Die Log-Analyse (`analyze.py`) wird bereits mit den **IONOS-Access-Logs** getestet (SFTP-Zugang zum IONOS `/logs`-Ordner), unabhängig davon, dass das Deploy-Ziel noch Bunny ist. Nach dem IONOS-Umzug bleibt der Analyse-Workflow gleich.
## Projektstruktur
```
iwz/
├── hugo.toml # Konfiguration: baseURL, Menü, Kontaktdaten (Params)
├── deploy.bat # Windows: hugo build + wrangler deploy
├── deploy.py # Python-Alternative zum deploy.bat
├── requirements.txt # Python-Abhängigkeiten (paramiko für analyze)
├── bin/ # Alle .bat-Skripte (Einstiegspunkte)
│ ├── setenv.bat # setzt IWZ, IWZ_BIN, IWZ_LIB, IWZ_ENV ... + PYTHONPATH
│ ├── install_py.bat # legt .venv an + pip install -r requirements.txt
│ ├── get_cmd.bat # öffnet Shell mit gesetzter Umgebung
│ ├── deploy.bat # → lib/deploy.py (Vollupload nach Bunny)
│ ├── deploy-lastdiff.bat# → lib/deploy_lastdiff.py (nur Änderungen)
│ ├── analyze.bat # → lib/analyze.py (IONOS-Logs + GoAccess)
│ ├── provide_previewlink.bat # → lib/preview_link.py
│ ├── .env # Secrets (NICHT im Git; von setenv als IWZ_ENV gesetzt)
│ └── .env.example # Vorlage zum Kopieren nach bin/.env
├── lib/ # Alle .py-Skripte (von den .bat aufgerufen)
│ ├── deploy.py # Hugo-Build + FTP-Upload nach Bunny Storage Zone
│ ├── deploy_lastdiff.py # nur geänderte Dateien (Hash-Manifest)
│ ├── analyze.py # SFTP-Download der Logs + GoAccess-Report
│ └── preview_link.py # signierter Bunny-Vorschau-Link
├── content/
│ ├── _index.md # Startseite
│ ├── preise.md # Preistabelle + Buchungsbedingungen
@@ -94,8 +115,15 @@ hugo --gc --minify
# danach: Upload von ./public zur Bunny.net Storage Zone
# (Storage Zone Name + API-Key/Storage-Passwort als Secrets/env, nicht im Git)
# Bei jeder Änderung:
deploy.bat
# oder:
python deploy.py
# Bei jeder Änderung (Skripte liegen jetzt in bin/, Python in lib/):
bin\deploy.bat # Vollupload
# oder nur geänderte Dateien:
bin\deploy-lastdiff.bat
# oder direkt:
python lib\deploy.py
```
Die `.bat`-Skripte in `bin/` rufen `setenv.bat` auf (setzt `IWZ_*` und
`PYTHONPATH`), wechseln ins Projekt-Root und starten das zugehörige Skript in
`lib/`. Die Secrets liegen in `bin/.env` (von `setenv.bat` als `IWZ_ENV`
gesetzt; die Python-Skripte lesen diesen Pfad, sonst Fallback auf `bin/.env`).
+38
View File
@@ -0,0 +1,38 @@
# Kopieren nach .env und mit echten Werten befuellen.
# .env ist in .gitignore eingetragen und wird NICHT eingecheckt.
# Storage Zone -> Access-Tab im Bunny.net Dashboard
BUNNY_STORAGE_ZONE=iwz-ferienwohnungen
BUNNY_STORAGE_HOST=storage.bunnycdn.com
BUNNY_STORAGE_PASSWORD=
# Pull Zone -> CDN -> Security-Tab im Bunny.net Dashboard (Token Authentication)
BUNNY_PULL_ZONE_URL=https://iwz-ferienwohnungen.b-cdn.net
BUNNY_AUTH_KEY=
# Account-API-Key fuer den CDN-Cache-Purge nach dem Deploy.
# Dashboard -> oben rechts auf den Namen -> Account -> API -> "API Key".
# ACHTUNG: nicht der BUNNY_AUTH_KEY oben, sondern der Account-weite API-Key!
BUNNY_API_KEY=
# --- Nutzungsanalyse (analyze.py) ------------------------------------------
# SFTP-Zugang zu IONOS (gleiche Zugangsdaten wie im IONOS-Kundenbereich / SFTP).
IONOS_SFTP_HOST=
IONOS_SFTP_PORT=22
IONOS_SFTP_USER=
IONOS_SFTP_PASSWORD=
# Ordner mit den Access-Logs auf dem Server (im Screenshot: /logs).
IONOS_LOGS_DIR=/logs
# GoAccess-Aufruf:
# GOACCESS_MODE=wsl -> GoAccess laeuft in der WSL (empfohlen, Standard).
# Installation: bin\install_py.bat prueft WSL+GoAccess
# und zeigt sonst die Installationsbefehle.
# GOACCESS_MODE=exe -> nativer Windows-Build; dann GOACCESS_EXE auf die
# goaccess.exe setzen (Download: https://goaccess.io/download).
GOACCESS_MODE=wsl
GOACCESS_EXE=goaccess
# Apache-Logformat. IONOS nutzt i.d.R. das Combined Log Format.
# Falls der Report leer bleibt -> siehe doc/analyse.md (Format anpassen).
LOG_FORMAT=COMBINED
+20
View File
@@ -0,0 +1,20 @@
@echo off
REM ================================================================
REM SPS_SKEL - Python Virtual Environment aktivieren
REM ================================================================
call "%~dp0setenv.bat"
if not exist "%IWZ%\.venv" (
echo FEHLER: Virtual environment nicht gefunden.
echo Bitte zuerst bin\install_py.bat ausfuehren.
exit /b 1
)
call "%IWZ%\.venv\Scripts\activate.bat"
echo Virtuelle Umgebung aktiviert.
echo Python-Version:
python --version
echo.
echo Installierte Pakete:
pip list
+26
View File
@@ -0,0 +1,26 @@
@echo off
REM ================================================================
REM IWZ Ferienwohnungen - Nutzungsanalyse (IONOS-Logs + GoAccess)
REM ================================================================
REM Aufruf:
REM bin\analyze.bat Logs laden + Report bauen + oeffnen
REM bin\analyze.bat --no-download nur neu auswerten (schon geladene Logs)
REM ================================================================
setlocal
call "%~dp0setenv.bat" >nul
REM Virtuelle Umgebung nutzen, falls vorhanden (paramiko liegt dort)
if exist "%IWZ%\.venv\Scripts\activate.bat" (
call "%IWZ%\.venv\Scripts\activate.bat"
)
set "PY=py"
py --version >nul 2>&1 || set "PY=python"
cd /d "%IWZ%"
"%PY%" "%IWZ_LIB%\analyze.py" %*
set "RC=%ERRORLEVEL%"
if not "%RC%"=="0" pause
endlocal & exit /b %RC%
+26
View File
@@ -0,0 +1,26 @@
@echo off
REM ================================================================
REM IWZ Ferienwohnungen - Deploy (nur geaenderte Dateien)
REM ================================================================
setlocal
call "%~dp0setenv.bat" >nul
REM Virtuelle Umgebung nutzen, falls vorhanden (paramiko etc. liegen dort)
if exist "%IWZ%\.venv\Scripts\activate.bat" (
call "%IWZ%\.venv\Scripts\activate.bat"
)
set "PY=py"
py --version >nul 2>&1 || set "PY=python"
echo.
echo === IWZ Ferienwohnungen - Deploy (nur Aenderungen) ===
echo.
cd /d "%IWZ%"
"%PY%" "%IWZ_LIB%\deploy_lastdiff.py" %*
set "RC=%ERRORLEVEL%"
if not "%RC%"=="0" pause
endlocal & exit /b %RC%
+29
View File
@@ -0,0 +1,29 @@
@echo off
REM ================================================================
REM IWZ Ferienwohnungen - Deploy (Vollupload nach Bunny.net)
REM ================================================================
setlocal
REM Umgebung setzen (IWZ, IWZ_LIB, IWZ_ENV, PYTHONPATH ...)
call "%~dp0setenv.bat" >nul
REM Virtuelle Umgebung nutzen, falls vorhanden (paramiko etc. liegen dort)
if exist "%IWZ%\.venv\Scripts\activate.bat" (
call "%IWZ%\.venv\Scripts\activate.bat"
)
REM Python-Interpreter bestimmen (py-Launcher bevorzugt, sonst python)
set "PY=py"
py --version >nul 2>&1 || set "PY=python"
echo.
echo === IWZ Ferienwohnungen - Deploy ===
echo.
REM Aus dem Projekt-Root heraus bauen (hugo + public liegen dort)
cd /d "%IWZ%"
"%PY%" "%IWZ_LIB%\deploy.py" %*
set "RC=%ERRORLEVEL%"
if not "%RC%"=="0" pause
endlocal & exit /b %RC%
+7
View File
@@ -0,0 +1,7 @@
@echo off
REM ================================================================
REM IWZ Ferienwohnungen - Shell mit gesetzten Umgebungsvariablen oeffnen
REM ================================================================
call "%~dp0setenv.bat"
start cmd /k "echo IWZ Umgebung aktiv. && echo IWZ=%IWZ%"
+54
View File
@@ -0,0 +1,54 @@
@echo off
REM ================================================================
REM IWZ - Python Virtual Environment einrichten
REM ================================================================
call "%~dp0setenv.bat"
if not exist "%IWZ%\.venv" (
echo Initialisiere Python virtual environment...
py -m venv "%IWZ%\.venv" --upgrade-deps
echo Erfolgreich.
call "%IWZ%\.venv\Scripts\activate.bat"
echo Installiere erforderliche Python Packages...
pip install -r "%IWZ%\requirements.txt" -q
echo Erfolgreich.
deactivate
) else (
echo Erforderliche Python Packages bereits installiert!
)
echo.
echo ================================================================
echo Pruefe Nutzungsanalyse-Voraussetzungen (WSL + GoAccess)
echo ================================================================
REM 1) Ist WSL verfuegbar?
where wsl >nul 2>&1
if errorlevel 1 (
echo [FEHLT] WSL ist nicht installiert.
echo So einrichten ^(einmalig, PowerShell als Administrator^):
echo wsl --install
echo Danach Windows neu starten und die Ubuntu-Ersteinrichtung
echo ^(Benutzername/Passwort^) durchlaufen. Anschliessend GoAccess
echo wie unten installieren.
goto :wsl_done
)
echo [OK] WSL gefunden.
REM 2) Ist GoAccess in der WSL installiert?
wsl -e bash -lc "command -v goaccess >/dev/null 2>&1"
if errorlevel 1 (
echo [FEHLT] GoAccess ist in der WSL nicht installiert.
echo So installieren:
echo wsl -e sudo apt update
echo wsl -e sudo apt install -y goaccess
echo ^(fragt einmalig nach dem WSL-sudo-Passwort^)
) else (
for /f "delims=" %%V in ('wsl -e bash -lc "goaccess --version ^| head -1"') do set "GA_VER=%%V"
echo [OK] GoAccess in der WSL: %GA_VER%
)
:wsl_done
echo ================================================================
+27
View File
@@ -0,0 +1,27 @@
@echo off
REM ================================================================
REM Ferienwohnungen Wolframm - Vorschau-Link erzeugen
REM ================================================================
setlocal
call "%~dp0setenv.bat" >nul
REM Virtuelle Umgebung nutzen, falls vorhanden
if exist "%IWZ%\.venv\Scripts\activate.bat" (
call "%IWZ%\.venv\Scripts\activate.bat"
)
set "PY=py"
py --version >nul 2>&1 || set "PY=python"
echo.
echo === Ferienwohnungen Wolframm - Vorschau-Link erzeugen ===
cd /d "%IWZ%"
"%PY%" "%IWZ_LIB%\preview_link.py" %*
echo.
echo Link oben markieren und mit Strg+C kopieren.
echo.
endlocal
cmd /k
+77
View File
@@ -0,0 +1,77 @@
@echo off
REM ================================================================
REM IWZ Ferienwohnungen - Umgebungsvariablen Setup
REM ================================================================
if defined IWZ_BIN (
echo IWZ Umgebung bereits gesetzt.
goto :show
)
echo Setting up environment variables for IWZ ...
REM Basis-Projektpfad (Ordner ueber bin\)
set "IWZ=%~dp0.."
if "%IWZ:~-6%"=="bin\.." set "IWZ=%IWZ:~0,-6%"
if "%IWZ:~-1%"=="\" set "IWZ=%IWZ:~0,-1%"
REM Pfade fuer verschiedene Komponenten
set "IWZ_BIN=%IWZ%\bin"
set "IWZ_LIB=%IWZ%\lib"
set "IWZ_DOC=%IWZ%\doc"
set "IWZ_CONTENT=%IWZ%\content"
set "IWZ_LAYOUTS=%IWZ%\layouts"
set "IWZ_STATIC=%IWZ%\static"
set "IWZ_PUBLIC=%IWZ%\public"
set "IWZ_LOGS=%IWZ%\logs"
set "IWZ_RESULTS=%IWZ%\results"
REM Pfad zur .env (liegt jetzt in bin\). Die Python-Skripte lesen IWZ_ENV.
set "IWZ_ENV=%IWZ_BIN%\.env"
REM Python-Pfad erweitern (nur wenn noch nicht vorhanden)
echo %PYTHONPATH% | find /i "%IWZ_LIB%" >nul
if errorlevel 1 (
set "PYTHONPATH=%IWZ_LIB%;%PYTHONPATH%"
)
REM Ordner erstellen falls sie nicht existieren
if not exist "%IWZ_BIN%" mkdir "%IWZ_BIN%"
if not exist "%IWZ_LIB%" mkdir "%IWZ_LIB%"
if not exist "%IWZ_LOGS%" mkdir "%IWZ_LOGS%"
if not exist "%IWZ_RESULTS%" mkdir "%IWZ_RESULTS%"
REM Optional dauerhaft in der Registry setzen: bin\setenv.bat --persist
if /i "%1"=="--persist" (
echo Setze Umgebungsvariablen dauerhaft in der Registry ^(setx^)...
setx IWZ "%IWZ%" >nul
setx IWZ_BIN "%IWZ_BIN%" >nul
setx IWZ_LIB "%IWZ_LIB%" >nul
setx IWZ_ENV "%IWZ_ENV%" >nul
echo Dauerhaft gesetzt. Gilt fuer alle neuen Prozesse.
)
REM Umgebungsvariablen anzeigen
:show
echo.
echo ================================================================
echo IWZ ENVIRONMENT SETUP COMPLETE
echo ================================================================
echo IWZ = %IWZ%
echo IWZ_BIN = %IWZ_BIN%
echo IWZ_LIB = %IWZ_LIB%
echo IWZ_DOC = %IWZ_DOC%
echo IWZ_CONTENT = %IWZ_CONTENT%
echo IWZ_LAYOUTS = %IWZ_LAYOUTS%
echo IWZ_STATIC = %IWZ_STATIC%
echo IWZ_PUBLIC = %IWZ_PUBLIC%
echo IWZ_LOGS = %IWZ_LOGS%
echo IWZ_RESULTS = %IWZ_RESULTS%
echo IWZ_ENV = %IWZ_ENV%
echo PYTHONPATH = %PYTHONPATH%
echo ================================================================
echo.
REM Optionally keep window open
if "%1"=="--keep-open" pause
-10
View File
@@ -1,10 +0,0 @@
@echo off
echo.
echo === IWZ Ferienwohnungen - Deploy (nur Änderungen) ===
echo.
python deploy_lastdiff.py
if %errorlevel% neq 0 (
pause
exit /b 1
)
pause
-10
View File
@@ -1,10 +0,0 @@
@echo off
echo.
echo === IWZ Ferienwohnungen - Deploy ===
echo.
python deploy.py
if %errorlevel% neq 0 (
pause
exit /b 1
)
pause
+146
View File
@@ -0,0 +1,146 @@
# Nutzungsanalyse der Website (Server-Logs + GoAccess)
Die neue Hugo-Seite verzichtet bewusst auf JavaScript-Tracking (kein Google
Analytics, keine Cookies, kein Consent-Banner nötig). Stattdessen werten wir
die **Apache-Access-Logs** aus, die IONOS ohnehin auf dem Server schreibt.
Ablauf des Skripts `analyze.py`:
1. Verbindet sich per **SFTP** mit IONOS und lädt alle `access.log*`-Dateien
aus dem `/logs`-Ordner nach `./logs/` herunter.
2. Entpackt die rotierten `.gz`-Logs und fügt alles zu einer Datei zusammen.
3. Lässt **GoAccess** darüberlaufen und erzeugt einen HTML-Report unter
`./results/report_JJJJ-MM-TT.html`, der automatisch im Browser geöffnet wird.
`logs/` und `results/` stehen in `.gitignore` und werden nicht eingecheckt.
---
## Einmalige Einrichtung
### 1. Python-Abhängigkeit (paramiko)
```powershell
pip install -r requirements.txt
```
### 2. GoAccess (über WSL – empfohlen)
GoAccess wird offiziell nur für Linux/macOS gebaut und ist in keinem
Windows-Paketmanager verfügbar. Unter Windows ist der zuverlässigste Weg die
**WSL** (Windows Subsystem for Linux). `bin\install_py.bat` prüft automatisch,
ob WSL und GoAccess vorhanden sind, und zeigt sonst die passenden Befehle.
**WSL installieren** (falls noch nicht vorhanden, PowerShell als Administrator):
```powershell
wsl --install
```
Danach Windows neu starten und die Ubuntu-Ersteinrichtung (Benutzer/Passwort)
durchlaufen.
**GoAccess in der WSL installieren:**
```powershell
wsl -e sudo apt update
wsl -e sudo apt install -y goaccess
```
In der `.env` steht dafür (Standard):
```text
GOACCESS_MODE=wsl
```
Das Skript ruft GoAccess dann per `wsl goaccess ...` auf und übersetzt die
Windows-Pfade automatisch nach `/mnt/c/...`.
> **Alternative – nativer Windows-Build:** Falls du kein WSL nutzen willst,
> lade den Cygwin-Build von <https://goaccess.io/download>, entpacke ihn (z. B.
> nach `C:\tools\goaccess\`) und setze in der `.env`:
>
> ```text
> GOACCESS_MODE=exe
> GOACCESS_EXE=C:\tools\goaccess\goaccess.exe
> ```
### 3. IONOS-SFTP-Zugang in `.env` eintragen
Die Zugangsdaten stehen im IONOS-Kundenbereich unter **Hosting → SFTP/SSH**.
Es sind dieselben, mit denen der `/logs`-Ordner erreichbar ist.
```text
IONOS_SFTP_HOST=<z.B. home123456789.1and1-data.host oder access-5000000000.webspace-host.com>
IONOS_SFTP_PORT=22
IONOS_SFTP_USER=<SFTP-Benutzer>
IONOS_SFTP_PASSWORD=<SFTP-Passwort>
IONOS_LOGS_DIR=/logs
```
> Die `.env` steht in `.gitignore` und wird **nicht** eingecheckt.
---
## Nutzung
```powershell
# Logs neu laden, Report bauen und im Browser öffnen:
analyze.bat
# oder:
python analyze.py
# Nur neu auswerten (ohne erneuten Download der schon geladenen Logs):
python analyze.py --no-download
```
Beim wiederholten Lauf werden bereits geladene, rotierte Logs übersprungen
(gleiche Dateigröße) – nur das aktuell wachsende Log wird neu geholt.
---
## Was im Report steht
GoAccess zeigt u. a.:
- **Eindeutige Besucher** und Seitenaufrufe pro Tag
- **Meistbesuchte Seiten** (welche Ferienwohnung interessiert am meisten?)
- **Referrer** (woher kommen die Besucher – Google, direkte Eingabe, Links)
- **Länder / Sprachen**, Browser, Betriebssysteme, Geräte
- **HTTP-Statuscodes** (404-Fehler = tote Links finden)
- **Traffic-Volumen**
Aufrufe von Suchmaschinen-Bots werden herausgefiltert (`--ignore-crawlers`),
IP-Adressen im Report anonymisiert (`--anonymize-ip`).
---
## DSGVO-Hinweis
Diese Auswertung passiert **nur lokal auf deinem Rechner** aus den ohnehin
anfallenden Server-Logs. Es wird nichts an Dritte übertragen und kein
Tracking-Code beim Besucher ausgeführt. Server-Logs mit IP-Adressen dürfen zu
Sicherheits-/Betriebszwecken vorübergehend gespeichert werden; im GoAccess-
Report sind die IPs zusätzlich anonymisiert. Ein Cookie-Consent-Banner ist für
diese Art der Auswertung **nicht** erforderlich.
---
## Fehlerbehebung
**Report bleibt leer / „No data".**
Dann passt das Log-Format nicht. IONOS nutzt meist das *Combined Log Format*
(`LOG_FORMAT=COMBINED`). Wirf einen Blick in die erste Zeile von
`logs/access.log.current`:
- Beginnt sie mit einer IP und enthält am Ende `"...Referrer" "...User-Agent"`
→ `COMBINED` ist korrekt.
- Fehlen Referrer/User-Agent → `LOG_FORMAT=COMMON`.
- Steht der Vhost-Name vor der IP → `LOG_FORMAT=VCOMBINED`.
Alternativ ein eigenes Format als GoAccess-`--log-format`-String in `.env`
hinterlegen (siehe <https://goaccess.io/man#custom-log>).
**`paramiko`-Authentifizierungsfehler.**
SFTP-Host/-User/-Passwort im IONOS-Kundenbereich gegenprüfen. Der SFTP-Benutzer
ist nicht zwingend identisch mit dem Login des Kundenbereichs.
+226
View File
@@ -0,0 +1,226 @@
#!/usr/bin/env python3
"""
IWZ Ferienwohnungen - Nutzungsanalyse
Laedt die Apache-Access-Logs per SFTP von IONOS herunter, entpackt die
rotierten .gz-Dateien und erzeugt mit GoAccess einen HTML-Report.
Kein JavaScript-Tracking, keine Cookies beim Besucher -> DSGVO-freundlich.
Die Auswertung passiert rein aus den Server-Logs.
Voraussetzungen:
- pip install -r requirements.txt (paramiko)
- GoAccess Windows-Build (Pfad in .env: GOACCESS_EXE)
- .env mit IONOS_SFTP_* befuellt (siehe .env.example)
Aufruf:
python analyze.py # Logs laden + Report bauen + oeffnen
python analyze.py --no-download # nur neu auswerten (schon geladene Logs)
"""
import gzip
import os
import shutil
import stat
import subprocess
import sys
import webbrowser
from datetime import date
from pathlib import Path
try:
import paramiko
except ImportError:
print("FEHLER: paramiko fehlt. Bitte 'pip install -r requirements.txt' ausfuehren.")
sys.exit(1)
PROJECT_ROOT = Path(__file__).resolve().parent.parent # Skript liegt in lib/
LOGS_DIR = PROJECT_ROOT / "logs" # heruntergeladene Rohlogs (in .gitignore)
RESULTS_DIR = PROJECT_ROOT / "results" # erzeugte Reports (in .gitignore)
SFTP_PORT = 22
def env_file():
"""Pfad zur .env: bevorzugt IWZ_ENV (von setenv.bat), sonst bin/.env."""
from_var = os.environ.get("IWZ_ENV")
if from_var:
return Path(from_var)
return PROJECT_ROOT / "bin" / ".env"
# Nur diese Dateien aus dem IONOS /logs-Ordner interessieren uns.
# Alles andere (sftp.log, traffic.db, forensic/, info.*) wird ignoriert.
ACCESS_LOG_PREFIX = "access.log"
def load_env(path):
env = {}
if not path.exists():
return env
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
env[key.strip()] = value.strip()
return env
def download_access_logs(host, port, user, password, remote_dir):
"""Laedt alle access.log*-Dateien aus dem IONOS /logs-Ordner nach LOGS_DIR."""
print(f"\n-> Verbinde per SFTP mit {host}:{port} ...")
transport = paramiko.Transport((host, port))
try:
transport.connect(username=user, password=password)
sftp = paramiko.SFTPClient.from_transport(transport)
sftp.chdir(remote_dir)
LOGS_DIR.mkdir(exist_ok=True)
count = 0
for attr in sftp.listdir_attr("."):
name = attr.filename
if not name.startswith(ACCESS_LOG_PREFIX):
continue
if stat.S_ISDIR(attr.st_mode):
continue
local_path = LOGS_DIR / name
# Schon vorhanden und gleich gross? -> ueberspringen (rotierte Logs
# aendern sich nicht mehr; nur .current / aktueller Tag waechst).
if (local_path.exists()
and local_path.stat().st_size == attr.st_size
and not name.endswith(".current")):
continue
print(f" lade {name} ({attr.st_size} Bytes)")
sftp.get(name, str(local_path))
count += 1
sftp.close()
print(f" {count} Datei(en) geladen/aktualisiert.")
finally:
transport.close()
def collect_log_lines():
"""Fuehrt alle access.log*-Dateien (auch .gz) zu einem kombinierten Log zusammen.
Gibt den Pfad zur kombinierten Klartext-Logdatei zurueck.
"""
combined = LOGS_DIR / "_combined.log"
sources = sorted(
p for p in LOGS_DIR.iterdir()
if p.is_file()
and p.name.startswith(ACCESS_LOG_PREFIX)
and p.name != combined.name
)
if not sources:
print("\nFEHLER: keine access.log*-Dateien in ./logs/ gefunden.")
print("Erst 'python analyze.py' (ohne --no-download) ausfuehren.")
sys.exit(1)
print(f"\n-> Fuehre {len(sources)} Logdatei(en) zusammen ...")
with open(combined, "wb") as out:
for src in sources:
if src.suffix == ".gz":
with gzip.open(src, "rb") as f:
shutil.copyfileobj(f, out)
else:
with open(src, "rb") as f:
shutil.copyfileobj(f, out)
return combined
def to_wsl_path(win_path):
"""Uebersetzt einen Windows-Pfad (C:\\...) in einen WSL-Pfad (/mnt/c/...)."""
p = Path(win_path).resolve()
drive = p.drive.rstrip(":").lower() # "C:" -> "c"
rest = p.as_posix()[len(p.drive):] # Rest ohne Laufwerk, mit /
return f"/mnt/{drive}{rest}"
def goaccess_args(log_format):
"""Gemeinsame GoAccess-Optionen (ohne Ein-/Ausgabedateien)."""
return [
"--log-format=" + log_format,
"--anonymize-ip", # DSGVO: IPs im Report anonymisieren
"--ignore-crawlers", # Bots aus den Besucherzahlen filtern
"--html-report-title=IWZ Ferienwohnungen - Zugriffsstatistik",
]
def run_goaccess(mode, goaccess_exe, log_file, report_file, log_format):
"""Ruft GoAccess auf und erzeugt einen eigenstaendigen HTML-Report.
mode="wsl": GoAccess laeuft in der WSL (Standard), Pfade werden nach
/mnt/c/... uebersetzt.
mode="exe": nativer Windows-Build unter GOACCESS_EXE.
"""
print(f"\n-> GoAccess ({mode}) -> {report_file.name}")
if mode == "wsl":
cmd = [
"wsl", "goaccess",
to_wsl_path(log_file),
"-o", to_wsl_path(report_file),
] + goaccess_args(log_format)
not_found_hint = (
"GoAccess ist in der WSL nicht installiert (oder WSL fehlt).\n"
" In der WSL installieren: wsl -e sudo apt update ^&^& "
"sudo apt install -y goaccess\n"
"Siehe auch bin/install_py.bat (prueft WSL + GoAccess) und doc/analyse.md."
)
else:
cmd = [
goaccess_exe,
str(log_file),
"-o", str(report_file),
] + goaccess_args(log_format)
not_found_hint = (
f"GoAccess-EXE nicht gefunden ('{goaccess_exe}').\n"
" Windows-Build laden: https://goaccess.io/download, entpacken,\n"
" dann GOACCESS_EXE in bin/.env auf die goaccess.exe setzen."
)
try:
result = subprocess.run(cmd)
except FileNotFoundError:
print("\nFEHLER: GoAccess konnte nicht gestartet werden.")
print(not_found_hint)
sys.exit(1)
if result.returncode != 0:
print("\nFEHLER: GoAccess fehlgeschlagen.")
print("Pruefe die GoAccess-Installation und das Log-Format (LOG_FORMAT).")
sys.exit(1)
if __name__ == "__main__":
print("=== IWZ Nutzungsanalyse ===")
no_download = "--no-download" in sys.argv
env = {**load_env(env_file()), **os.environ}
host = env.get("IONOS_SFTP_HOST")
port = int(env.get("IONOS_SFTP_PORT", SFTP_PORT))
user = env.get("IONOS_SFTP_USER")
password = env.get("IONOS_SFTP_PASSWORD")
remote_dir = env.get("IONOS_LOGS_DIR", "/logs")
# GoAccess-Aufruf: "wsl" (Standard) oder "exe" (nativer Windows-Build).
goaccess_mode = env.get("GOACCESS_MODE", "wsl").strip().lower()
goaccess_exe = env.get("GOACCESS_EXE", "goaccess")
# IONOS liefert i.d.R. Apache "Combined Log Format". Bei anderen
# Formaten hier anpassen (siehe doc/analyse.md).
log_format = env.get("LOG_FORMAT", "COMBINED")
if not no_download:
if not host or not user or not password:
print("\nFEHLER: IONOS_SFTP_HOST / _USER / _PASSWORD fehlen in bin/.env.")
print("Siehe bin/.env.example. Oder mit --no-download nur neu auswerten.")
sys.exit(1)
download_access_logs(host, port, user, password, remote_dir)
else:
print("\n-> Download uebersprungen (--no-download).")
combined = collect_log_lines()
RESULTS_DIR.mkdir(exist_ok=True)
report_file = RESULTS_DIR / f"report_{date.today().isoformat()}.html"
run_goaccess(goaccess_mode, goaccess_exe, combined, report_file, log_format)
print(f"\nFertig! Report: {report_file}")
webbrowser.open(report_file.as_uri())
+11 -3
View File
@@ -13,11 +13,19 @@ import urllib.parse
import urllib.request
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent
PROJECT_ROOT = Path(__file__).resolve().parent.parent # Skript liegt in lib/
PUBLIC_DIR = PROJECT_ROOT / "public"
FTP_PORT = 21
def env_file():
"""Pfad zur .env: bevorzugt IWZ_ENV (von setenv.bat), sonst bin/.env."""
from_var = os.environ.get("IWZ_ENV")
if from_var:
return Path(from_var)
return PROJECT_ROOT / "bin" / ".env"
def load_env(path):
env = {}
if not path.exists():
@@ -99,7 +107,7 @@ def purge_cdn_cache(pull_zone_url, api_key):
if __name__ == "__main__":
print("=== IWZ Deploy ===")
env = {**load_env(PROJECT_ROOT / ".env"), **os.environ}
env = {**load_env(env_file()), **os.environ}
zone = env.get("BUNNY_STORAGE_ZONE")
host = env.get("BUNNY_STORAGE_HOST", "storage.bunnycdn.com")
password = env.get("BUNNY_STORAGE_PASSWORD")
@@ -108,7 +116,7 @@ if __name__ == "__main__":
if not zone or not password:
print("\nFEHLER: BUNNY_STORAGE_ZONE / BUNNY_STORAGE_PASSWORD fehlen.")
print("Siehe .env.example -> .env kopieren und befuellen.")
print("Siehe bin/.env.example -> bin/.env kopieren und befuellen.")
sys.exit(1)
hugo_build()
+11 -3
View File
@@ -14,12 +14,20 @@ import subprocess
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent
PROJECT_ROOT = Path(__file__).resolve().parent.parent # Skript liegt in lib/
PUBLIC_DIR = PROJECT_ROOT / "public"
MANIFEST_PATH = PROJECT_ROOT / ".deploy_manifest.json"
FTP_PORT = 21
def env_file():
"""Pfad zur .env: bevorzugt IWZ_ENV (von setenv.bat), sonst bin/.env."""
from_var = os.environ.get("IWZ_ENV")
if from_var:
return Path(from_var)
return PROJECT_ROOT / "bin" / ".env"
def load_env(path):
env = {}
if not path.exists():
@@ -124,14 +132,14 @@ def deploy_to_bunny(host, zone, password, changed, deleted):
if __name__ == "__main__":
print("=== IWZ Deploy (nur Änderungen) ===")
env = {**load_env(PROJECT_ROOT / ".env"), **os.environ}
env = {**load_env(env_file()), **os.environ}
zone = env.get("BUNNY_STORAGE_ZONE")
host = env.get("BUNNY_STORAGE_HOST", "storage.bunnycdn.com")
password = env.get("BUNNY_STORAGE_PASSWORD")
if not zone or not password:
print("\nFEHLER: BUNNY_STORAGE_ZONE / BUNNY_STORAGE_PASSWORD fehlen.")
print("Siehe .env.example -> .env kopieren und befuellen.")
print("Siehe bin/.env.example -> bin/.env kopieren und befuellen.")
sys.exit(1)
hugo_build()
+12 -3
View File
@@ -6,15 +6,24 @@ fuer die per Bunny Token Authentication geschuetzte Pull Zone.
import base64
import hashlib
import os
import sys
import time
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent
PROJECT_ROOT = Path(__file__).resolve().parent.parent # Skript liegt in lib/
GUELTIGKEIT_SEKUNDEN = 7 * 24 * 60 * 60 # 7 Tage
URL_PATH = "/"
def env_file():
"""Pfad zur .env: bevorzugt IWZ_ENV (von setenv.bat), sonst bin/.env."""
from_var = os.environ.get("IWZ_ENV")
if from_var:
return Path(from_var)
return PROJECT_ROOT / "bin" / ".env"
def load_env(path):
env = {}
if not path.exists():
@@ -36,12 +45,12 @@ def build_signed_url(pull_zone_url, auth_key, url_path, expires):
if __name__ == "__main__":
env = load_env(PROJECT_ROOT / ".env")
env = load_env(env_file())
pull_zone_url = env.get("BUNNY_PULL_ZONE_URL", "").rstrip("/")
auth_key = env.get("BUNNY_AUTH_KEY")
if not pull_zone_url or not auth_key:
print("\nFEHLER: BUNNY_PULL_ZONE_URL / BUNNY_AUTH_KEY fehlen in .env.")
print("\nFEHLER: BUNNY_PULL_ZONE_URL / BUNNY_AUTH_KEY fehlen in bin/.env.")
print("Siehe .env.example. Auth Key: Pull Zone -> Security -> Token Authentication.")
sys.exit(1)
-8
View File
@@ -1,8 +0,0 @@
@echo off
echo.
echo === Ferienwohnungen Wolframm - Vorschau-Link erzeugen ===
python preview_link.py
echo.
echo Link oben markieren und mit Strg+C kopieren.
echo.
cmd /k
+9
View File
@@ -0,0 +1,9 @@
# Python-Abhaengigkeiten fuer die lib/-Skripte.
#
# deploy.py / deploy_lastdiff.py / preview_link.py:
# nutzen ausschliesslich die Standardbibliothek (ftplib, urllib, hashlib,
# json, subprocess ...) -> keine externen Pakete noetig.
#
# analyze.py:
# paramiko fuer den SFTP-Download der IONOS-Access-Logs.
paramiko