++ fix
This commit is contained in:
12
.env.example
12
.env.example
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
3
sync.sh
3
sync.sh
@@ -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"
|
||||||
|
|||||||
61
web/app.py
61
web/app.py
@@ -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
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user