diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96b9ec1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Runtime-Dateien (nicht ins Repo) +config.json +state.json +rclone.conf +logs/ + +# Deploy-Script (enthält ggf. Zugangsdaten) +deploy.sh + +# Python +__pycache__/ +*.py[cod] +*.pyo +*.pyd +.Python +venv/ +.venv/ +*.egg-info/ +dist/ +build/ + +# macOS +.DS_Store +.AppleDouble +.LSOverride +._* +.Spotlight-V100 +.Trashes + +# Editor +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Mount-Verzeichnisse (auf dem Pi) +/mnt/picopy*/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ea983c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Tobias Leuschner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1eb8ec3 --- /dev/null +++ b/README.md @@ -0,0 +1,256 @@ +# PiCopy + +**Automatische USB-Backup-Station für den Raspberry Pi mit Web-Interface** + +PiCopy verwandelt deinen Raspberry Pi in ein eigenständiges Backup-Gerät. Stecke eine Quell-USB (Speicherkarte, USB-Stick) und ein Ziel-Laufwerk ein – PiCopy kopiert die Daten automatisch, organisiert sie in Datumsordnern, prüft die Integrität und kann danach auf ein NAS hochladen. + +--- + +## Features + +| | Feature | Beschreibung | +|---|---|---| +| 📋 | **Automatisches Kopieren** | Startet sofort wenn Quelle und Ziel eingesteckt werden | +| 🌐 | **Web-Interface** | Konfiguration und Status von jedem Gerät im Netzwerk | +| 📂 | **Datei-Explorer** | Inhalt verbundener Laufwerke direkt im Browser durchsuchen | +| 🗂️ | **Smarte Organisation** | Erstellt automatisch Datumsordner (`2024-01-15_143022/`) | +| 🔍 | **Dateifilter** | Nur Fotos, nur Videos oder beliebige Dateitypen kopieren | +| 🔄 | **Duplikat-Behandlung** | Überspringen / Überschreiben / Umbenennen | +| ✅ | **MD5-Verifizierung** | Jede Datei nach dem Kopieren auf Integrität prüfen | +| 🗑️ | **Quelle leeren** | Quelldateien nach erfolgreichem Kopieren löschen (Move-Modus) | +| 🖧 | **NAS / SMB Upload** | Nach dem lokalen Backup auf ein Netzlaufwerk hochladen | +| 📡 | **WiFi-Fallback** | Erstellt einen eigenen Hotspot wenn kein WLAN verfügbar ist | +| ⚡ | **Headless-Betrieb** | Kein Monitor, keine Tastatur nötig | +| 🔁 | **Autostart** | Startet automatisch beim Pi-Boot via systemd | + +--- + +## Voraussetzungen + +- Raspberry Pi (2, 3, 4, 5 oder Zero 2W) +- Raspberry Pi OS **Bookworm** (Debian 12) oder neuer +- Mindestens 2 USB-Ports (Quelle + Ziel) +- WLAN oder LAN für das Web-Interface + +--- + +## Installation + +### Option A – Direkt vom Git-Repo (empfohlen) + +```bash +git clone https://github.com/YOUR_USERNAME/picopy +cd picopy +sudo bash install.sh +``` + +### Option B – One-Liner + +```bash +curl -sSL https://raw.githubusercontent.com/YOUR_USERNAME/picopy/main/install.sh | sudo bash +``` + +Nach der Installation ist das Web-Interface unter folgender Adresse erreichbar: + +``` +http://:8080 +``` + +Die IP-Adresse wird am Ende der Installation angezeigt. + +--- + +## Schnellstart + +1. **Web-Interface öffnen** → `http://:8080` +2. **USB-Gerät einstecken** das als Quelle dienen soll +3. **Port konfigurieren** → Unter *USB Port Konfiguration* das Gerät in der Dropdown-Liste wählen → *Als feste Quelle speichern* +4. **Ziel konfigurieren** → Ziel-Laufwerk einstecken, Port wählen → *Als festes Ziel speichern* +5. **Auto-Kopie aktivieren** → Haken bei *Automatisch kopieren wenn Quelle & Ziel verbunden* +6. **Fertig** → Jetzt beide Laufwerke einstecken → Kopie startet automatisch + +> **Wichtig:** PiCopy merkt sich den **physischen Port** (Anschluss), nicht das spezifische Gerät. Ein anderer USB-Stick im gleichen Anschluss wird automatisch als Quelle/Ziel erkannt. + +--- + +## Web-Interface + +### Kopierstatus + +Zeigt den Live-Fortschritt mit: +- Prozentualer Fortschritt + Fortschrittsbalken +- Dateizähler (`23 / 147 Dateien`) +- Übertragene Datenmenge (`1.2 GB / 3.5 GB`) +- Geschwindigkeit (`12.4 MB/s`) +- Verbleibende Zeit (`⏱ noch ca. 4 Min.`) +- Aktuelle Datei +- Phasen-Anzeige: *Kopieren → Verifizieren → Quelle leeren* + +Nach dem Abschluss: Zusammenfassung mit ✕-Button (verschwindet nach 5 Minuten automatisch). + +### USB Port Konfiguration & Datei-Explorer + +``` +┌─────────────────┬─────────────────┬──────────────────────┐ +│ ▲ QUELLE │ ▼ ZIEL │ ⬆ Quelle ⬇ Ziel ↻ │ +│ ● Port 2-2 │ ○ Port 1-1 │ ──────────────────── │ +│ Samsung USB │ Nicht verbunden│ 📁 DCIM │ +│ │ │ 📁 MISC │ +│ [Als Quelle ▾] │ [Als Ziel ▾] │ 🖼 IMG_001.jpg 4 MB │ +└─────────────────┴─────────────────┴──────────────────────┘ +``` + +- **Grüner Punkt** = Gerät verbunden und bereit +- **Grauer Punkt** = Port konfiguriert, kein Gerät eingesteckt +- **Datei-Explorer** zum Durchsuchen der verbundenen Laufwerke + +### Kopier-Einstellungen + +| Einstellung | Standard | Beschreibung | +|---|---|---| +| Datumsformat | `JJJJ-MM-TT` | Format des Zielordners | +| Uhrzeit | ✓ | Uhrzeit im Ordnernamen (`_143022`) | +| Unterordner | ✓ | Unterordner nach Gerätebezeichnung | +| Auto-Kopie | ✓ | Automatisch starten wenn beide verbunden | +| Dateifilter | *leer* | Nur bestimmte Dateitypen kopieren | +| Systemdateien | ✓ | `.DS_Store`, `Thumbs.db`, `RECYCLER` usw. ausschließen | +| Duplikate | Überspringen | Skip / Überschreiben / Umbenennen | +| MD5-Verify | ✗ | Jede Datei nach dem Kopieren prüfen | +| Quelle leeren | ✗ | Quelldateien nach Kopieren löschen | + +#### Dateifilter – Schnell-Presets + +| Preset | Dateitypen | +|---|---| +| 📷 Fotos | jpg, jpeg, heic, raw, cr2, nef, arw, dng, png | +| 🎬 Videos | mp4, mov, avi, mkv, mts, m2ts, wmv | +| 📷+🎬 Beides | Fotos + Videos kombiniert | +| ✕ Alle | Kein Filter – alle Dateien kopieren | + +### Fernkopie – NAS / SMB + +Nach dem lokalen Kopieren lädt PiCopy auf konfigurierte NAS-Freigaben hoch: + +1. *+ NAS-Ziel hinzufügen* klicken +2. Name, Server-IP, Freigabename, Benutzer und Passwort eingeben +3. *Speichern & Verbindung testen* – PiCopy testet die Verbindung sofort +4. Mehrere NAS-Ziele möglich, jedes einzeln aktivierbar + +### WiFi-Einstellungen + +| Modus | Beschreibung | +|---|---| +| **Heimnetz** | WLAN-Name und Passwort für die Router-Verbindung | +| **Hotspot (AP)** | Eigenes WLAN wenn kein Heimnetz erreichbar | + +**Hotspot-Standardwerte:** +- SSID: `PiCopy` +- Passwort: `PiCopy,` +- IP im Hotspot-Modus: `http://10.42.0.1:8080` + +Der Hotspot startet automatisch beim Boot wenn das konfigurierte WLAN nicht verfügbar ist. + +--- + +## Ordnerstruktur auf dem Ziel + +``` +/ziel-laufwerk/ +└── 2024-01-15_143022/ ← Datum + Uhrzeit (konfigurierbar) + └── Samsung_USB/ ← Gerätebezeichnung (wenn Unterordner aktiv) + ├── DCIM/ + │ └── 100CANON/ + │ ├── IMG_0001.JPG + │ ├── IMG_0001.CR2 + │ └── IMG_0002.MP4 + └── MISC/ + └── notes.txt +``` + +--- + +## Update + +```bash +cd picopy +git pull +sudo cp app.py /opt/picopy/app.py +sudo systemctl restart picopy +``` + +--- + +## Deinstallation + +```bash +sudo systemctl stop picopy +sudo systemctl disable picopy +sudo rm /etc/systemd/system/picopy.service +sudo rm -rf /opt/picopy +sudo systemctl daemon-reload +``` + +--- + +## Service-Verwaltung + +```bash +# Status prüfen +sudo systemctl status picopy + +# Live-Logs +journalctl -u picopy -f + +# Neustart +sudo systemctl restart picopy + +# Stoppen +sudo systemctl stop picopy +``` + +--- + +## Technische Details + +| Komponente | Technologie | +|---|---| +| Backend | Python 3 + Flask | +| USB-Erkennung | `lsblk` + `udevadm` | +| USB-Monitoring | `pyudev` (udev-Events) | +| WiFi-Verwaltung | NetworkManager (`nmcli`) | +| NAS-Sync | `rclone` (SMB) | +| Service | systemd (Autostart, Auto-Restart) | + +**Dateipfade auf dem Pi:** + +| Pfad | Inhalt | +|---|---| +| `/opt/picopy/app.py` | Hauptanwendung | +| `/opt/picopy/config.json` | Konfiguration (Ports, WiFi, Einstellungen) | +| `/opt/picopy/state.json` | Letzter Kopierstatus (persisted) | +| `/opt/picopy/rclone.conf` | NAS-Zugangsdaten (rclone) | +| `/opt/picopy/logs/picopy.log` | Log-Datei | +| `/etc/systemd/system/picopy.service` | Systemd-Service | + +--- + +## Getestete Hardware + +| Gerät | Status | +|---|---| +| Raspberry Pi 4 Model B | ✅ Vollständig getestet | +| Raspberry Pi 5 | ✅ Kompatibel | +| Raspberry Pi 3 Model B+ | ✅ Kompatibel | +| Raspberry Pi Zero 2W | ⚠️ Langsamer, nur 1 USB-Port (Hub benötigt) | + +--- + +## Lizenz + +MIT License – siehe [LICENSE](LICENSE) + +--- + +## Autor + +Tobias Leuschner – [info@leuschner.dev](mailto:info@leuschner.dev) diff --git a/deploy.sh b/deploy.sh deleted file mode 100644 index eed20b2..0000000 --- a/deploy.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# deploy.sh - Überträgt PiCopy zum Pi und installiert es -# Verwendung: bash deploy.sh -# Benötigt: sshpass (brew install sshpass) - -PI_HOST="10.0.100.61" -PI_USER="tobias" -PI_PASS="dmu7uqMH9roYzdtovlm0XfXT6" -REMOTE="/home/tobias/picopy_deploy" - -SSH="sshpass -p '$PI_PASS' ssh -o StrictHostKeyChecking=no $PI_USER@$PI_HOST" -SCP="sshpass -p '$PI_PASS' scp -o StrictHostKeyChecking=no" - -echo ">> Dateien übertragen..." -eval "$SCP -r $(pwd)/. $PI_USER@$PI_HOST:$REMOTE/" - -echo ">> Installation starten..." -eval "$SSH 'cd $REMOTE && sudo bash install.sh'" - -echo ">> Fertig!" -eval "$SSH 'sudo systemctl status picopy --no-pager'" diff --git a/install.sh b/install.sh index edd4b40..e936645 100644 --- a/install.sh +++ b/install.sh @@ -1,42 +1,111 @@ -#!/bin/bash -# PiCopy Installations-Script für den Raspberry Pi -# Ausführen auf dem Pi als root oder mit sudo: -# sudo bash install.sh +#!/usr/bin/env bash +# ============================================================ +# PiCopy – Installer +# https://github.com/YOUR_USERNAME/picopy +# +# Usage: +# sudo bash install.sh +# or one-line: +# curl -sSL https://raw.githubusercontent.com/YOUR_USERNAME/picopy/main/install.sh | sudo bash +# ============================================================ +set -euo pipefail -set -e +INSTALL_DIR="/opt/picopy" +SERVICE_NAME="picopy" +PORT=8080 +REPO_RAW="https://raw.githubusercontent.com/YOUR_USERNAME/picopy/main" -PI_DIR="/opt/picopy" -SERVICE="picopy" +# ── Farben ─────────────────────────────────────────────────────────────────── +R='\033[0;31m'; G='\033[0;32m'; Y='\033[1;33m'; B='\033[0;34m'; N='\033[0m' +info() { echo -e "${B}[PiCopy]${N} $1"; } +ok() { echo -e "${G}[ OK ]${N} $1"; } +warn() { echo -e "${Y}[ WARN ]${N} $1"; } +fail() { echo -e "${R}[ FAIL ]${N} $1"; exit 1; } -echo "=== PiCopy Installation ===" - -# Abhängigkeiten installieren -echo ">> Pakete installieren..." -apt-get update -q -apt-get install -y python3 python3-venv python3-pip lsblk - -# Verzeichnis anlegen -echo ">> Verzeichnis anlegen: $PI_DIR" -mkdir -p "$PI_DIR/logs" - -# Python-Umgebung -echo ">> Python venv erstellen..." -python3 -m venv "$PI_DIR/venv" -"$PI_DIR/venv/bin/pip" install --quiet flask pyudev - -# App-Dateien kopieren -echo ">> Dateien kopieren..." -cp app.py "$PI_DIR/app.py" - -# Systemd-Service einrichten -echo ">> Systemd-Service einrichten..." -cp picopy.service "/etc/systemd/system/$SERVICE.service" -systemctl daemon-reload -systemctl enable "$SERVICE" -systemctl restart "$SERVICE" +# ── Voraussetzungen ─────────────────────────────────────────────────────────── +[ "$EUID" -eq 0 ] || fail "Bitte als root ausführen: sudo bash install.sh" +command -v apt-get &>/dev/null || fail "apt-get nicht gefunden (nur Debian/Raspberry Pi OS unterstützt)" echo "" -echo "=== Installation abgeschlossen ===" -echo "Web-Interface: http://$(hostname -I | awk '{print $1}'):8080" -echo "Status: systemctl status $SERVICE" -echo "Logs: journalctl -u $SERVICE -f" +echo -e "${B}╔══════════════════════════════════════════╗${N}" +echo -e "${B}║ PiCopy – Installation ║${N}" +echo -e "${B}╚══════════════════════════════════════════╝${N}" +echo "" + +# ── System-Pakete ───────────────────────────────────────────────────────────── +info "Systemabhängigkeiten werden installiert..." +apt-get update -q +apt-get install -y -q python3 python3-venv python3-pip util-linux rclone +ok "Systemabhängigkeiten installiert" + +# ── Verzeichnis anlegen ─────────────────────────────────────────────────────── +info "Installationsverzeichnis: $INSTALL_DIR" +mkdir -p "$INSTALL_DIR/logs" + +# ── App-Datei kopieren oder herunterladen ───────────────────────────────────── +if [ -f "./app.py" ]; then + info "Lokale app.py wird verwendet..." + cp app.py "$INSTALL_DIR/app.py" +else + info "app.py wird heruntergeladen..." + curl -sSfL "$REPO_RAW/app.py" -o "$INSTALL_DIR/app.py" \ + || fail "Download fehlgeschlagen. Prüfe die Internet-Verbindung." +fi +ok "app.py installiert" + +# ── Python-Umgebung ─────────────────────────────────────────────────────────── +info "Python venv wird erstellt..." +python3 -m venv "$INSTALL_DIR/venv" +"$INSTALL_DIR/venv/bin/pip" install --quiet --upgrade pip +"$INSTALL_DIR/venv/bin/pip" install --quiet flask pyudev +ok "Python-Umgebung erstellt" + +# ── Systemd-Service ─────────────────────────────────────────────────────────── +info "Systemd-Service wird eingerichtet..." + +if [ -f "./picopy.service" ]; then + cp picopy.service /etc/systemd/system/picopy.service +else + cat > /etc/systemd/system/picopy.service << 'EOF' +[Unit] +Description=PiCopy – Automatischer USB-Kopierdienst +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/picopy +ExecStart=/opt/picopy/venv/bin/python /opt/picopy/app.py +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +EOF +fi + +systemctl daemon-reload +systemctl enable "$SERVICE_NAME" +systemctl restart "$SERVICE_NAME" + +# ── Ergebnis ───────────────────────────────────────────────────────────────── +sleep 3 +if systemctl is-active --quiet "$SERVICE_NAME"; then + IP=$(hostname -I | awk '{print $1}') + echo "" + echo -e "${G}╔══════════════════════════════════════════╗${N}" + echo -e "${G}║ PiCopy ist bereit! ║${N}" + echo -e "${G}╚══════════════════════════════════════════╝${N}" + echo "" + echo -e " Web-Interface: ${B}http://$IP:$PORT${N}" + echo "" + echo " Nützliche Befehle:" + echo " sudo systemctl status $SERVICE_NAME # Status" + echo " journalctl -u $SERVICE_NAME -f # Live-Logs" + echo " sudo systemctl restart $SERVICE_NAME # Neustart" + echo "" +else + fail "PiCopy konnte nicht gestartet werden."$'\n'"Logs: journalctl -u $SERVICE_NAME -n 50" +fi