Add uploader scripts and docs

This commit is contained in:
Maxx Cherevko
2026-04-30 09:02:36 +02:00
parent bcf4353722
commit f85a1eac62
9 changed files with 23849 additions and 0 deletions
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════
# Downloads Auto-Uploader — macOS launchd installer
# Runs downloads_uploader.py every 5 minutes in the background.
#
# Usage:
# ./downloads_uploader_install.sh install # register & start
# ./downloads_uploader_install.sh uninstall # stop & remove
# ./downloads_uploader_install.sh status # show current state
# ./downloads_uploader_install.sh run # manual one-shot run
# ═══════════════════════════════════════════════════════════════════
set -euo pipefail
LABEL="com.user.downloads-uploader"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCAN_DIR="$(dirname "$SCRIPT_DIR")" # parent of uploader/ = Downloads/
PYTHON_SCRIPT="$SCRIPT_DIR/downloads_uploader.py"
PLIST_DIR="$HOME/Library/LaunchAgents"
PLIST_FILE="$PLIST_DIR/$LABEL.plist"
LOG_OUT="$SCRIPT_DIR/uploader_stdout.log"
LOG_ERR="$SCRIPT_DIR/uploader_stderr.log"
INTERVAL=300 # seconds (5 minutes)
# macOS shows the executable name in permission dialogs, not the script name.
# To display "downloads-uploader" instead of "python3.13", we create a symlink
# named "downloads-uploader" pointing to the real python3 binary, and use that
# symlink in the launchd plist.
SYMLINK_DIR="$HOME/.local/bin"
SYMLINK="$SYMLINK_DIR/downloads-uploader"
# Detect python3
PYTHON=$(command -v python3 || command -v python || echo "")
if [[ -z "$PYTHON" ]]; then
echo "❌ python3 not found. Install it: brew install python"
exit 1
fi
# ───────────────────────────────────────────────
generate_plist() {
cat <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>$LABEL</string>
<key>ProgramArguments</key>
<array>
<string>$SYMLINK</string>
<string>$PYTHON_SCRIPT</string>
</array>
<key>StartInterval</key>
<integer>$INTERVAL</integer>
<key>RunAtLoad</key>
<true/>
<!-- Pin the working directory so __file__ resolves correctly -->
<key>WorkingDirectory</key>
<string>$SCRIPT_DIR</string>
<key>StandardOutPath</key>
<string>$LOG_OUT</string>
<key>StandardErrorPath</key>
<string>$LOG_ERR</string>
<key>KeepAlive</key>
<false/>
</dict>
</plist>
PLIST
}
# ───────────────────────────────────────────────
cmd_install() {
echo "📦 Installing Downloads Auto-Uploader..."
if [[ ! -f "$PYTHON_SCRIPT" ]]; then
echo "❌ Script not found: $PYTHON_SCRIPT"
exit 1
fi
# Create named symlink so macOS shows "downloads-uploader" in permission dialogs
mkdir -p "$SYMLINK_DIR"
if [[ -L "$SYMLINK" || -e "$SYMLINK" ]]; then
rm -f "$SYMLINK"
fi
ln -s "$PYTHON" "$SYMLINK"
echo "✅ Symlink created: $SYMLINK$PYTHON"
echo " (macOS permission dialogs will now show 'downloads-uploader')"
# Write the absolute scan_directory into the config so launchd's cwd=/
# never affects path resolution inside the Python script.
"$PYTHON" - "$SCRIPT_DIR" "$SCAN_DIR" <<'PYEOF'
import json, sys
cfg_path = sys.argv[1] + "/downloads_uploader_config.json"
scan_dir = sys.argv[2]
with open(cfg_path) as f:
cfg = json.load(f)
cfg["scan_directory"] = scan_dir
with open(cfg_path, "w") as f:
json.dump(cfg, f, indent=2, ensure_ascii=False)
print(f" scan_directory set to: {scan_dir}")
PYEOF
echo "✅ Config updated with absolute scan_directory"
mkdir -p "$PLIST_DIR"
generate_plist > "$PLIST_FILE"
echo "✅ Plist written: $PLIST_FILE"
# Unload first if already loaded
launchctl unload "$PLIST_FILE" 2>/dev/null || true
launchctl load -w "$PLIST_FILE"
echo "✅ Agent loaded. Script will run every $((INTERVAL / 60)) minutes."
echo " Logs → $LOG_OUT"
}
cmd_uninstall() {
echo "🗑 Uninstalling Downloads Auto-Uploader..."
if [[ -f "$PLIST_FILE" ]]; then
launchctl unload -w "$PLIST_FILE" 2>/dev/null || true
rm -f "$PLIST_FILE"
echo "✅ Removed $PLIST_FILE"
else
echo "️ Plist not found — already uninstalled."
fi
if [[ -L "$SYMLINK" ]]; then
rm -f "$SYMLINK"
echo "✅ Removed symlink $SYMLINK"
fi
}
cmd_status() {
echo "── Status ────────────────────────────────"
if launchctl list | grep -q "$LABEL"; then
echo "✅ Agent is RUNNING (label: $LABEL)"
launchctl list "$LABEL" 2>/dev/null || true
else
echo "⏹ Agent is NOT loaded."
fi
echo ""
echo "── Files ─────────────────────────────────"
echo " Script dir : $SCRIPT_DIR"
echo " Scan dir : $(dirname "$SCRIPT_DIR")"
echo " Config : $SCRIPT_DIR/downloads_uploader_config.json"
echo " State : $SCRIPT_DIR/.uploader_state.json"
echo " App log : $SCRIPT_DIR/uploader_log.txt"
echo " Stdout log : $LOG_OUT"
echo " Stderr log : $LOG_ERR"
echo ""
echo "── Recent activity (uploader_log.txt) ────"
LOG_MAIN="$SCRIPT_DIR/uploader_log.txt"
[[ -f "$LOG_MAIN" ]] && tail -25 "$LOG_MAIN" || echo "(no log yet)"
}
cmd_run() {
echo "▶ Running uploader now (one-shot)..."
"$PYTHON" "$PYTHON_SCRIPT"
}
# ───────────────────────────────────────────────
case "${1:-help}" in
install) cmd_install ;;
uninstall) cmd_uninstall ;;
status) cmd_status ;;
run) cmd_run ;;
*)
echo "Usage: $0 {install|uninstall|status|run}"
echo ""
echo " install Register the launchd agent (runs every 5 min)"
echo " uninstall Remove the launchd agent"
echo " status Show if the agent is running + recent log"
echo " run Run the uploader once right now"
;;
esac