gotify cli for notifying me of nextcloud uploads

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...

Copy this post

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
}

Connections

Related tags and posts connected to this entry.