This commit is contained in:
2026-03-18 16:35:15 +01:00
parent cb6536f656
commit f1711db72a
5 changed files with 89 additions and 18 deletions

View File

@@ -41,15 +41,15 @@ TZ=Europe/Rome
# { # {
# "id": "sync_identifier", # "id": "sync_identifier",
# "bucket": "bucket-name", # "bucket": "bucket-name",
# "path": "/mount/path/in/container", # "local_path": "/mount/path/in/container",
# "interval": 300 # "interval": 300
# } # }
# #
# Esempio con 2 sync: # Esempio con 2 sync:
# #
SYNC_CONFIGS='[ SYNC_CONFIGS='[
{"id": "sync1", "bucket": "bucket-a", "path": "/data/local", "interval": 300}, {"id": "sync1", "bucket": "bucket-a", "local_path": "/data/local1", "interval": 300},
{"id": "sync2", "bucket": "bucket-b", "path": "/data/local2", "interval": 600} {"id": "sync2", "bucket": "bucket-b", "local_path": "/data/local2", "interval": 600}
]' ]'
# ============================================================ # ============================================================
@@ -67,8 +67,12 @@ GOTIFY_PRIORITY=5
# multiple sync nel docker-compose.yml: # multiple sync nel docker-compose.yml:
# #
# volumes: # volumes:
# - ./data/sync1:/data/local # Primo sync # - ./data/sync1:/data/local1 # Primo sync
# - ./data/sync2:/data/local2 # Secondo sync # - ./data/sync2:/data/local2 # Secondo sync
# - sync-state:/data/state # Stato condiviso # - 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 LOCAL_SYNC_PATH=./data

View File

@@ -13,8 +13,8 @@
# #
# Formato .env per multiple sync: # Formato .env per multiple sync:
# SYNC_CONFIGS='[ # SYNC_CONFIGS='[
# {"id":"sync1", "bucket":"bucket-a", "path":"/data/sync1", "interval":300}, # {"id":"sync1", "bucket":"bucket-a", "local_path":"/data/local1", "interval":300},
# {"id":"sync2", "bucket":"bucket-b", "path":"/data/sync2", "interval":600} # {"id":"sync2", "bucket":"bucket-b", "local_path":"/data/local2", "interval":600}
# ]' # ]'
# ============================================================ # ============================================================
@@ -61,9 +61,12 @@ services:
- TZ=${TZ:-Europe/Rome} - TZ=${TZ:-Europe/Rome}
# Volumi: # 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) # - Volume per lo stato interno (persistente tra i restart)
volumes: volumes:
- ${LOCAL_SYNC_PATH_1:-./data/sync1}:/data/local1
- ${LOCAL_SYNC_PATH_2:-./data/sync2}:/data/local2
- ${LOCAL_SYNC_PATH:-./data}:/data/local - ${LOCAL_SYNC_PATH:-./data}:/data/local
- sync-state:/data/state - sync-state:/data/state

View File

@@ -46,7 +46,6 @@ else
import json import json
import os import os
import subprocess import subprocess
import signal
import time import time
sync_configs_json = os.environ.get('SYNC_CONFIGS', '[]') sync_configs_json = os.environ.get('SYNC_CONFIGS', '[]')
@@ -64,18 +63,33 @@ print(f"[ENTRYPOINT] Avvio {len(configs)} sync...")
processes = [] processes = []
for i, cfg in enumerate(configs): for i, cfg in enumerate(configs):
sync_id = cfg.get('id', f'sync{i}') sync_id = cfg.get('id', f'sync{i+1}')
bucket = cfg.get('bucket', 'default') 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}" 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 # Crea directory di stato
os.makedirs(state_dir, exist_ok=True) os.makedirs(state_dir, exist_ok=True)
os.makedirs(local_path, exist_ok=True)
# Lancia sync.sh in background con STATE_DIR specifico # Lancia sync.sh in background con STATE_DIR specifico
env = os.environ.copy() env = os.environ.copy()
env['STATE_DIR'] = state_dir 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( proc = subprocess.Popen(
['/usr/local/bin/sync.sh'], ['/usr/local/bin/sync.sh'],

View File

@@ -411,7 +411,8 @@ SYNC_BANDWIDTH="${SYNC_BANDWIDTH:-0}"
SYNC_LOG_LEVEL="${SYNC_LOG_LEVEL:-INFO}" SYNC_LOG_LEVEL="${SYNC_LOG_LEVEL:-INFO}"
SYNC_ON_START="${SYNC_ON_START:-true}" 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) --- # --- Helper cron (script Python per il parsing delle espressioni) ---
CRON_HELPER="/app/web/cron_helper.py" CRON_HELPER="/app/web/cron_helper.py"

View File

@@ -40,7 +40,17 @@ def load_sync_configs():
if sync_configs_json: if sync_configs_json:
try: try:
configs = json.loads(sync_configs_json) 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: except json.JSONDecodeError:
pass pass
@@ -49,7 +59,8 @@ def load_sync_configs():
"sync1": { "sync1": {
"id": "sync1", "id": "sync1",
"bucket": os.environ.get("S3_BUCKET", "default"), "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): def get_sync_state_dir(sync_id):
"""Ritorna la directory di stato per una specifica sync.""" """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 return STATE_DIR / sync_id
def get_sync_bucket(sync_id): def get_sync_bucket(sync_id):
@@ -67,6 +75,19 @@ def get_sync_bucket(sync_id):
cfg = SYNC_CONFIGS.get(sync_id) cfg = SYNC_CONFIGS.get(sync_id)
return cfg.get("bucket", sync_id) if cfg else 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à # Funzioni di utilità
# ============================================================ # ============================================================
@@ -123,6 +144,8 @@ def index():
"""Pagina principale della dashboard con tab untuk multi-sync.""" """Pagina principale della dashboard con tab untuk multi-sync."""
sync_interval = int(os.environ.get("SYNC_INTERVAL", 300)) sync_interval = int(os.environ.get("SYNC_INTERVAL", 300))
sync_schedule = os.environ.get("SYNC_SCHEDULE", "") sync_schedule = os.environ.get("SYNC_SCHEDULE", "")
default_sync = get_default_sync_id()
default_cfg = SYNC_CONFIGS.get(default_sync, {})
if sync_schedule: if sync_schedule:
from cron_helper import human_readable from cron_helper import human_readable
@@ -134,6 +157,8 @@ def index():
config = { config = {
"endpoint": os.environ.get("S3_ENDPOINT", "N/A"), "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_mode": os.environ.get("SYNC_MODE", "mirror"),
"sync_interval": sync_interval, "sync_interval": sync_interval,
"sync_schedule": sync_schedule, "sync_schedule": sync_schedule,
@@ -174,10 +199,16 @@ def api_status(sync_id):
# Aggiungi statistiche della cartella locale # Aggiungi statistiche della cartella locale
status["bucket"] = get_sync_bucket(sync_id) 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) 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>") @app.route("/api/changes/<sync_id>")
def api_changes(sync_id): def api_changes(sync_id):
"""API: restituisce le ultime modifiche ai file per una sync.""" """API: restituisce le ultime modifiche ai file per una sync."""
@@ -187,6 +218,12 @@ def api_changes(sync_id):
return jsonify(changes) 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>") @app.route("/api/history/<sync_id>")
def api_history(sync_id): def api_history(sync_id):
"""API: restituisce lo storico delle sincronizzazioni.""" """API: restituisce lo storico delle sincronizzazioni."""
@@ -196,6 +233,12 @@ def api_history(sync_id):
return jsonify(history) 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>") @app.route("/api/stream/<sync_id>")
def api_stream(sync_id): def api_stream(sync_id):
"""SSE: stream in tempo reale dei log della sincronizzazione.""" """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 # Avvio server
# ============================================================ # ============================================================