Synology PMS start-stop-status script prevents mounting Plex directory

I’m using an external USB thumb drive, but the thumb drive is inserted at the time the Synology boots.

Then it’s definitely a Synology issue.
My credential keys are stored on the system volume.

What you’re describing is the case where they don’t make the exception to get to the USB to unlock the main volume until AFTER the main volume is unlocked. IIRC, external device access normally initializes after the main internal devices (standard Linux).

Normally, Linux will look to external USB for boot first (if UEFI/bios says so) but doesn’t go for mounting the external devices until after main controllers - HDDs are complete

There’s the race condition.

If the key is stored externally, which it knows, then it must start that device and load the key.

They need the USB made available before the main volume or (if needed) on demand. This is a circular dependency.

Hi, I am having the same issue as x509v3. In my opinion, this is a Plex implementation issue of their scripts. PMS scripts are writing into the /volumeX/Plex directory before the share is mounted and ready.

synoshare shows where the Plex directory is, it also shows the Status. The plex script only checks where and ignores the status. Just because it is known where the Plex share is located, doesn’t mean its online. And if it’s not online, don’t touch it.
This is the best analogy I can come up with :grinning:
Plex’s script is looking out the window and seeing Crew Dragon on top of the Falcon 9. It’s there, everyone must be onboard, hit launch.
x509v3’s script modification is checking the Status, making sure the launch is a go, and then lifting off.

I have a DS918+ running DSM 6.2.3-25426 Update 2 and Plex 1.20.0.3181. This also has occurred and previous versions of DSM and PMS.
I have my /volumeX/Plex share encrypted. In fact, all my shares are encrypted. I store the encryption keys on a USB stick (that is hidden and secured to the building) that is attached to the DS918+ via a USB extension cable.
When the synology reboots, all Shares are decrypted and mounted except Plex. The Plex share doesn’t decrypt and mount because PMS put a empty tmp_transcoding directory inside the /volumeX/Plex directory.

Plex stores the encrytped files in /volumeX/@Plex@. In order for DSM to decrypted /volumeX/@Plex@, the /volumeX/Plex has to be empty. It is not empty because PMS created the empty directory. This is the race condition x509v3 has stated.

I am so excited to find x509v3’s script modifications!
I placed x509v3’s script block at the top of the start_plex() function. It looks like Plex changed the function since May 27th.

Thank you x509v3!

Plex, please implement a status check of the share, before writing to the share.

@Dave_I_the_Science_Guy

Dave,
I implemented that. I went to get it to share here. I cannot find it. I do not know how I messed that up but will fix immediately.

Please accept my apologies for whatever happened. I will get this in a release ASAP.

1 Like

Thank you @ChuckPa :smiley:

@Dave_I_the_Science_Guy

Dave, would you be willing to give this a try?

I was able to find code but want to confirm this works for someone with actual encryption or USB-based shares before testing on the QA systems.

This is the start of start-stop-status

It will not do anything for up to 20 seconds. The moment the share becomes “available” (mounted), it will continue.

If, after 20 attempts, it is still not ready, startup will fail out with error 150.
Error 150 is how packages send you a message when you’re using Package Center.

20 seconds is enough for a stored key / key tool to supply the key and unlock?

#!/bin/sh

# Check if th share is mounted or unmounted.
ShareUnmounted() {

  local Unmounted
  local MountStatus

  # Determine current share state, returning TRUE while unmounted
  MountStatus="$(synoshare --get Plex | grep Status | sed -e 's/^.*0x//' | tr -d ']')"

  Unmounted="$(expr $MountStatus % 2)"
  echo $Unmounted
}

# Give up to 20 seconds for DSM to unlock the Plex share before failing.
Retries=20

while [ $(ShareUnmounted) -gt 0 ] && [ $Retries -gt 0 ]
do
   sleep 1
   Retries=$(($Retries - 1))
done

# Exited the loop,  Did we run out of retries?
if [ $Retries -eq 0 ]; then
  echo "ERROR: The Plex share is still locked or unavailable after waiting 20 seconds.  Giving up." $SYNOPKG_TEMP_LOGFILE
  exit 150
fi

# DSM version variables
DSM_VERSION="$(get_key_value /etc.defaults/VERSION productversion)"
DSM_BUILD="$(get_key_value /etc.defaults/VERSION buildnumber)"
DSM_UPDATE="$(get_key_value /etc.defaults/VERSION smallfixnumber)"

Supplemental.

Manually testing, using a shared folder I mount after invoking the script.

  1. Status while umounted
  2. Mount via GUI
  3. Check Status again
bash-4.3# synoshare --get encrypt | grep Status
	 Status .....[0x1883]
bash-4.3# ./ShareUmounted 
Share mounted.
bash-4.3# synoshare --get encrypt | grep Status
	 Status .....[0x1882]
bash-4.3# 

Confirmation of Status where there is no encryption asserted

bash-4.3# synoshare --get encrypt | grep Status
	 Status .....[0x1880]
bash-4.3# 

To all following here:

Below please find start-stop-status which I will be submitting to QA for final testing.

Please let me know if this satisfies the need.
Please let me know even quicker if there are any issues :smiley:

#!/bin/sh

# Check if th share is mounted or unmounted.
ShareUnmounted() {

  local Unmounted
  local MountStatus

  # Determine current share state, returning TRUE while unmounted (LSB=1 signifies unmounted state)
  MountStatus="$(synoshare --get Plex | grep Status | sed -e 's/^.*0x//' | tr -d ']')"

  Unmounted="$(expr $MountStatus % 2)"
  echo $Unmounted
}

# DSM version variables
DSM_VERSION="$(get_key_value /etc.defaults/VERSION productversion)"
DSM_BUILD="$(get_key_value /etc.defaults/VERSION buildnumber)"
DSM_UPDATE="$(get_key_value /etc.defaults/VERSION smallfixnumber)"

# Set identification variables
PMS_INFO_VENDOR=Synology
PMS_INFO_DEVICE="$(get_key_value /etc.defaults/synoinfo.conf unique|cut -d'_' -f3|sed 's/^ds/DS/;s/^fs/FS/;s/^rs/RS/'|sed 's/^\([0-9]\)/DS\1/')"
PMS_INFO_MODEL="$(uname -m)"
PMS_INFO_PLATFORM_VERSION="DSM $DSM_VERSION.$DSM_BUILD-$DSM_UPDATE"

# Base Plex installation directory
PLEX_DIR="/var/packages/Plex Media Server/target"

# Find the Plex share
PLEX_PATH="$(/usr/syno/sbin/synoshare --get Plex | grep Path | awk -F[ '{print $2}' | awk -F] '{print $1}')"
if [ "$PLEX_PATH" = "" ]; then

  # Issue error to users (Not Found - rerun installation)
  echo "The Plex share could not be found.  Please check your configuration, reinstall Plex, or ask in our Support Forums." > $SYNOPKG_TEMP_LOGFILE
  exit 150
fi

# Give up to 20 seconds for DSM to unlock the Plex share before failing. (DSM will wait no more than 30 which includes actual PMS startup)
Retries=20

while [ $(ShareUnmounted) -gt 0 ] && [ $Retries -gt 0 ]
do
   sleep 1
   Retries=$(($Retries - 1))
done

# Exited the loop,  Did we run out of retries?
if [ $Retries -eq 0 ]; then
  echo "ERROR: The Plex share is still locked or unavailable after waiting 20 seconds.  Giving up." $SYNOPKG_TEMP_LOGFILE
  exit 150
fi

# Set final variables
PLEX_LIBRARY_PATH="$PLEX_PATH/Library/Application Support"
PID_FILE="$PLEX_LIBRARY_PATH/Plex Media Server/plexmediaserver.pid"


# Plex status must now be defined before parsing below
plex_status()
{
    if [ ! -f "$PID_FILE" ]
    then
        return 1
    fi
    if [ -d /proc/`cat "$PID_FILE"` ]
    then
        return 0
    else
        return 1
    fi
}

start_plex ()
{

  # Verify tmp_transcoding directory exists.  Silently recreate if possible.
  if [ ! -d "${PLEX_PATH}/tmp_transcoding" ]; then

    # There is nothing there or not a directory,  Silently recreate if possible
    rm -rf "${PLEX_PATH}/tmp_transcoding"
    mkdir -p "${PLEX_PATH}/tmp_transcoding"
    if [ $? -ne 0 ]; then

      echo "The Plex 'tmp_transcoding' directory was missing.  Attempts to recreate it failed.  Please repair manually or ask in our Support Forums." > $SYNOPKG_TEMP_LOGFILE
      exit 150
    fi

    # Grant permissions
    chown plex:users ${PLEX_PATH}/tmp_transcoding
  fi

  # Verify device permissions are intact
  Changed=0

  # Ensure the TV Butler udev rule exists
  if [ ! -f /lib/udev/rules.d/60-tv-butler-fix.rules ]; then
    echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="1d19", ATTRS{idProduct}=="0100", GROUP="video", MODE="0660"' > /lib/udev/rules.d/60-tv-butler-fix.rules
    Changed=1
  fi

  # Check if this model supports HW transcoding
  if [ -d /dev/dri ]; then

    # Ensure the HW Transcoding udev rule exists
    if [ ! -f /lib/udev/rules.d/60-fix-plex-hw-transcoding.rules ]; then
      echo 'SUBSYSTEM=="drm", GROUP="video"' > /lib/udev/rules.d/60-fix-plex-hw-transcoding.rules
      Changed=1

    # If the HW Transcoding udev rule exists, ensure it is correct
    elif [ "$(grep -c drm /lib/udev/rules.d/60-fix-plex-hw-transcoding.rules)" -eq 0 ]; then
      echo 'SUBSYSTEM=="drm", GROUP="video"' > /lib/udev/rules.d/60-fix-plex-hw-transcoding.rules
      Changed=1
    fi

    # Check if permissions correct
    if [ "$(ls -la /dev/dri/renderD128 | awk -F' ' '{print $4}')" != "video" ]; then
      Changed=1
    fi
  fi

  # Retrigger udevadm if needed
  if [ $Changed -eq 1 ]; then
    sync
    udevadm trigger
    sleep 3
  fi

  # Continue with normal PMS start
  PLEX_PATH=$(/usr/syno/sbin/synoshare --get Plex | grep Path | awk -F[ '{print $2}' | awk -F] '{print $1}')
  su plex -s /bin/sh -c \
  "export LC_ALL=en_US.utf8; \
   export LANG=en_US.utf8; \
   export LD_LIBRARY_PATH='$PLEX_DIR/lib'; \
   export PLEX_MEDIA_SERVER_MAX_PLUGIN_PROCS=6; \
   export PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR='$PLEX_LIBRARY_PATH'; \
   export TMPDIR=$PLEX_PATH/tmp_transcoding; \
   export PLEX_MEDIA_SERVER_DEFAULT_PREFERENCES='HardwareAcceleratedCodecs=true&TranscoderCanOnlyRemuxVideo=false'; \
   export PLEX_MEDIA_SERVER_INFO_VENDOR='$PMS_INFO_VENDOR'; \
   export PLEX_MEDIA_SERVER_INFO_DEVICE='$PMS_INFO_DEVICE'; \
   export PLEX_MEDIA_SERVER_INFO_MODEL='$PMS_INFO_MODEL'; \
   export PLEX_MEDIA_SERVER_INFO_PLATFORM_VERSION='$PMS_INFO_PLATFORM_VERSION'; \
   export PLEX_BROWSER_NO_HOME_DIR=1; \
   ulimit -s 3000; \
   sleep 2; \
   /var/packages/Plex\ Media\ Server/target/Plex\ Media\ Server >/dev/null 2>&1 &"

  counter=20
  while [ $counter -gt 0 ]
  do
    plex_status && break
    counter=$((counter - 1))
    sleep 1
  done

  # Give 3 seconds to get started
  sleep 3

  # create DSM shortcut
  if [ ! -f "/usr/syno/synoman/webman/3rdparty/plex" ]; then
    ln -s "$PLEX_DIR/dsm_config/plex" /usr/syno/synoman/webman/3rdparty
  fi
  plex_status

}

stop_plex ()
{

    # Make first requests nicely
    Count=11

    # Ask nicely for 1 minute
    while [ "$(ps -ef | grep -i ^plex | grep "Plex Media Server"| wc -l)" -ne 0 ] &&  [ $Count -gt 0 ]
    do

      if [ -f "$PID_FILE" ]; then

        kill -15 $(cat "$PID_FILE")
        sleep 5
      fi

      Count=$(($Count - 1))

    done

    # PMS has been asked nicely to shut down for 1 minute but has not.
    # Now we take down any processes owned by user 'plex'
    Pids="$(ps -ef | grep -i ^plex | awk '{print $2}')"

    Count=10
    Sig=-9

    while [ "$Pids" != "" ] && [ $Count -gt 0 ]
    do
        # Kill what's still running
        kill $Sig $Pids
        sleep 5

        # Look again
        Pids="$(ps -ef | grep -i ^plex | awk '{print $2}')"
        Count=$(($Count -1))

        # Force them down if our last iteration
        if [ $Count -eq 1 ]; then
          Sig=-11
        fi
    done

    # Remove the Package Manager linkage
    rm -rf /usr/syno/synoman/webman/3rdparty/plex
}

case $1 in
    start)
        echo Starting Plex ...
        start_plex
        exit $?
        ;;
    stop)
        echo Stopping Plex ...
        stop_plex
        exit $?
        ;;
    status)
        if plex_status
        then
            echo Plex is running
            exit 0
        else
            echo Plex is not running
            exit 1
        fi
        ;;
    log)
        echo "$PLEX_LIBRARY_PATH/Plex Media Server/Logs/Plex Media Server.log"
        exit 0
        ;;
    *)
        exit 1
        ;;
esac

@ChuckPa, It didn’t quite work on my installation
I believe the script needs to perform the expression in hex.

bash-4.3# synoshare --get Plex | grep Status
	 Status .....[0x1D82]

My Plex share status returns 0x1D82 (when decrypted/mounted) and 0x1D83 (when encrypted/unmounted).
The provided script truncated the 0x, therefore the expression was treated as a decimal integer instead of hexadecimal. Expr would throw an error

bash-4.3# ./start-stop-status status
expr: non-integer argument
./start-stop-status: line 42: [: -gt: unary operator expected
Plex is running

I updated the ShareUnmounted function to perform the expressions in hex.

ShareUnmounted() {

  local Unmounted
  local MountStatus

  # Determine current share state, returning TRUE while unmounted (LSB=1 signifies unmounted state)
  MountStatus="$(synoshare --get Plex | grep Status | sed -n 's/.*\[\(.*\)\].*/\1/p')"

  Unmounted="$(expr $((MountStatus)) % 2)"
  echo $Unmounted
}

The updated start-stop-status script works well

bash-4.3# synoshare --get Plex | grep Status
	 Status .....[0x1D82]
bash-4.3# ./start-stop-status status
Plex is running
bash-4.3# ./start-stop-status stop
Stopping Plex ...
bash-4.3# ./start-stop-status status
Plex is not running

# I unmounted the Plex share
bash-4.3# synoshare --get Plex | grep Status
	 Status .....[0x1D83]
bash-4.3# ./start-stop-status status
ERROR: The Plex share is still locked or unavailable after waiting 20 seconds.  Giving up.
bash-4.3# ./start-stop-status start
ERROR: The Plex share is still locked or unavailable after waiting 20 seconds.  Giving up.

# I decrypted/mounted the Plex share
bash-4.3# synoshare --get Plex | grep Status
	 Status .....[0x1D82]
bash-4.3# ./start-stop-status status
Plex is not running
bash-4.3# ./start-stop-status start
Starting Plex ...
bash-4.3# ./start-stop-status status
Plex is running

The script also works on reboot!

Thanks for the quick turn around, sorry i wasn’t able to test until just now

@Dave_I_the_Science_Guy

Try this? :smiling_imp: (so much for my fat fingers)

MountStatus="0x$(synoshare --get Plex | grep Status | sed -e 's/^.*0x//' | tr -d ']')"

Unmounted="$(expr $(($MountStatus)) % 2)"

As for 20 seconds. I have a maximum of 30 seconds (DSM limit) for an app to respond to a start-stop-status request before DSM marks it as dead.

I am trying to take other apps starting (on slower CPUs) all at the same time.
I cannot predict what’s going on. I only know the DSM SDK limits.

What is reasonable for me to wait for you, the user, to enter the key?
How long should I wait? I can’t leave it indefinitely hung without completely rewriting how the start-stop-status works.

This way, it fails but DSM doesn’t mark it dead. When you do get to the keyboard and unlock, you can then manually start PMS.

@ChuckPa,
The updated MountStatus and Unmounted statements worked.
My last reboot, the 20 second delay wasn’t long enough. I updated the wait time to 30 seconds, and that worked consistently.

I’m wondering if there is a better way?
The docker package starts without any issue. The docker share is also encrypted, and that daemon and containers start without issue.

The upstart config for both pkgctl-Docker.conf and pkgctl-Plex Media Server.conf both start on syno.pkgctl.pkgstart. I’m looking at Docker’s start-stop-status script to determine what type of check and/or delay they implemented.

Docker is completely different in how the master service executive works.
Package Center and Docker controller are different.

This is all we can do within the confines of the SDK. Docker and sub-containers versus native SDK. I’m willing to bet while the main package runs at boot, the Docker exec doesn’t start containers until after native packages.

I’ll bump it up the max delay and hope for the best. It should never happen but hey, it is what it is.

I will get this off to QA for review tonight.

QA has approved my changes.
I moved the changes into the production status.

We will see them when Engineering makes their next update of the packaging.
(I am trying to get that in this next release cycle)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.