gotify cli for notifying me of nextcloud uploads cover image

gotify cli for notifying me of nextcloud uploads

The Ask

I was looking for a way to get notified from nextcloud when files were uploaded to a certain directory. This is because the upload is very spotty due to the physical environment of where the nextcloud client is, and once a file shows up I have a job to do. So I was hoping nextcloud had an easy way to say "if file shows up in X folder, notify me" but I can't find it...

Solution 2

Since I already use gotify and they provide a DOPE CLI we can write a little bash script to work at the filesystem level rather than the nextcloud level.

Permissions

We'll need to make sure the user running this script has all the right read permissions... In my exact case the file upload happens in a group folder, which is easily found on the filesystem in the Nextcloud volume at .../data/__groupfolders/{id}/{rest of path}. This was owned by www-data on the host, see add-yourself-to-www-data-to-view-your-nextcloud-data-on-the-filesystem if you want but a simple usermod -aG www-data <me> does the trick.

The Script

With a little AI magic to spruce up the messaging, the script basically looks like this:


#!/bin/bash

# Script to monitor sermon directory using gotify watch
# Used with a systemd service or standalone
# Can run in local or remote (SSH) mode

# Configuration

# Target directory to monitor
DIR_PATH="/path/to/watch"

# Mode: Set to "ssh" or "local"
# If ssh, will connect to SSH_HOST as SSH_USER to monitor the directory
# If local, will monitor the directory directly on this machine
MODE="ssh"

# Remote SSH settings (only used if MODE="ssh")
SSH_USER="youruser"
SSH_HOST="yourserver"

# Log settings
LOG_DIR="$HOME/.local/state/sermon-monitor"
LOG_FILE="$LOG_DIR/sermon-monitor.log"
MAX_LOG_SIZE_KB=1024 # 1MB max log size

# Create log directory if it doesn't exist
mkdir -p "$LOG_DIR" 2>/dev/null

# Create or truncate log file if it's too large
if [ -f "$LOG_FILE" ] && [ $(stat -c%s "$LOG_FILE" 2>/dev/null || echo 0) -gt $((MAX_LOG_SIZE_KB * 1024)) ]; then
  # Keep the last 20 lines when rotating
  tail -n 20 "$LOG_FILE" >"${LOG_FILE}.tmp" 2>/dev/null && mv "${LOG_FILE}.tmp" "$LOG_FILE" 2>/dev/null
  echo "$(date '+%Y-%m-%d %H:%M:%S') - Log file rotated due to size limit" >>"$LOG_FILE"
fi

# Ensure log file exists
touch "$LOG_FILE" 2>/dev/null

# Log function
log() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

# Create a temp file to store the previous state
TEMP_DIR="/tmp/gotify-watcher"
PREV_STATE_FILE="$TEMP_DIR/previous_state.txt"
mkdir -p "$TEMP_DIR" 2>/dev/null || true

# Function to get directory listing based on mode
get_directory_listing() {
  if [ "$MODE" = "ssh" ]; then
    # Remote mode - use SSH
    ssh $SSH_USER@$SSH_HOST "ls -la '$DIR_PATH' | grep -v '^\.\.$' | grep -v '^\.$'" ||
      echo "Error: Failed to connect to $SSH_HOST"
  else
    # Local mode - direct access
    if [ -d "$DIR_PATH" ]; then
      ls -la "$DIR_PATH" | grep -v '^\.\.$' | grep -v '^\.$'
    else
      echo "Error: Directory $DIR_PATH does not exist locally"
    fi
  fi
}

# Removed unused function - was leftover from previous version

# Display startup message based on mode
if [ "$MODE" = "ssh" ]; then
  log "Starting sermon file monitor for $SSH_USER@$SSH_HOST:$DIR_PATH (SSH mode)"
else
  log "Starting sermon file monitor for $DIR_PATH (local mode)"
fi

# Run initial check to establish baseline
CURRENT_LISTING=$(get_directory_listing)
echo "$CURRENT_LISTING" >"$PREV_STATE_FILE"
log "Initial state captured. Watching for changes."

# Monitor continuously
while true; do
  # Sleep for 30 seconds between checks
  sleep 30

  # Get current listing
  CURRENT_LISTING=$(get_directory_listing)

  # Find new files by comparing with previous state
  NEW_FILES=$(diff --new-line-format="%L" --old-line-format="" --unchanged-line-format="" \
    "$PREV_STATE_FILE" <(echo "$CURRENT_LISTING") | grep -v "^total" | grep -v "^drwx")

  # If new files found, notify
  if [ -n "$NEW_FILES" ]; then
    # Filter out directories and system entries, keep only regular files
    FILE_ENTRIES=$(echo "$NEW_FILES" | grep -v "^d" | grep -v "^total" | grep -v "^\.$" | grep -v "^\.\.")

    # Count how many actual files (non-empty lines)
    NEW_COUNT=$(echo "$FILE_ENTRIES" | grep -v "^$" | wc -l)

    # Only proceed if we have actual files
    if [ "$NEW_COUNT" -gt 0 ]; then
      # Create notification message
      if [ "$NEW_COUNT" -eq 1 ]; then
        TITLE="New File Added"
        # Extract filename from listing line
        FILENAME=$(echo "$FILE_ENTRIES" | awk '{print $NF}')
        MESSAGE="New file detected: $FILENAME"
      else
        TITLE="New Files Added"
        # Format filenames only for readability
        FILELIST=$(echo "$FILE_ENTRIES" | awk '{print $NF}' | sort)
        MESSAGE="$NEW_COUNT new files detected:\n$FILELIST"
      fi

      # Send notification
      gotify push --title "$TITLE" "$MESSAGE"
      log "Notification sent for new files: $NEW_COUNT"
    fi

    # Update previous state
    echo "$CURRENT_LISTING" >"$PREV_STATE_FILE"
  fi
done

# If we get here, gotify watch has exited
log "gotify watch exited unexpectedly"
exit 1

Credentials

As noted in the gotify repo - you can put the relevant credentials in a ./cli.json which is what I did since this is in a big repo of mine and that's where the script executes from


{
  "token": "yourToken",
  "url": "https://gotify.example.com",
  "defaultPriority": 6
}