Initial commit: slskd auto-disconnect script
Cron script that disconnects slskd from the Soulseek network after a configurable period of inactivity to avoid sharing files 24/7. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
#!/bin/bash
|
||||
# slskd auto-disconnect
|
||||
# Disconnects from Soulseek after a period of inactivity to avoid
|
||||
# sharing files 24/7. Runs via cron every 5 minutes.
|
||||
#
|
||||
# Activity is detected by:
|
||||
# 1. Active downloads in progress via the API
|
||||
# 2. Recent log activity (searches, browsing, downloads) in docker logs
|
||||
#
|
||||
# Uploads do NOT reset the idle timer, but an in-progress upload will
|
||||
# delay disconnection until it finishes.
|
||||
#
|
||||
# Environment overrides:
|
||||
# SLSKD_IDLE_TIMEOUT - minutes before disconnect (default: 30)
|
||||
# SLSKD_GOTIFY_TOKEN - Gotify app token for notifications (optional)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Configuration ---
|
||||
SLSKD_URL="http://localhost:5030"
|
||||
SLSKD_USER="slskd"
|
||||
SLSKD_PASS="slskd"
|
||||
IDLE_TIMEOUT_MIN="${SLSKD_IDLE_TIMEOUT:-30}"
|
||||
LOG_CHECK_MIN=5
|
||||
CONTAINER_NAME="slskd"
|
||||
|
||||
SCRIPT_DIR="/opt/arr/slskd/scripts"
|
||||
STATE_FILE="$SCRIPT_DIR/.last_active"
|
||||
LOG_FILE="$SCRIPT_DIR/auto-disconnect.log"
|
||||
LOG_MAX_LINES=500
|
||||
|
||||
GOTIFY_URL="https://notify.thecozycat.net"
|
||||
GOTIFY_TOKEN="${SLSKD_GOTIFY_TOKEN:-}"
|
||||
|
||||
# --- Functions ---
|
||||
|
||||
log_msg() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"
|
||||
if [ -f "$LOG_FILE" ] && [ "$(wc -l < "$LOG_FILE")" -gt "$LOG_MAX_LINES" ]; then
|
||||
tail -n "$((LOG_MAX_LINES / 2))" "$LOG_FILE" > "$LOG_FILE.tmp"
|
||||
mv "$LOG_FILE.tmp" "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
get_token() {
|
||||
curl -sf --max-time 10 "$SLSKD_URL/api/v0/session" -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"$SLSKD_USER\",\"password\":\"$SLSKD_PASS\"}" \
|
||||
| jq -r '.token // empty'
|
||||
}
|
||||
|
||||
api_get() {
|
||||
curl -sf --max-time 10 "$SLSKD_URL$1" -H "Authorization: Bearer $TOKEN"
|
||||
}
|
||||
|
||||
is_connected() {
|
||||
local connected
|
||||
connected=$(api_get "/api/v0/server" | jq -r '.isConnected')
|
||||
[ "$connected" = "true" ]
|
||||
}
|
||||
|
||||
has_active_downloads() {
|
||||
local count
|
||||
count=$(api_get "/api/v0/transfers/downloads" \
|
||||
| jq '[.[].directories[]?.files[]? | select(.state | test("InProgress|Initializing|Queued, Locally"))] | length')
|
||||
[ "${count:-0}" -gt 0 ]
|
||||
}
|
||||
|
||||
has_active_uploads() {
|
||||
local count
|
||||
count=$(api_get "/api/v0/transfers/uploads" \
|
||||
| jq '[.[]?.directories[]?.files[]? | select(.state | test("InProgress|Initializing"))] | length')
|
||||
[ "${count:-0}" -gt 0 ]
|
||||
}
|
||||
|
||||
has_recent_log_activity() {
|
||||
local count
|
||||
count=$(docker logs "$CONTAINER_NAME" --since "${LOG_CHECK_MIN}m" 2>&1 \
|
||||
| grep -cE '\[DOWNLOAD\]|search response|Folder contents|Browse response|Connected to the Soulseek' || true)
|
||||
[ "${count:-0}" -gt 0 ]
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
curl -sf --max-time 10 -X DELETE "$SLSKD_URL/api/v0/server" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}' > /dev/null
|
||||
}
|
||||
|
||||
touch_active() {
|
||||
date +%s > "$STATE_FILE"
|
||||
}
|
||||
|
||||
seconds_since_active() {
|
||||
if [ ! -f "$STATE_FILE" ]; then
|
||||
touch_active
|
||||
echo 0
|
||||
return
|
||||
fi
|
||||
local last now
|
||||
last=$(cat "$STATE_FILE")
|
||||
now=$(date +%s)
|
||||
echo $((now - last))
|
||||
}
|
||||
|
||||
notify() {
|
||||
local msg="$1"
|
||||
if [ -n "$GOTIFY_TOKEN" ]; then
|
||||
curl -sf --max-time 10 "$GOTIFY_URL/message" -X POST \
|
||||
-H "X-Gotify-Key: $GOTIFY_TOKEN" \
|
||||
-F "title=slskd" \
|
||||
-F "message=$msg" \
|
||||
-F "priority=3" > /dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Main ---
|
||||
|
||||
TOKEN=$(get_token)
|
||||
if [ -z "$TOKEN" ]; then
|
||||
log_msg "ERROR: Failed to authenticate with slskd API"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! is_connected; then
|
||||
rm -f "$STATE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Downloads and user activity reset the idle timer
|
||||
if has_active_downloads || has_recent_log_activity; then
|
||||
touch_active
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Past idle threshold?
|
||||
idle_sec=$(seconds_since_active)
|
||||
threshold_sec=$((IDLE_TIMEOUT_MIN * 60))
|
||||
|
||||
if [ "$idle_sec" -ge "$threshold_sec" ]; then
|
||||
# Let any in-progress upload finish before pulling the plug
|
||||
if has_active_uploads; then
|
||||
log_msg "Idle threshold reached but upload in progress, waiting"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
disconnect
|
||||
idle_min=$((idle_sec / 60))
|
||||
log_msg "Disconnected after ${idle_min}m idle (threshold: ${IDLE_TIMEOUT_MIN}m)"
|
||||
notify "Disconnected from Soulseek after ${idle_min} minutes of inactivity"
|
||||
fi
|
||||
Reference in New Issue
Block a user