Files
slskd-auto-disconnect/auto-disconnect.sh
2026-02-14 22:39:15 -05:00

160 lines
4.2 KiB
Bash

#!/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
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Load .env if present
if [ -f "$SCRIPT_DIR/.env" ]; then
set -a
source "$SCRIPT_DIR/.env"
set +a
fi
# --- Configuration ---
SLSKD_URL="${SLSKD_URL:-http://localhost:5030}"
SLSKD_USER="${SLSKD_USER:-slskd}"
SLSKD_PASS="${SLSKD_PASS:-slskd}"
IDLE_TIMEOUT_MIN="${SLSKD_IDLE_TIMEOUT:-30}"
LOG_CHECK_MIN=5
CONTAINER_NAME="slskd"
STATE_FILE="$SCRIPT_DIR/.last_active"
LOG_FILE="$SCRIPT_DIR/auto-disconnect.log"
LOG_MAX_LINES=500
GOTIFY_URL="${GOTIFY_URL:-}"
GOTIFY_TOKEN="${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" ] && [ -n "$GOTIFY_URL" ]; 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