#!/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 < Label $LABEL ProgramArguments $SYMLINK $PYTHON_SCRIPT StartInterval $INTERVAL RunAtLoad WorkingDirectory $SCRIPT_DIR StandardOutPath $LOG_OUT StandardErrorPath $LOG_ERR KeepAlive 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