#!/bin/bash # Linux Utility: plexmediaserver.preinst # Description: Collect setup and runtime information about the host Plex Media Server is (to be) installed on # then validate to see if PMS can indeed be installed. # Author: Plex # Runtime Environments Init=0 # True if Init system Systemd=0 # True if Systemd system LinuxContainer=0 # True if in a Linux Container Custom=0 # True if container which is not recognized. # PMS default installation values NewInstall=1 # Default to a new until determined otherwise PlexUser=plex # Default PMS user PlexGroup=plex # Default PMS group AppSuppDir="/var/lib/plexmediaserver/Library/Application Support" # Default location for "Plex Media Server" PlexTempDir="/tmp" # Default temporary transcoding directory LangEncoding="en_US.UTF-8" # Character set and language encoding VideoGroup=video # Default udev group NeedGroup=1 # Need the group created NeedUser=1 # Need the username created # Process Control Variables Errors=0 # Number of Errors ExistingVersion=0 # Currently installed PMS version Fail=0 # True if critical errors detected. HaveHardware=0 # True if Transcoding HW found in /dev/dri Havei915=0 # True if i915 found (QSV) HaveNvidia=0 # True if Nvidia GPU found HaveOverride=0 # True if an override file was used HaveTranscoderPref=0 # True if TranscoderTempDir used from Preferences.xml NeedVideo=0 # Do we need to join the video group? OverrideDir="" # Where user overrides are found OverrideFile="" # Path to override file we are using, if any Running=0 # True if Plex processes exist Verbose=1 # Output error messages Warnings=0 # Number of Warnings # Distribution variables DistroName="" # ID= from /etc/os-releases DistroVersion="" # VERSION_ID= from /etc/os-releases # Support Functions # Function to output to log and console. # # Usage: # $1 = Message (should be "Text Message" quoted). # $2 = Optional "0" or "1" flag. "1" adds "(extracted from Preferences.xml)" message # # Note: Blank lines (Null text strings "") are not written to the system log. There is no need to waste that space. Output() { # Prepare base message Message="PlexMediaServer install: $1" # Add supplemental information for special case of preferences.xml if [ "$2" = "1" ]; then Message="$Message (set in Preferences.xml)" fi # Output the message even if a blank message (make output nice) echo "$Message" # Return if blank. if [ "$1" = "" ]; then return fi # Output to log if [ $Systemd -eq 1 ]; then echo MESSAGE="$Message" | logger --journald else echo "$Message" | logger fi } # Evaluate the Username given and return the UID # # Usage: # $1 = Username # echo out the UID if found (or null) # GetUID() { Retval="-1" # Lookup the name and hold result UserString="$(getent passwd "$1")" # if null, return -1 (not found) if [ "$UserString" != "" ]; then Retval="$(echo "$UserString" | awk -F: '{print $3}')" fi echo "$Retval" } # Extract value from Preferences.xml # Return null string if not present GetPref() { Retval="" # This must remain as written with everything escaped for variables to work. # Do not attempt to extract unless exists in the file. if [ -e "$2" ]; then if [ "$(grep "$1" "$2" | wc -l)" -gt 0 ]; then Retval="$(grep -iPo \(\?\<=$1=\"\)\[\^\"\]\* "$2")" fi fi echo "$Retval" } # Extract Environment and Process variables from Configuration files # Config files such as /etc/default/plexmediaserver and override.conf # # This function does not care which format is supplied # Processing sequence is important # # Usage: # $1 = variable # $2 = file GetValue() { Retval="" Retval="$(grep -v '^#' "$2" | sed -e 's/^export //' | sed -e 's/^Environment=//' | \ sed -e 's/^"//' | grep ^"$1" | head -1 | awk -F= '{print $2}' | tr -d '"' )" echo "$Retval" } # # # Check through a list of locations and locate the named command. # This is necessary because some distros do not properly configure the default path for root when not a login shell. # Discovered on Debian 10.x FindLocation() { local Location Location="" [ -e "/usr/local/bin/$1" ] && Location="/usr/local/bin/$1" [ -e "/usr/local/sbin/$1" ] && Location="/usr/local/sbin/$1" [ -e "/bin/$1" ] && Location="/bin/$1" [ -e "/usr/bin/$1" ] && Location="/usr/bin/$1" [ -e "/sbin/$1" ] && Location="/sbin/$1" [ -e "/usr/sbin/$1" ] && Location="/usr/sbin/$1" echo "$Location" } # Find the listed external commands which are used by the installer. FindCommands() { local Location for i in cat chmod chown cp find getent grep groupadd head mkdir stat tail useradd usermod do Location="$(FindLocation $i)" # If not null (we found it) create an alias to circumvent default path shortcomings. [ "$Location" != "" ] && alias $i="$Location" done } # Conversion version string to decimal integer for comparisons, stripping alpha and - # Args: $1 = version string # Ret: Integerized decimal version: e.g. "21.23.49" - 21023049 (decimal) ToInteger() { echo "$(echo "$1" | sed -e 's/-.*//' | sed -e 's/\./0/g')" } # Get and return the installed version of the specified package. # return null if not installed. GetVersion() { local Version Version="$(dpkg-query -f '${source:Upstream-Version}\n' -W "$1" 2>/dev/null | sed -e 's/[+_-].*//')" [ "$Version" = "" ] && Version="Not Installed" echo "$Version" } # Module: plexmediaserver.preinst # Purpose: Determine if PMS can be installed on this host. # Return error to the package manager if it cannot be installed. # Write configuration information to use to /tmp/plexinstaller.tmp if the configuration is valid. # # Define a file and use this 'temp' file for passing all gleened information to plexmediaserver.postinst # The information is written in bash shell variable format so it may be sourced inline as postinst runs. # Should installation fail or the resultant envionment not be what the user expects, this file contains everything gleened # during preinstallation validation which is important to PMS and valid. # The contents of this file (flags, a couple pathnames and text names) will drive the rest of installation InstConfig=/tmp/plexinstaller.log # Find all external commands we will need in this script FindCommands # Clear previous installation attempt rm -f "$InstConfig" # Let the user know what we are doing Output "Pre-installation Validation." # Let's see what we can learn from this system # Detect true control program (init or systemd) if [ "$(cat /proc/1/comm)" = "systemd" ]; then # If systemctl exists, we're doing ok so far if [ "$(which systemctl)" != "" ]; then Systemd=1 fi # If this looks like init, be careful of redirectioon elif [ "$(cat /proc/1/comm)" = "init" ]; then # Verify it's not a redirected init -> systemd if [ "$(readlink /sbin/init)" = "" ]; then Init=1 elif [ "$(readlink /sbin/init | grep systemd | wc -l)" -gt 0 ]; then Systemd=1 else Output "Unsupported init/systemd hybrid control configuration." exit 1 fi # See if in a Docker container elif [ "$(grep docker /proc/1/cgroup | wc -l)" -gt 0 ]; then Output "Docker detected. Preinstallation validation not required." # Put date/time stamp in log file for use in debugging echo "# Plex Media Server installation configuration info: $(date)" >>"$InstConfig" echo "Docker=1" >>"$InstConfig" exit 0 fi # Having tested for Systemd, Init, and Docker containers, declare all others as custom and report if [ $((Systemd + Init)) -eq 0 ]; then # Inform user we detect a custom environment, write the log, and exit Output "Custom environment detected. Skipping preinstallation validation." echo "# Plex Media Server installation configuration info: $(date)" >>"$InstConfig" echo Custom=1 >>"$InstConfig" # Exit now exit 0 fi # Begin General validation # Who's in command here? # Sensors to maximum # This is a Systemd system, look for: a) Current status, b) incorrect service file, c) Old init file, d) Current Config if [ $Systemd -eq 1 ]; then # if there is a lingering /etc/systemd/system/plexmediaserver.service, this is wrong. Flag the PlexUser to correct if [ -e /etc/systemd/system/plexmediaserver.service ]; then Output "Systemd configuration error detected:" Output "File \"/etc/systemd/system/plexmediaserver.service\" is leftover from an earlier Debian/Ubuntu bug." Output "Please transfer any customizations \(APP_DIR, User, Group, TMPDIR, and/or UMask\) to a proper \"$OverrideDir/override.conf\"" Output "and reinstall." Output "Unit override \"/etc/systemd/system/plexmediaserver.service\" found." Output "Only \"/etc/systemd/system/plexmediaserver.service.d/override.conf\" is supported." Fail=1 else # Where are we looking? OverrideDir="/etc/systemd/system/plexmediaserver.service.d" # Validate OverrideDir contents if it exists if [ -d "$OverrideDir" ]; then # Use override.conf if it exists because systemctl writes "override.conf" when editing. It has priority. if [ -e "$OverrideDir/override.conf" ]; then OverrideFile="$OverrideDir/override.conf" HaveOverride=1 # Use local.conf as next in line due to quasi-established convention. elif [ -e "$OverrideDir/local.conf" ]; then OverrideFile="$OverrideDir/local.conf" HaveOverride=1 # It's not any of the well known names. else # Take the first of whatever we find Count=$(find "$OverrideDir" -name '*.conf' -print | wc -l) if [ $Count -eq 0 ]; then Output "INFO: \"$OverrideDir\" exists but no override \"override.conf\" file was found. Ignored." OverrideDir="" HaveOverride=0 # If one, and only one, file exists then use it. elif [ $Count -eq 1 ]; then # Lock phasers on target ("Some-Nice-Restaurant.conf" ?) OverrideFile="$(find "$OverrideDir" -name '*.conf' -print | head -1)" HaveOverride=1 fi fi # Examine all possible (should only be one but be prudent) Count=$(find "$OverrideDir" -name '*.conf' -print | wc -l) if [ $Count -gt 1 ]; then # Inform user what we found (not an error) FileList=" Files found are: " for i in "$OverrideDir"/*.conf do FileList="$FileList $(basename "$i")" done Output "Warning: Multiple override files found. PMS may not function as expected." Output "$FileList" Output " Selected: \"$(basename "$OverrideFile")\"." Warnings=$((Warnings + 1)) fi fi fi # Pre-Warp ship, go easy on her. elif [ $Init -eq 1 ]; then # Validate /etc/default/plexmediaserver if present if [ -e /etc/default/plexmediaserver ]; then OverrideDir=/etc/default OverrideFile=/etc/default/plexmediaserver HaveOverride=1 fi # Don't know how we got this far. Engage tractor beam else Output "ERROR: Unknown system control process found for PID 1 in validation phase. Cannot install." Fail=1 return $Fail fi # Independently, evaluate if in a LXC. This is used later in installation to avoid UDEV trigger bug # Look only for a the prefix 'container='. No further refinement needed as docker already handled above. if [ "$(cat /proc/1/environ | tr '\000' '\n' | grep -a '^container=' )" != "" ]; then LinuxContainer=1 fi # Validate the override file contents if it exists if [ $HaveOverride -eq 1 ]; then # Search for, and retrieve if present OverrideUser="$(GetValue User "$OverrideFile")" OverrideGroup="$(GetValue Group "$OverrideFile")" OverrideAppSupp="$(GetValue PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR "$OverrideFile")" # Use first found of 4 possible Temp Dir specifications (warp core) OverrideTmpDir="$(GetValue TMPDIR "$OverrideFile")" if [ "$OverrideTmpDir" = "" ]; then OverrideTmpDir="$(GetValue TEMP "$OverrideFile")" if [ "$OverrideTmpDir" = "" ]; then OverrideTmpDir="$(GetValue TMP "$OverrideFile")" fi fi # If we have something, validate it. if [ "$OverrideTmpDir" != "" ]; then if [ ! -d "$OverrideTmpDir" ]; then Output "Error: Temp directory \"$OverrideTmpDir\" used in \"$OverrideFile\" file does not exist." Fail=1 Errors=$((Errors + 1)) else PlexTempDir="$OverrideTmpDir" fi fi # Use Override values if they exist and begin validation. Some Warp nacelles need this. # Validate the user, accept as PlexUser if valid if [ "$OverrideUser" != "" ]; then if [ "$(getent passwd "$OverrideUser" | wc -l)" -eq 0 ]; then Output "Error: Unknown username \"$OverrideUser\" used in \"$OverrideFile\"." Fail=1 Errors=$((Errors + 1)) else PlexUser="$OverrideUser" fi fi # Validate the group, accept as PlexGroup if valid if [ "$OverrideGroup" != "" ]; then if [ "$(getent group "$OverrideGroup" | wc -l)" -eq 0 ]; then Output "Error: Unknown group \"$OverrideGroup\" used in \"$OverrideFile\"." Fail=1 Errors=$((Errors + 1)) else PlexGroup="$OverrideGroup" fi fi # Validate Application Support Dir (antimatter storage) if [ "$OverrideAppSupp" != "" ]; then if [ ! -d "$OverrideAppSupp" ]; then Output "Error: Directory \"$OverrideAppSupp\" used in \"$OverrideFile\" does not exist." Errors=$((Errors + 1)) Fail=1 else AppSuppDir="$OverrideAppSupp" fi fi # Validate Tmpdir if [ "$OverrideTmpdir" != "" ]; then if [ ! -d "$OverrideTmpdir" ]; then Output "Error: Directory \"$OverrideTmpdir\" used in \"$OverrideFile\" does not exist." Errors=$((Errors + 1)) Fail=1 else PlexTempDir="$OverrideTmpdir" fi fi # Look for Plex_Media_Server_User (backward compatibility) if [ "$(GetValue PLEX_MEDIA_SERVER_USER "$OverrideFile")" != "" ]; then OverrideUser="$(GetValue PLEX_MEDIA_SERVER_USER "$OverrideFile")"; if [ "$(getent passwd "$OverrideUser" | wc -l)" -eq 0 ]; then Output "Error: Unknown username \"$OverrideUser\" used in \"$OverrideFile\"." Errors=$((Errors + 1)) Fail=1 else PlexUser="$OverrideUser" fi fi # Look for forbidden (no longer needed) environment variables in it for ForbiddenVar in LD_LIBRARY_PATH PLEX_MEDIA_SERVER_HOME PLEX_MEDIA_SERVER_TMPDIR do if [ "$(GetValue $ForbiddenVar "$OverrideFile")" != "" ]; then Output "ERROR: Environment variable \"$ForbiddenVar\" is no longer allowed in \"$OverrideFile\". Please remove." Errors=$((Errors + 1)) Fail=1 fi done # Verify UTF-8 encoding specified if found OverrideLang="$(GetValue LC_ALL "$OverrideFile")" if [ "$OverrideLang" != "" ]; then if [ "$(echo "$OverrideLang" | grep UTF-8 | wc -l)" -eq 0 ]; then Output "Error: LC_ALL specifies a non UTF-8 encoding: \"$OverrideLang\"" Fail=1 Errors=$((Errors + 1)) else LangEncoding="$OverrideLang" fi fi fi # Begin Preferences ingestion PrefTempDir="$(GetPref TranscoderTempDirectory "$AppSuppDir/Plex Media Server/Preferences.xml")" if [ "$PrefTempDir" != "" ]; then # Validate this preference just as we would with any else # but remain silent if it does not exist (per PMS default behavior) if [ -d "$PrefTempDir" ]; then # Supersede existing value just as PMS will PlexTempDir="$PrefTempDir" HaveTranscoderPref=1 fi fi # All Overrides loaded. # Validate PlexUser, PlexGroup, and AppSuppDir existance for Installation processing flags # Do we need to create the user? if [ "$(getent passwd "$PlexUser" | wc -l)" -gt 0 ]; then NeedUser=0 fi # Do we need to create the group (fix previous ill deeds done) if [ "$(getent group "$PlexGroup" | wc -l)" -gt 0 ]; then NeedGroup=0 fi # If we have a AppSuppDir & User & Group then it's an update. if [ -d "$AppSuppDir" ] && [ $NeedUser -eq 0 ] && [ $NeedGroup -eq 0 ]; then NewInstall=0 fi # If update, look at current Application Support directory. It might be customized if [ $NewInstall -eq 0 ]; then # What's the PlexUser's UID PlexUserUID=$(GetUID "$PlexUser") # Make certain Captain Dunsel can read/write everything there. (Watch for overloaded names) Owner="$(stat -L --format=%U "$AppSuppDir")" OwnerUID="$(stat -L --format=%u "$AppSuppDir")" # Check resolved UID and not name to avoid pitfalls of Inside vs. Outside a container and an overloaded /etc/passwd. # Warn if a mismatch as it still might be OK. if [ "$OwnerUID" != "$PlexUserUID" ]; then Output "Warning: \"$AppSuppDir\" isn't owned by \"$PlexUser\", UID: $PlexUserUID. Found \"$Owner\", UID: $OwnerUID instead. Continuing." Warnings=$((Warnings + 1)) fi fi # If PMS is already installed, get the existing version number from README.Debian if [ -f /usr/share/doc/plexmediaserver/README.Debian ]; then ExistingVersion=$(head -1 /usr/share/doc/plexmediaserver/README.Debian | awk -F- '{print $2}' | sed -e 's/\./ /g' | awk '{printf "%d%2.2d%2.2d", $1,$2,$3}') fi # Operating system distro and version DistroName="$(grep ^ID= /etc/os-release)" ; DistroName="${DistroName//[^.]*=/}" DistroVersion="$(grep ^VERSION_ID= /etc/os-release)" ; DistroVersion="${DistroVersion//[^.]*=\"/}" DistroVersion="${DistroVersion//[\"\.]/}" # Processor info Processor="$(cat /proc/cpuinfo | grep 'model name' | uniq)" Processor="${Processor//[^:]*: /}" if [ "$(echo "$Processor" | grep -c Intel)" -gt 0 ]; then # Is there an Intel i915 present (possible QSV capability) [ "$(echo /sys/module/i915/drivers/pci:i915/*:*:*.*)" != "/sys/module/i915/drivers/pci:i915/*:*:*.*" ] && Havei915=1 fi # Only check for Transcoding hardware on viable architectures (include in conditional as needed) # Keep PlexUser in the video group regardess in case the user has other non-QSV capability. Architecture="$(uname -m)" if [ "$Architecture" = "x86_64" ]; then # Is there a Nvidia card present (possible HW transcoding capability) [ "$(echo /sys/module/nvidia/drivers/pci:nvidia/*:*:*.*)" != "/sys/module/nvidia/drivers/pci:nvidia/*:*:*.*" ] && HaveNvidia=1 # Final check; Is this system HW transcoding capable (Info only) if [ -e /dev/dri/renderD128 ]; then HaveHardware=1 fi # Make certain PlexUser is a member of the video group (don't pick up the root group - doesn't count) NeedVideo=0 if [ -e /dev/dri/renderD128 ]; then if [ "$(stat --format=%G /dev/dri/renderD128)" != "root" ]; then VideoGroup="$(stat --format=%G /dev/dri/renderD128)" fi elif [ -e /dev/fb0 ]; then if [ "$(stat --format=%G /dev/fb0)" != "root" ]; then VideoGroup="$(stat --format=%G /dev/fb0)" fi elif [ -e /dev/video0 ]; then if [ "$(stat --format=%G /dev/video0)" != "root" ]; then VideoGroup="$(stat --format=%G /dev/video0)" fi fi # If PlexUser not member of VideoGroup, flag it if [ "$(getent group "$VideoGroup" | grep "$PlexUser" | wc -l)" -eq 0 ]; then # PlexUser not a member of the Video group NeedVideo=1 fi fi # X86_64 # With all data having been ingested # If this is Init-based, make certain /usr/sbin/update-rc.d exists before we need it. if [ $Init -eq 1 ]; then if [ ! -e /usr/sbin/update-rc.d ]; then Output "File: /usr/sbin/update-rc.d not found. Required to configure autostart." Errors=$((Errors + 1)) Fail=1 fi fi # # Let's gather some basic host information to aid in further diagnostics should the need arise # # Platform info if [ -e /sys/class/dmi/id/product_name ]; then Platform="$(cat /sys/class/dmi/id/product_name)" else Platform="not available" fi # Distribution if [ -e /etc/os-release ]; then Distro="$(cat /etc/os-release | grep PRETTY_NAME | sed -e 's/^.*=//' | tr -d '"')" else Distro="not available" fi # Kernel info if [ -e /bin/uname ] || [ -e /usr/bin/uname ]; then Kernel="$(uname -a)" else Kernel="not available" fi # Memory info if [ -e /bin/free ] || [ -e /usr/bin/free ]; then Memory="$(free -m | grep '^Mem' | awk '{print $2, $3, $4}')" else Memory="not available" fi # Determine if anything is running if [ "$(ps -ef | grep "Plex Media Server" | grep -v grep | wc -l)" -gt 0 ]; then Running=1 elif [ -e /tmp/.plexmediaserver.state ] && [ "$(grep 'Running=1' /tmp/.plexmediaserver.state | wc -l)" -gt 0 ]; then Running=1 rm -f /tmp/.plexmediaserver.state fi # Installed memory Memory="$(free -m | tail -2 | head -1 | awk '{print $2,$3,$4 }')" # If no failures, silently write the ".log" info transfer file in /tmp if [ $Fail -eq 0 ]; then # Create secure config file { rm -f "$InstConfig" touch "$InstConfig" } owner=$(stat -L --format=%u "$InstConfig") group=$(stat -L --format=%g "$InstConfig") if [ $owner -ne 0 ] || [ $group -ne 0 ]; then Output "ERROR: Invalid installation config. Refusing to install." exit 1 fi { # Output everything to the Installation Config file # Put date/time stamp in log file for use in postinstall echo "# Plex Media Server installation configuration info: $(date)" # Write out what we need echo "Init=$Init" echo "Systemd=$Systemd" echo "Custom=$Custom" echo "LinuxContainer=$LinuxContainer" # Local config echo "NewInstall=$NewInstall" echo "HaveOverride=$HaveOverride" echo "OverrideFile=\"$OverrideFile\"" echo "PlexUser=\"$PlexUser\"" echo "PlexGroup=\"$PlexGroup\"" echo "VideoGroup=\"$VideoGroup\"" echo "AppSuppDir=\"$AppSuppDir\"" echo "PlexTempDir=\"$PlexTempDir\"" echo "LangEncoding=\"$LangEncoding\"" echo "ExistingVersion=$ExistingVersion" echo "DistroName=\"$DistroName\"" echo "DistroVersion=\"$DistroVersion\"" # What Installation needs to do echo "HaveHardware=$HaveHardware" echo "HaveTranscoderPref=$HaveTranscoderPref" echo "NeedUser=$NeedUser" echo "NeedGroup=$NeedGroup" echo "NeedVideo=$NeedVideo" echo "Verbose=$Verbose" echo "Running=$Running" echo "Errors=$Errors" echo "Warnings=$Warnings" # Transcoding hardware information echo "Havei915=$Havei915" echo "HaveNvidia=$HaveNvidia" # Host information for diagnostics (not used by installation. Only used in forum for diagnostic purposes) echo "Platform=\"$Platform\"" echo "Processor=\"$Processor\"" echo "Distro=\"$Distro\"" echo "Kernel=\"$Kernel\"" echo "Memory=\"$Memory\"" } > "$InstConfig" else # Write to the tty and system log about what we found so it can be fixed or audited. Output "" Output "Pre-installation Validation failed." Output "Configuration information discovered:" # New or Update if [ $NewInstall -eq 1 ]; then Output " Installation Type: New" else Output " Installation Type: Update" fi # Init or Systemd if [ $Init -eq 1 ]; then Output " Process Control: init" elif [ $Systemd -eq 1 ]; then Output " Process Control: systemd" else Output " Process Control: UNKNOWN" fi # The particulars Output " Plex User: $PlexUser" Output " Plex Group: $PlexGroup" Output " Video Group: $VideoGroup" Output " Metadata Dir: $AppSuppDir" Output " Temp Directory: $PlexTempDir" "$HaveTranscoderPref" Output " Lang Encoding: $LangEncoding" Output " Processor: $Processor" # Have a customized configuration if [ $HaveOverride -gt 0 ]; then Output " Config file used: $OverrideFile" fi # Inform the user of Transcoding HW state if [ $Havei915 -gt 0 ]; then Output " Intel i915 Hardware: Found" else Output " Intel i915 Hardware: Not found" fi # Inform the user of Transcoding HW state if [ $HaveNvidia -gt 0 ]; then Output " Nvidia GPU card: Found" else Output " Nvidia GPU card: Not Found" fi # A blank line output for clarity Output " " fi # Provide informative progress message if [ $((Errors + Warnings)) -eq 0 ]; then Output "Pre-installation Validation complete." else Output "Pre-installation Validation complete. Errors: $Errors, Warnings: $Warnings" fi # we are done exit $Fail