++ fix
This commit is contained in:
12
.env.example
12
.env.example
@@ -41,15 +41,15 @@ TZ=Europe/Rome
|
||||
# {
|
||||
# "id": "sync_identifier",
|
||||
# "bucket": "bucket-name",
|
||||
# "path": "/mount/path/in/container",
|
||||
# "local_path": "/mount/path/in/container",
|
||||
# "interval": 300
|
||||
# }
|
||||
#
|
||||
# Esempio con 2 sync:
|
||||
#
|
||||
SYNC_CONFIGS='[
|
||||
{"id": "sync1", "bucket": "bucket-a", "path": "/data/local", "interval": 300},
|
||||
{"id": "sync2", "bucket": "bucket-b", "path": "/data/local2", "interval": 600}
|
||||
{"id": "sync1", "bucket": "bucket-a", "local_path": "/data/local1", "interval": 300},
|
||||
{"id": "sync2", "bucket": "bucket-b", "local_path": "/data/local2", "interval": 600}
|
||||
]'
|
||||
|
||||
# ============================================================
|
||||
@@ -67,8 +67,12 @@ GOTIFY_PRIORITY=5
|
||||
# multiple sync nel docker-compose.yml:
|
||||
#
|
||||
# volumes:
|
||||
# - ./data/sync1:/data/local # Primo sync
|
||||
# - ./data/sync1:/data/local1 # Primo sync
|
||||
# - ./data/sync2:/data/local2 # Secondo sync
|
||||
# - sync-state:/data/state # Stato condiviso
|
||||
#
|
||||
LOCAL_SYNC_PATH_1=./data/sync1
|
||||
LOCAL_SYNC_PATH_2=./data/sync2
|
||||
|
||||
# Fallback legacy (sync singola)
|
||||
LOCAL_SYNC_PATH=./data
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
#
|
||||
# Formato .env per multiple sync:
|
||||
# SYNC_CONFIGS='[
|
||||
# {"id":"sync1", "bucket":"bucket-a", "path":"/data/sync1", "interval":300},
|
||||
# {"id":"sync2", "bucket":"bucket-b", "path":"/data/sync2", "interval":600}
|
||||
# {"id":"sync1", "bucket":"bucket-a", "local_path":"/data/local1", "interval":300},
|
||||
# {"id":"sync2", "bucket":"bucket-b", "local_path":"/data/local2", "interval":600}
|
||||
# ]'
|
||||
# ============================================================
|
||||
|
||||
@@ -61,9 +61,12 @@ services:
|
||||
- TZ=${TZ:-Europe/Rome}
|
||||
|
||||
# Volumi:
|
||||
# - Cartella locale per i file sincronizzati
|
||||
# - Cartelle locali per sync multiple (default)
|
||||
# - Cartella locale legacy per sync singola
|
||||
# - Volume per lo stato interno (persistente tra i restart)
|
||||
volumes:
|
||||
- ${LOCAL_SYNC_PATH_1:-./data/sync1}:/data/local1
|
||||
- ${LOCAL_SYNC_PATH_2:-./data/sync2}:/data/local2
|
||||
- ${LOCAL_SYNC_PATH:-./data}:/data/local
|
||||
- sync-state:/data/state
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ else
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import signal
|
||||
import time
|
||||
|
||||
sync_configs_json = os.environ.get('SYNC_CONFIGS', '[]')
|
||||
@@ -64,18 +63,33 @@ print(f"[ENTRYPOINT] Avvio {len(configs)} sync...")
|
||||
|
||||
processes = []
|
||||
for i, cfg in enumerate(configs):
|
||||
sync_id = cfg.get('id', f'sync{i}')
|
||||
bucket = cfg.get('bucket', 'default')
|
||||
sync_id = cfg.get('id', f'sync{i+1}')
|
||||
bucket = cfg.get('bucket')
|
||||
local_path = cfg.get('local_path') or cfg.get('path') or f'/data/local{i+1}'
|
||||
interval = str(cfg.get('interval', os.environ.get('SYNC_INTERVAL', '300')))
|
||||
prefix = cfg.get('prefix', os.environ.get('S3_PATH_PREFIX', ''))
|
||||
schedule = cfg.get('schedule', os.environ.get('SYNC_SCHEDULE', ''))
|
||||
|
||||
if not bucket:
|
||||
print(f"[ERROR] Config sync '{sync_id}' senza campo 'bucket'")
|
||||
exit(1)
|
||||
|
||||
state_dir = f"/data/state/{sync_id}"
|
||||
|
||||
print(f"[ENTRYPOINT] Avvio sync {i+1}/{len(configs)}: {sync_id} (bucket: {bucket})")
|
||||
print(f"[ENTRYPOINT] Avvio sync {i+1}/{len(configs)}: {sync_id} (bucket: {bucket}, local: {local_path})")
|
||||
|
||||
# Crea directory di stato
|
||||
os.makedirs(state_dir, exist_ok=True)
|
||||
os.makedirs(local_path, exist_ok=True)
|
||||
|
||||
# Lancia sync.sh in background con STATE_DIR specifico
|
||||
env = os.environ.copy()
|
||||
env['STATE_DIR'] = state_dir
|
||||
env['S3_BUCKET'] = bucket
|
||||
env['LOCAL_PATH'] = local_path
|
||||
env['SYNC_INTERVAL'] = interval
|
||||
env['S3_PATH_PREFIX'] = prefix
|
||||
env['SYNC_SCHEDULE'] = schedule
|
||||
|
||||
proc = subprocess.Popen(
|
||||
['/usr/local/bin/sync.sh'],
|
||||
|
||||
3
sync.sh
3
sync.sh
@@ -411,7 +411,8 @@ SYNC_BANDWIDTH="${SYNC_BANDWIDTH:-0}"
|
||||
SYNC_LOG_LEVEL="${SYNC_LOG_LEVEL:-INFO}"
|
||||
SYNC_ON_START="${SYNC_ON_START:-true}"
|
||||
|
||||
LOCAL_PATH="/data/local"
|
||||
LOCAL_PATH="${LOCAL_PATH:-/data/local}"
|
||||
mkdir -p "${LOCAL_PATH}"
|
||||
|
||||
# --- Helper cron (script Python per il parsing delle espressioni) ---
|
||||
CRON_HELPER="/app/web/cron_helper.py"
|
||||
|
||||
61
web/app.py
61
web/app.py
@@ -40,7 +40,17 @@ def load_sync_configs():
|
||||
if sync_configs_json:
|
||||
try:
|
||||
configs = json.loads(sync_configs_json)
|
||||
return {cfg.get("id", f"sync{i}"): cfg for i, cfg in enumerate(configs)}
|
||||
parsed = {}
|
||||
for i, cfg in enumerate(configs):
|
||||
sync_id = cfg.get("id", f"sync{i+1}")
|
||||
parsed[sync_id] = {
|
||||
"id": sync_id,
|
||||
"bucket": cfg.get("bucket", "default"),
|
||||
"prefix": cfg.get("prefix", ""),
|
||||
"local_path": cfg.get("local_path") or cfg.get("path") or f"/data/local{i+1}",
|
||||
}
|
||||
if parsed:
|
||||
return parsed
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
@@ -49,7 +59,8 @@ def load_sync_configs():
|
||||
"sync1": {
|
||||
"id": "sync1",
|
||||
"bucket": os.environ.get("S3_BUCKET", "default"),
|
||||
"state_dir": STATE_DIR / "sync1"
|
||||
"prefix": os.environ.get("S3_PATH_PREFIX", ""),
|
||||
"local_path": os.environ.get("LOCAL_PATH", "/data/local"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +68,6 @@ SYNC_CONFIGS = load_sync_configs()
|
||||
|
||||
def get_sync_state_dir(sync_id):
|
||||
"""Ritorna la directory di stato per una specifica sync."""
|
||||
cfg = SYNC_CONFIGS.get(sync_id)
|
||||
if cfg and "state_dir" in cfg:
|
||||
return Path(cfg["state_dir"])
|
||||
return STATE_DIR / sync_id
|
||||
|
||||
def get_sync_bucket(sync_id):
|
||||
@@ -67,6 +75,19 @@ def get_sync_bucket(sync_id):
|
||||
cfg = SYNC_CONFIGS.get(sync_id)
|
||||
return cfg.get("bucket", sync_id) if cfg else sync_id
|
||||
|
||||
def get_sync_local_path(sync_id):
|
||||
"""Ritorna la cartella locale della sync specifica."""
|
||||
cfg = SYNC_CONFIGS.get(sync_id)
|
||||
if cfg:
|
||||
return cfg.get("local_path", "/data/local")
|
||||
return "/data/local"
|
||||
|
||||
def get_default_sync_id():
|
||||
"""Ritorna il primo sync id disponibile."""
|
||||
if SYNC_CONFIGS:
|
||||
return next(iter(SYNC_CONFIGS.keys()))
|
||||
return "sync1"
|
||||
|
||||
# ============================================================
|
||||
# Funzioni di utilità
|
||||
# ============================================================
|
||||
@@ -123,6 +144,8 @@ def index():
|
||||
"""Pagina principale della dashboard con tab untuk multi-sync."""
|
||||
sync_interval = int(os.environ.get("SYNC_INTERVAL", 300))
|
||||
sync_schedule = os.environ.get("SYNC_SCHEDULE", "")
|
||||
default_sync = get_default_sync_id()
|
||||
default_cfg = SYNC_CONFIGS.get(default_sync, {})
|
||||
|
||||
if sync_schedule:
|
||||
from cron_helper import human_readable
|
||||
@@ -134,6 +157,8 @@ def index():
|
||||
|
||||
config = {
|
||||
"endpoint": os.environ.get("S3_ENDPOINT", "N/A"),
|
||||
"bucket": default_cfg.get("bucket", os.environ.get("S3_BUCKET", "N/A")),
|
||||
"prefix": default_cfg.get("prefix", os.environ.get("S3_PATH_PREFIX", "")) or "(tutto il bucket)",
|
||||
"sync_mode": os.environ.get("SYNC_MODE", "mirror"),
|
||||
"sync_interval": sync_interval,
|
||||
"sync_schedule": sync_schedule,
|
||||
@@ -174,10 +199,16 @@ def api_status(sync_id):
|
||||
|
||||
# Aggiungi statistiche della cartella locale
|
||||
status["bucket"] = get_sync_bucket(sync_id)
|
||||
status["folder_stats"] = get_folder_stats()
|
||||
status["folder_stats"] = get_folder_stats(get_sync_local_path(sync_id))
|
||||
return jsonify(status)
|
||||
|
||||
|
||||
@app.route("/api/status")
|
||||
def api_status_default():
|
||||
"""Compat: stato della sync di default."""
|
||||
return api_status(get_default_sync_id())
|
||||
|
||||
|
||||
@app.route("/api/changes/<sync_id>")
|
||||
def api_changes(sync_id):
|
||||
"""API: restituisce le ultime modifiche ai file per una sync."""
|
||||
@@ -187,6 +218,12 @@ def api_changes(sync_id):
|
||||
return jsonify(changes)
|
||||
|
||||
|
||||
@app.route("/api/changes")
|
||||
def api_changes_default():
|
||||
"""Compat: modifiche della sync di default."""
|
||||
return api_changes(get_default_sync_id())
|
||||
|
||||
|
||||
@app.route("/api/history/<sync_id>")
|
||||
def api_history(sync_id):
|
||||
"""API: restituisce lo storico delle sincronizzazioni."""
|
||||
@@ -196,6 +233,12 @@ def api_history(sync_id):
|
||||
return jsonify(history)
|
||||
|
||||
|
||||
@app.route("/api/history")
|
||||
def api_history_default():
|
||||
"""Compat: storico della sync di default."""
|
||||
return api_history(get_default_sync_id())
|
||||
|
||||
|
||||
@app.route("/api/stream/<sync_id>")
|
||||
def api_stream(sync_id):
|
||||
"""SSE: stream in tempo reale dei log della sincronizzazione."""
|
||||
@@ -240,6 +283,12 @@ def api_stream(sync_id):
|
||||
)
|
||||
|
||||
|
||||
@app.route("/api/stream")
|
||||
def api_stream_default():
|
||||
"""Compat: stream log della sync di default."""
|
||||
return api_stream(get_default_sync_id())
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Avvio server
|
||||
# ============================================================
|
||||
|
||||
Reference in New Issue
Block a user