d42d6d5d96
Mehrere SFTP-Backup-Ziele konfigurierbar mit:
- Host, Port, Benutzername, Passwort, Remote-Pfad
- Konfigurierbares Intervall (15 Min. bis woechentlich oder deaktiviert)
- Maximale Anzahl aufbewahrter Versionen (aeltere werden automatisch geloescht)
- Aktiv/Inaktiv-Toggle pro Ziel
Features:
- Automatischer Hintergrund-Scheduler prueft alle 60 Sekunden ob
Backups faellig sind und fuehrt sie aus
- Manuelles Backup per Klick ("Jetzt sichern")
- SFTP-Verbindungstest-Button
- Versionen-Dialog: Alle Backup-Versionen auf dem SFTP-Server auflisten
mit Groesse und Datum
- Restore direkt von SFTP: Version auswaehlen -> wird heruntergeladen
und ueber die bestehende DB-Merge-Logik wiederhergestellt
- Chunked Upload zum SFTP in 16MB-Bloecken (fuer grosse Backups)
- Status-Anzeige: Letztes Backup, Erfolg/Fehler, Nachricht
Backend: BackupTarget Model, SFTP-Service (paramiko), Backup-Scheduler
API: /admin/backup/targets CRUD, /test, /run, /versions, /restore
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
80 lines
2.5 KiB
Python
80 lines
2.5 KiB
Python
import os
|
|
from pathlib import Path
|
|
|
|
from flask import Flask, redirect, send_from_directory
|
|
from flask_cors import CORS
|
|
|
|
from app.config import Config
|
|
from app.extensions import db, bcrypt, migrate
|
|
|
|
|
|
def create_app(config_class=Config):
|
|
# Check if static frontend build exists (Docker production mode)
|
|
static_dir = Path(__file__).resolve().parent.parent / 'static'
|
|
if static_dir.exists():
|
|
app = Flask(__name__, static_folder=str(static_dir), static_url_path='')
|
|
else:
|
|
app = Flask(__name__)
|
|
|
|
app.config.from_object(config_class)
|
|
|
|
# Ensure data directories exist
|
|
Path(app.config['UPLOAD_PATH']).mkdir(parents=True, exist_ok=True)
|
|
db_dir = Path(app.config['SQLALCHEMY_DATABASE_URI'].replace('sqlite:///', '')).parent
|
|
db_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Initialize extensions
|
|
db.init_app(app)
|
|
bcrypt.init_app(app)
|
|
migrate.init_app(app, db)
|
|
|
|
# CORS
|
|
CORS(app, resources={r'/api/*': {'origins': app.config['FRONTEND_URL']}},
|
|
supports_credentials=True)
|
|
|
|
# Register blueprints
|
|
from app.api import api_bp
|
|
app.register_blueprint(api_bp)
|
|
|
|
# Well-known URLs for CalDAV/CardDAV auto-discovery (iOS, DAVx5, etc.)
|
|
@app.route('/.well-known/caldav')
|
|
def wellknown_caldav():
|
|
return redirect('/dav/', code=301)
|
|
|
|
@app.route('/.well-known/carddav')
|
|
def wellknown_carddav():
|
|
return redirect('/dav/', code=301)
|
|
|
|
# iCal export (public, no auth)
|
|
@app.route('/ical/<token>')
|
|
def ical_export_route(token):
|
|
from app.api.calendar import ical_export as _ical_export
|
|
return _ical_export(token)
|
|
|
|
# Serve frontend SPA for all non-API routes (production/Docker)
|
|
if static_dir.exists():
|
|
@app.route('/')
|
|
def serve_index():
|
|
return send_from_directory(str(static_dir), 'index.html')
|
|
|
|
@app.errorhandler(404)
|
|
def serve_spa(e):
|
|
return send_from_directory(str(static_dir), 'index.html')
|
|
|
|
# Create tables
|
|
with app.app_context():
|
|
from app import models # noqa: F401
|
|
db.create_all()
|
|
|
|
# Enable WAL mode for SQLite
|
|
with db.engine.connect() as conn:
|
|
conn.execute(db.text('PRAGMA journal_mode=WAL'))
|
|
conn.commit()
|
|
|
|
# Start backup scheduler (only in main process, not reloader)
|
|
if not app.debug or os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
|
|
from app.services.backup_scheduler import start_backup_scheduler
|
|
start_backup_scheduler(app)
|
|
|
|
return app
|