First complete version
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
# NAS Connection Settings
|
||||||
|
NAS_USER="your_nas_user"
|
||||||
|
NAS_SSH_PORT=22
|
||||||
|
NAS_IP="192.168.1.100"
|
||||||
|
|
||||||
|
# NAS Paths
|
||||||
|
NAS_DB_DIR="/path/on/nas/darktable_db"
|
||||||
|
NAS_PHOTO_DIR="/path/on/nas/photo_library"
|
||||||
|
|
||||||
|
# Local Paths
|
||||||
|
LOCAL_PHOTO_DIR="$HOME/Pictures/raw"
|
||||||
|
LOCAL_DARKTABLE_DB_DIR="$HOME/.config/darktable"
|
||||||
|
BIN_DIR="$HOME/.local/bin"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
.env
|
||||||
|
.DS_Store
|
||||||
|
thumbs.db
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 MaTr74
|
Copyright (c) 2025 Martin Tröger (@MaTr74)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Darktable Sync 🔄
|
||||||
|
|
||||||
|
Auto-sync your Darktable database and photos between different computers with a server as intermediate. Keeps local and remote files in sync using rsync over SSH. The wrapper script synchronizes files before launching Darktable and after it closes. The scheduled background synchronization ensures continuous and reliable data exchange — even if the wrapper script is not used.
|
||||||
|
|
||||||
|
Since only file synchronization is performed, only one Darktable instance should run at a time.
|
||||||
|
|
||||||
|
## Features ✨
|
||||||
|
- 🔄 Bidirectional sync between local machines and a server
|
||||||
|
- ⏲️ Automatic sync every 5 minutes via systemd timer
|
||||||
|
- 🖱️ Desktop shortcuts for starting Darktable with sync and sync only
|
||||||
|
- 📊 Desktop notifications
|
||||||
|
|
||||||
|
## Requirements 📋
|
||||||
|
- Bash 4+
|
||||||
|
- rsync
|
||||||
|
- SSH key-based auth to NAS
|
||||||
|
- systemd (for automatic sync)
|
||||||
|
|
||||||
|
## Installation 💻
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/MaTr74/darktable-sync.git
|
||||||
|
cd darktable-sync
|
||||||
|
cp .env.example .env
|
||||||
|
nano .env # Edit with your values
|
||||||
|
chmod +x install.sh uninstall.sh
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage 🚀
|
||||||
|
- Start via desktop shortcut: "Darktable with Sync"
|
||||||
|
- Manual sync: Start via desktop shortcut: "Darktable Sync Only"
|
||||||
|
|
||||||
|
## Uninstall 🧹
|
||||||
|
```bash
|
||||||
|
./uninstall.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## License 📄
|
||||||
|
MIT License - see [LICENSE](LICENSE)
|
||||||
Executable
+265
@@ -0,0 +1,265 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
### 🔧 Default Configuration (can be overridden by .env file)
|
||||||
|
NAS_USER="${NAS_USER:-$USER}" # Default: current user
|
||||||
|
NAS_SSH_PORT="${NAS_SSH_PORT:-22}" # Default: standard SSH port
|
||||||
|
NAS_IP="${NAS_IP:-192.168.1.100}" # Default: common local network
|
||||||
|
NAS_DB_DIR="${NAS_DB_DIR:-/volume1/Darktable/darktable_db}"
|
||||||
|
NAS_PHOTO_DIR="${NAS_PHOTO_DIR:-/volume1/Darktable/photo_library}"
|
||||||
|
LOCAL_PHOTO_DIR="${PHOTO_DIR:-$HOME/Pictures/raw}"
|
||||||
|
LOCAL_DARKTABLE_DB_DIR="${DARKTABLE_DB_DIR:-$HOME/.config/darktable}"
|
||||||
|
BIN_DIR="${BIN_DIR:-$HOME/.local/bin}"
|
||||||
|
APPLICATIONS_DIR="$HOME/.local/share/applications"
|
||||||
|
SYNC_SCRIPT="$BIN_DIR/darktable_sync.sh"
|
||||||
|
WRAPPER_SCRIPT="$BIN_DIR/darktable_wrapper.sh"
|
||||||
|
DESKTOP_SHORTCUT="$APPLICATIONS_DIR/darktable-with-sync.desktop"
|
||||||
|
SYNC_ONLY_SHORTCUT="$APPLICATIONS_DIR/darktable-sync-only.desktop"
|
||||||
|
|
||||||
|
|
||||||
|
### 📁 Prepare folders
|
||||||
|
mkdir -p "$BIN_DIR"
|
||||||
|
mkdir -p "$HOME/.config/systemd/user"
|
||||||
|
|
||||||
|
### 🔄 Load .env if present (overrides defaults)
|
||||||
|
ENV_FILE=".env"
|
||||||
|
if [[ -f "$ENV_FILE" ]]; then
|
||||||
|
echo "📥 Loading configuration from .env file..."
|
||||||
|
set -a # automatically export all variables
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
### 📝 Show effective configuration
|
||||||
|
echo "Using configuration:"
|
||||||
|
echo "NAS_USER: $NAS_USER"
|
||||||
|
echo "NAS_IP: $NAS_IP"
|
||||||
|
echo "NAS_SSH_PORT: $NAS_SSH_PORT"
|
||||||
|
echo "NAS_DB_DIR: $NAS_DB_DIR"
|
||||||
|
echo "NAS_PHOTO_DIR: $NAS_PHOTO_DIR"
|
||||||
|
echo "PHOTO_DIR: $LOCAL_PHOTO_DIR"
|
||||||
|
echo "DARKTABLE_DB_DIR:$LOCAL_DARKTABLE_DB_DIR"
|
||||||
|
echo "BIN_DIR: $BIN_DIR"
|
||||||
|
|
||||||
|
### 🔍 Check dependencies
|
||||||
|
echo "🔍 Checking requirements..."
|
||||||
|
|
||||||
|
REQUIRED_CMDS=("rsync" "notify-send" "ping" "darktable" "systemctl" "xdg-user-dir")
|
||||||
|
for cmd in "${REQUIRED_CMDS[@]}"; do
|
||||||
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
echo "❌ Error: '$cmd' is not installed."
|
||||||
|
echo "👉 You can install it with: sudo apt install $cmd"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check folder presence
|
||||||
|
if [ ! -d "$LOCAL_PHOTO_DIR" ]; then
|
||||||
|
echo "❌ Local photo folder does not exist: $LOCAL_PHOTO_DIR"
|
||||||
|
echo "👉 Please create it using: mkdir -p \"$LOCAL_PHOTO_DIR\""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$LOCAL_DARKTABLE_DB_DIR" ]; then
|
||||||
|
echo "❌ Darktable database path does not exist: $LOCAL_DARKTABLE_DB_DIR"
|
||||||
|
echo "👉 Start Darktable once or create the directory manually."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if NAS is reachable
|
||||||
|
if ping -c 1 "$NAS_IP" &>/dev/null; then
|
||||||
|
echo "✅ NAS is reachable: $NAS_IP"
|
||||||
|
|
||||||
|
if ! ssh -p "$NAS_SSH_PORT" "$NAS_USER@$NAS_IP" "[ -d '$NAS_DB_DIR' ]"; then
|
||||||
|
echo "❌ Remote directory missing on NAS: $NAS_DB_DIR"
|
||||||
|
echo "👉 Please create it or adjust the path."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ssh -p "$NAS_SSH_PORT" "$NAS_USER@$NAS_IP" "[ -d '$NAS_PHOTO_DIR' ]"; then
|
||||||
|
echo "❌ Remote directory missing on NAS: $NAS_PHOTO_DIR"
|
||||||
|
echo "👉 Please create it or adjust the path."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ NAS not reachable: $NAS_IP"
|
||||||
|
echo "➡️ Sync will fail until NAS is online."
|
||||||
|
fi
|
||||||
|
|
||||||
|
### ✅ Create sync script
|
||||||
|
cat > "$SYNC_SCRIPT" <<EOF
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "\$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
count_synced_files() {
|
||||||
|
LOG="\$1"
|
||||||
|
DIRECTION="\$2"
|
||||||
|
|
||||||
|
case "\$DIRECTION" in
|
||||||
|
"up")
|
||||||
|
COUNT=\$(grep -E '^(<f|cd)' "\$LOG" | wc -l)
|
||||||
|
;;
|
||||||
|
"down")
|
||||||
|
COUNT=\$(grep -E '^(>f|cd)' "\$LOG" | wc -l)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
COUNT=0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "\$COUNT"
|
||||||
|
}
|
||||||
|
|
||||||
|
SHOW_NOTIFY_START_STOP=false
|
||||||
|
if [[ "\$1" == "--with-notify-start-stop" ]]; then
|
||||||
|
SHOW_NOTIFY_START_STOP=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ping -c 1 $NAS_IP &>/dev/null; then
|
||||||
|
export DISPLAY=:0
|
||||||
|
SYNC_LOG=\$(mktemp)
|
||||||
|
log "🔃 NAS is reachable – starting sync..."
|
||||||
|
log "Log file: \$SYNC_LOG"
|
||||||
|
|
||||||
|
if [ "\$SHOW_NOTIFY_START_STOP" = true ]; then
|
||||||
|
notify-send "Darktable Sync" "🔄 Sync started..." -t 3000
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "⬆️ Uploading Darktable DB to NAS..."
|
||||||
|
UPLOAD_LOG1=\$(mktemp)
|
||||||
|
rsync -avh --itemize-changes -e "ssh -p $NAS_SSH_PORT" "$LOCAL_DARKTABLE_DB_DIR/" "$NAS_USER@$NAS_IP:$NAS_DB_DIR/" 2>&1 | tee -a "\$SYNC_LOG" "\$UPLOAD_LOG1"
|
||||||
|
SENT1=\$(count_synced_files "\$UPLOAD_LOG1" "up")
|
||||||
|
rm "\$UPLOAD_LOG1"
|
||||||
|
|
||||||
|
log "⬆️ Uploading photos to NAS..."
|
||||||
|
UPLOAD_LOG2=\$(mktemp)
|
||||||
|
rsync -avh --itemize-changes -e "ssh -p $NAS_SSH_PORT" "$LOCAL_PHOTO_DIR/" "$NAS_USER@$NAS_IP:$NAS_PHOTO_DIR/" 2>&1 | tee -a "\$SYNC_LOG" "\$UPLOAD_LOG2"
|
||||||
|
SENT2=\$(count_synced_files "\$UPLOAD_LOG2" "up")
|
||||||
|
rm "\$UPLOAD_LOG2"
|
||||||
|
|
||||||
|
log "⬇️ Downloading DB back from NAS..."
|
||||||
|
DOWNLOAD_LOG1=\$(mktemp)
|
||||||
|
rsync -avh --itemize-changes -e "ssh -p $NAS_SSH_PORT" "$NAS_USER@$NAS_IP:$NAS_DB_DIR/" "$LOCAL_DARKTABLE_DB_DIR/" 2>&1 | tee -a "\$SYNC_LOG" "\$DOWNLOAD_LOG1"
|
||||||
|
RECEIVED1=\$(count_synced_files "\$DOWNLOAD_LOG1" "down")
|
||||||
|
rm "\$DOWNLOAD_LOG1"
|
||||||
|
|
||||||
|
log "⬇️ Downloading photos from NAS..."
|
||||||
|
DOWNLOAD_LOG2=\$(mktemp)
|
||||||
|
rsync -avh --itemize-changes -e "ssh -p $NAS_SSH_PORT" "$NAS_USER@$NAS_IP:$NAS_PHOTO_DIR/" "$LOCAL_PHOTO_DIR/" 2>&1 | tee -a "\$SYNC_LOG" "\$DOWNLOAD_LOG2"
|
||||||
|
RECEIVED2=\$(count_synced_files "\$DOWNLOAD_LOG2" "down")
|
||||||
|
rm "\$DOWNLOAD_LOG2"
|
||||||
|
|
||||||
|
if [ "\$SHOW_NOTIFY_START_STOP" = true ]; then
|
||||||
|
notify-send "Darktable Sync" "✅ Sync finished." -t 3000
|
||||||
|
fi
|
||||||
|
|
||||||
|
TOTAL_SENT=\$((SENT1 + SENT2))
|
||||||
|
TOTAL_RECEIVED=\$((RECEIVED1 + RECEIVED2))
|
||||||
|
|
||||||
|
if [ "\$TOTAL_SENT" -gt 0 ] || [ "\$TOTAL_RECEIVED" -gt 0 ]; then
|
||||||
|
log "✅ Uploaded: \$TOTAL_SENT files"
|
||||||
|
log "✅ Downloaded: \$TOTAL_RECEIVED files"
|
||||||
|
notify-send "Darktable Sync" "⬆️ \$TOTAL_SENT uploaded | ⬇️ \$TOTAL_RECEIVED downloaded" -t 10000
|
||||||
|
else
|
||||||
|
log "ℹ️ No changes detected."
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "\$SYNC_LOG"
|
||||||
|
else
|
||||||
|
log "❌ NAS not reachable – skipping sync."
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x "$SYNC_SCRIPT"
|
||||||
|
|
||||||
|
### ✅ Create wrapper script with sync + notify
|
||||||
|
cat > "$WRAPPER_SCRIPT" <<EOF
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "\$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log "🔄 Sync starting before Darktable..."
|
||||||
|
"$SYNC_SCRIPT" --with-notify-start-stop
|
||||||
|
|
||||||
|
log "🚀 Launching Darktable..."
|
||||||
|
darktable
|
||||||
|
log "🛑 Darktable closed."
|
||||||
|
|
||||||
|
log "🔄 Sync starting after Darktable..."
|
||||||
|
"$SYNC_SCRIPT" --with-notify-start-stop
|
||||||
|
log "Darktable Sync" "✅ Final sync completed." -t 3000
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x "$WRAPPER_SCRIPT"
|
||||||
|
|
||||||
|
|
||||||
|
### ✅ Create systemd service and timer
|
||||||
|
cat > "$HOME/.config/systemd/user/darktable-sync.service" <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Darktable Sync
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=$SYNC_SCRIPT
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$HOME/.config/systemd/user/darktable-sync.timer" <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Run Darktable sync every 5 minutes
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=2min
|
||||||
|
OnUnitActiveSec=5min
|
||||||
|
Unit=darktable-sync.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
### ✅ Create desktop shortcut
|
||||||
|
cat > "$DESKTOP_SHORTCUT" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Darktable with Sync
|
||||||
|
Exec=$WRAPPER_SCRIPT
|
||||||
|
Icon=darktable
|
||||||
|
Type=Application
|
||||||
|
Terminal=false
|
||||||
|
Categories=Graphics;
|
||||||
|
StartupNotify=true
|
||||||
|
Keywords=Darktable;Photo;Sync;NAS;Rsync;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x "$DESKTOP_SHORTCUT"
|
||||||
|
|
||||||
|
### ✅ Create additional desktop shortcut (sync only)
|
||||||
|
cat > "$SYNC_ONLY_SHORTCUT" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Darktable Sync Only
|
||||||
|
Exec=$SYNC_SCRIPT --with-notify-start-stop
|
||||||
|
Icon=darktable
|
||||||
|
Type=Application
|
||||||
|
Terminal=false
|
||||||
|
Categories=Utility;
|
||||||
|
StartupNotify=true
|
||||||
|
Keywords=Darktable;Photo;Sync;NAS;Schedule;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x "$SYNC_ONLY_SHORTCUT"
|
||||||
|
|
||||||
|
|
||||||
|
### ▶️ Enable systemd timer
|
||||||
|
systemctl --user daemon-reexec
|
||||||
|
systemctl --user enable --now darktable-sync.timer
|
||||||
|
|
||||||
|
### ✅ Summary
|
||||||
|
echo ""
|
||||||
|
echo "✅ Setup complete."
|
||||||
|
echo ""
|
||||||
|
echo "🖱 You can start Darktable via 'Darktable with Sync' in your application menu."
|
||||||
|
echo "🕒 Sync will run every 5 minutes automatically when the NAS is online."
|
||||||
Executable
+27
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Load possible custom paths from install-time .env if exists
|
||||||
|
if [[ -f ".env" ]]; then
|
||||||
|
export $(grep -v '^#' .env | xargs)
|
||||||
|
fi
|
||||||
|
|
||||||
|
BIN_DIR="${BIN_DIR:-$HOME/.local/bin}"
|
||||||
|
APPLICATIONS_DIR="${APPLICATIONS_DIR:-$HOME/.local/share/applications}"
|
||||||
|
SYSTEMD_USER_DIR="${SYSTEMD_USER_DIR:-$HOME/.config/systemd/user}"
|
||||||
|
|
||||||
|
# Stop and disable systemd service
|
||||||
|
echo "🛑 Removing systemd services..."
|
||||||
|
systemctl --user disable --now darktable-sync.timer >/dev/null 2>&1 || true
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
|
||||||
|
# Remove files
|
||||||
|
echo "🧹 Cleaning up installed files..."
|
||||||
|
rm -fv \
|
||||||
|
"$BIN_DIR/darktable_sync.sh" \
|
||||||
|
"$BIN_DIR/darktable_wrapper.sh" \
|
||||||
|
"$APPLICATIONS_DIR/darktable-with-sync.desktop" \
|
||||||
|
"$APPLICATIONS_DIR/darktable-sync-only.desktop" \
|
||||||
|
"$SYSTEMD_USER_DIR/darktable-sync.service" \
|
||||||
|
"$SYSTEMD_USER_DIR/darktable-sync.timer"
|
||||||
|
|
||||||
|
echo "✅ Uninstall complete. Config files in ~/.config/darktable remain untouched."
|
||||||
Reference in New Issue
Block a user