Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

Plex Activity Logging and Alerting Project

Overview

Plex media server has the ability to send webhooks when used with a Plex Pass (premium) account. This project documents how to set up a webhook to log and alert on media activity. The design goals include logging all media playing activity (play, stop, pause, resume) on my Plex server, and sending email notifications. Technologies used: apache, shell, selinux, jq, mailx

Architecture

Plex can be configured to point to a webhook, e.g., http://plex.example.com/cgi-bin/plex-log.cgi. The webhook file is documented below. The webhook is a script that acts upon the data passed from Plex. The actions include copying important values to different log files, and invoking a separate script that sends an email to the admin.

Involved files

Multiple files are a part of this project.

  • /var/server1/shares/public/www/cgi-bin/plex-log.cgi
  • /var/server1/shares/public/Support/Systems/server1/var/log/plex-webhook/activity.log
  • /var/server1/shares/public/Support/Systems/server1/var/log/plex-webhook/short.log
  • /usr/local/bin/send-plex-alert.sh
  • /usr/src/selinux/plexlog.te

Webhook script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!/bin/sh
# File: server1:/var/server1/shares/public/www/cgi-bin/plex-log.cgi
# License: CC-BY-SA 4.0
# Author: bgstack15
# Startdate: 2020-08-06 07:55
# Title: Logging Webhook for Plex
# Project: Plex Activity Logging and Alerting Project
# History:
# References:
#    send from bgscripts
#    /posts/2017/08/05/send-authenticated-gmail-from-cli-with-mailx/
#    Not sure which mail I have https://unix.stackexchange.com/questions/15405/how-do-i-send-html-email-using-linux-mail-command/15463#15463
# Related files:
#    /usr/local/bin/send-plex-alert.sh
# Improve:
# Dependencies:
#    /usr/local/bin/send-plex-alert.sh
#    Webhook functionality of Plex with a Plex Pass account
#    apache cgi capability
# Documentation:
#    /mnt/public/Support/Programs/Plex/webhooks/palap-readme.md
LOGFILE=/var/server1/shares/public/Support/Systems/server1/var/log/plex-webhook/activity.log
LOGFILESHORT=/var/server1/shares/public/Support/Systems/server1/var/log/plex-webhook/short.log
VERBOSE=0 # set to anything to display extra things
ALERT_ON_EVENTS="play:stop:pause:resume" # options include play stop resume pause, colon separated
NOW="$( date -u "+%FT%TZ" )"
TMPFILE1="$( mktemp )"
TMPFILE2="$( mktemp )"
TMPFILE3="$( mktemp )"
printf "%s\n\n" "Content-type: text/html"
echo "<html><head><title>Plex webhook</title>"
echo "</head><body>"
echo "<pre>"
cat > "${TMPFILE1}"
boundary="$( echo "${CONTENT_TYPE}" | awk '{print $2}' | awk -F'=' '{print $2}' )"
{
    test "${VERBOSE}" -gt 0 && {
       printf "%s\n" "" >> "${LOGFILE}"
      echo "REQUEST-START: ${NOW}"
    env
    set
    echo "BEGIN RAW"
   }
    sed -r -e "s/-*${boundary}-*/\n/g;" "${TMPFILE1}" | tee "${TMPFILE2}" | \
      {
         test "${VERBOSE}" -gt 0 && { cat ; } || { cat >/dev/null ; }
      }
    test "${VERBOSE}" -gt 0 && {
      echo "END RAW"
      echo "BEGIN JQ"
   }
   json="$( grep -aE "^.{0,4}\"event\"" "${TMPFILE2}" | jq -c ". += {timestamp: \"${NOW}\", useragent: \"${HTTP_USER_AGENT}\" }" 2>&1 )"
   # full json for log
   echo "${json}"
   # send truncated json to mail account for alerts
   echo "${json}" | jq '{timestamp: .timestamp, event: .event, user: .Account.title, player: .Player.title, ipAddress: .Player.publicAddress, file: {type: .Metadata.type, title: .Metadata.title, parentTitle: .Metadata.parentTitle, grandparentTitle: .Metadata.grandparentTitle } }' > "${TMPFILE3}" 
   # log to short log and send email only if it has contents
   printf "%s" "${json}" | grep -qE '.' && {
      cat "${TMPFILE3}" >> "${LOGFILESHORT}"
      # only alert if it matches. This syntax relies on the event syntax from Plex to be "media.play" or "media.stop" et al.
      alert_regex="(media\.)$( echo "${ALERT_ON_EVENTS}" | sed -r -e 's/^://g;' -e 's/:$//g;' -e 's/:+/|/g;' )"
      if grep -qE "${alert_regex}" "${TMPFILE3}" ;
      then
         # magic to get fixed-width font in gmail by using html pre tags
         { echo "<html><body><pre>" ; cat "${TMPFILE3}" ; echo "</pre></html></body>" ; } | mailsubject="Media activity
Content-Type: text/html" /usr/local/bin/send-plex-alert.sh STDIN
      else
         test "${VERBOSE}" -gt 0 && {
            echo "SKIPPING EMAIL because event does not match ${ALERT_ON_EVENTS}"
         }
      fi
   }
   test "${VERBOSE}" -gt 0 && {
      echo "END JQ"
      echo "REQUEST-STOP: ${NOW}"
   }
} | tee -a "${LOGFILE}"
echo "</pre>"
echo "</body></html>"

# clean up
rm -f "${TMPFILE1}" "${TMPFILE2}" "${TMPFILE3}"

The above webhook script contains its own comments. The VERBOSE attribute can be set to a non-zero amount (usually 1 ) to log much more information useful for debugging. Attribute ALERT_ON_EVENTS is a colon-separated list of event types passed from Plex that should trigger an email to the admin. This syntax depends on Plex's syntax not changing. The two log files are configured in the main webhook script. The LOGFILE contains the verbose logging (if enabled) and the full json object from Plex. The LOGFILESHORT contains the trimmed contents that will be used for the email.

Email script

A separate script holds the logic to send the email, file /usr/local/bin/send-plex-alert.sh.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/sh
# File: send-plex-alert.sh
# Project: Plex Activity Logging and Alerting Project
# startdate: 2020-08-07 17:21
# Documentation:
#    this one worked, but took a while, probably due to ISP smtp relay delay. echo "echo juliet oscar 10" | mailx -S 'from="bgstack15@ipa.example.com"' -S "Subject line" bgstack15@gmail.com ; echo $?
#    worked immediately: echo "hotel whiskey zulu 14" | mailx -v -S "Another thread here" -S smtp-use-starttls -S ssl-verify=ignore -S smtp-auth=login -S smtp=smtp://smtp.gmail.com:587 -S from="B Stack <bgstack15@gmail.com>" -S smtp-auth-user="bgstack15@gmail.com" -S smtp-auth-password='FIXME' -S nss-config-dir=/etc/pki/nssdb/ bgstack15@gmail.com
# Improve:
#    learn how to use a custom MAILRC env var which holds the "set smtp-user-password=" contents
# Dependencies:
#    mailx (mailx-12.5-19.el7.x86_64 @base)
#    Google account security set to allow "insecure apps" or similar https://support.google.com/accounts/answer/6010255?authuser=2&p=lsa_blocked&hl=en&authuser=2&visit_id=637324418441299012-2559162990&rd=1

test -z "${mailmessage}" && export mailmessage="${1}"
test "${mailmessage}" = "STDIN" && export mailmessage="$( cat 2>/dev/null )"
test -z "${mailuser}" && export mailuser="bgstack15@gmail.com"
test -z "${mailpassword}" && export mailpassword='SUPERSAFESTRING;'
test -z "${mailfrom}" && export mailfrom="Plex activity <bgstack15@gmail.com>"
test -z "${mailsubject}" && export mailsubject="Media activity"
test -z "${mailto}" && export mailto=bgstack15@gmail.com
printf "%s\n" "${mailmessage}" | mailx -s "${mailsubject}" \
   -S smtp-use-starttls \
   -S ssl-verify=ignore \
   -S smtp=smtp://smtp.gmail.com:587 \
   -S smtp-auth=login \
   -S from="${mailfrom}" \
   -S smtp-auth-user="${mailuser}" \
   -S smtp-auth-password="${mailpassword}" \
   -S nss-config-dir=/etc/pki/nssdb \
   "${mailto}"

SELinux policy

To use the webhook script and mail script with SELinux enforcing, you need to add a custom policy. File /usr/src/selinux/plexlog.te contains the uncompiled policy.

# File: /usr/src/selinux/plexlog.te
# Startdate: 2020-08-07 20:45
# Title: SELinux policy to allow webhook to send email
# Project: Plex Activity Logging and Alerting Project
module plexlog 1.0;

require {
    type httpd_sys_script_t;
    type init_t;
    type httpd_t;
    type smtp_port_t;
    type var_t;
    class process { noatsecure rlimitinh siginh };
    class unix_stream_socket { read write };
    class capability net_admin;
    class file { append execute getattr open read };
    class tcp_socket name_connect;
}

#============= httpd_sys_script_t ==============

#!!!! WARNING: 'var_t' is a base type.
allow httpd_sys_script_t var_t:file { append execute getattr open read };

#!!!! This avc can be allowed using one of the these booleans:
#     httpd_can_network_connect, nis_enabled
allow httpd_sys_script_t smtp_port_t:tcp_socket name_connect;

#============= httpd_t ==============
allow httpd_t httpd_sys_script_t:process { noatsecure rlimitinh siginh };
allow httpd_t self:capability net_admin;

To use this policy, you need to compile and install it.

sudo checkmodule -M -m -o plexlog.mod plexlog.te && sudo semodule_package -o plexlog.pp -m plexlog.mod && sudo semodule -i plexlog.pp

References

Weblinks

  1. Running Starbound server on CentOS 7
  2. Technical Notes: How to setup/configure/use mailx for Office365 account?

Comments