@
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:
@@ -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=
|
||||
@@ -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/
|
||||
|
||||
@@ -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`).
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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%
|
||||
@@ -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%
|
||||
@@ -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%
|
||||
@@ -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%"
|
||||
@@ -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 ================================================================
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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())
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user