#!/command/with-contenv bash # shellcheck shell=bash # Use /command/with-contenv shebang because /usr/bin/with-contenv is created in this script for older bases # Version 3 # 2022-09-25 - Initial Release MOD_SCRIPT_VER="3.20250825" # Define custom folder paths SCRIPTS_DIR="/custom-cont-init.d" SERVICES_DIR="/custom-services.d" if [[ ${DOCKER_MODS_DEBUG_CURL,,} = "true" ]]; then CURL_NOISE_LEVEL="-vs" else CURL_NOISE_LEVEL="--silent" fi tamper_check() { # Tamper check custom service locations if [[ -d "${SERVICES_DIR}" ]] && [[ -n "$(find ${SERVICES_DIR}/* ! -user root 2>/dev/null)" ]]; then echo "╔═════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ Some of the contents of the folder ${SERVICES_DIR} ║ ║ are not owned by root, which is a security risk. ║ ║ ║ ║ Please review the permissions of this folder and its contents to make ║ ║ sure they are owned by root, and can only be modified by root. ║ ║ ║ ╚═════════════════════════════════════════════════════════════════════════╝" elif [[ -d "${SERVICES_DIR}" ]] && [[ -n "$(find ${SERVICES_DIR}/* -perm -o+w 2>/dev/null)" ]]; then echo "╔═════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ Some of the contents of the folder ${SERVICES_DIR} ║ ║ have write permissions for others, which is a security risk. ║ ║ ║ ║ Please review the permissions of this folder and its contents to make ║ ║ sure they are owned by root, and can only be modified by root. ║ ║ ║ ╚═════════════════════════════════════════════════════════════════════════╝" fi # Tamper check custom script locations if [[ -d "${SCRIPTS_DIR}" ]] && [[ -n "$(find ${SCRIPTS_DIR}/* ! -user root 2>/dev/null)" ]]; then echo "╔═════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ Some of the contents of the folder ${SCRIPTS_DIR} ║ ║ are not owned by root, which is a security risk. ║ ║ ║ ║ Please review the permissions of this folder and its contents to make ║ ║ sure they are owned by root, and can only be modified by root. ║ ║ ║ ╚═════════════════════════════════════════════════════════════════════════╝" elif [[ -d "${SCRIPTS_DIR}" ]] && [[ -n "$(find ${SCRIPTS_DIR}/* -perm -o+w 2>/dev/null)" ]]; then echo "╔═════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ Some of the contents of the folder ${SCRIPTS_DIR} ║ ║ have write permissions for others, which is a security risk. ║ ║ ║ ║ Please review the permissions of this folder and its contents to make ║ ║ sure they are owned by root, and can only be modified by root. ║ ║ ║ ╚═════════════════════════════════════════════════════════════════════════╝" fi } process_custom_services() { # Remove all existing custom services before continuing to ensure # we aren't running anything the user may have removed if [[ -n "$(/bin/ls -A /etc/s6-overlay/s6-rc.d/custom-svc-* 2>/dev/null)" ]]; then echo "[custom-init] removing existing custom services..." rm -rf /etc/s6-overlay/s6-rc.d/custom-svc-* rm /etc/s6-overlay/s6-rc.d/user/contents.d/custom-svc-* fi # Make sure custom service directory exists and has files in it if [[ -e "${SERVICES_DIR}" ]] && [[ -n "$(/bin/ls -A ${SERVICES_DIR} 2>/dev/null)" ]]; then echo "[custom-init] Service files found in ${SERVICES_DIR}" for SERVICE in "${SERVICES_DIR}"/*; do NAME="$(basename "${SERVICE}")" if [[ -f "${SERVICE}" ]]; then echo "[custom-init] ${NAME}: service detected, copying..." mkdir -p /etc/s6-overlay/s6-rc.d/custom-svc-"${NAME}"/dependencies.d/ cp "${SERVICE}" /etc/s6-overlay/s6-rc.d/custom-svc-"${NAME}"/run chmod +x /etc/s6-overlay/s6-rc.d/custom-svc-"${NAME}"/run echo "longrun" >/etc/s6-overlay/s6-rc.d/custom-svc-"${NAME}"/type touch /etc/s6-overlay/s6-rc.d/custom-svc-"${NAME}"/dependencies.d/init-services touch /etc/s6-overlay/s6-rc.d/user/contents.d/custom-svc-"${NAME}" echo "[custom-init] ${NAME}: copied" elif [[ ! -f "${SERVICE}" ]]; then echo "[custom-init] ${NAME}: is not a file" fi done else echo "[custom-init] No custom services found, skipping..." fi } # Create our with-contenv alias with umask support create_with_contenv_alias() { if [[ ! -f /command/with-contenv ]]; then echo "/command/with-contenv not found, skipping alias creation" return fi rm -rf /usr/bin/with-contenv # intentional tabs in the heredoc cat <<-EOF >/usr/bin/with-contenv #!/bin/bash if [[ -f /run/s6/container_environment/UMASK ]] && { [[ "\$(pwdx \$\$)" =~ "/run/s6/legacy-services/" ]] || [[ "\$(pwdx \$\$)" =~ "/run/s6/services/" ]] || [[ "\$(pwdx \$\$)" =~ "/servicedirs/svc-" ]]; }; then umask "\$(cat /run/s6/container_environment/UMASK)" fi exec /command/with-contenv "\$@" EOF chmod +x /usr/bin/with-contenv } # Check for curl curl_check() { if [[ ! -f /usr/bin/curl ]] || [[ ! -f /usr/bin/jq ]]; then write_mod_info "Curl/JQ was not found on this system and is required for Docker mods, installing..." if [[ -f /usr/bin/apt ]]; then # Ubuntu export DEBIAN_FRONTEND="noninteractive" apt-get update apt-get install --no-install-recommends -y \ curl \ jq elif [[ -f /sbin/apk ]]; then # Alpine apk add --no-cache \ curl \ jq elif [[ -f /usr/bin/dnf ]]; then # Fedora dnf install -y --setopt=install_weak_deps=False --best \ curl \ jq elif [[ -f /usr/sbin/pacman ]]; then # Arch pacman -Sy --noconfirm \ curl \ jq fi fi } write_mod_info() { local MSG=$* echo -e "[mod-init] $MSG" } write_mod_error() { local MSG=$* echo -e "[mod-init] (ERROR) $MSG" } write_mod_debug() { local MSG=$* if [[ ${DOCKER_MODS_DEBUG,,} = "true" ]]; then echo -e "[mod-init] (DEBUG) $MSG"; fi } # Use different filtering depending on URL get_blob_sha() { MULTIDIGEST=$(curl -f --retry 5 --retry-max-time 30 --retry-connrefused \ ${CURL_NOISE_LEVEL} \ --location \ --header "Accept: application/vnd.docker.distribution.manifest.v2+json" \ --header "Accept: application/vnd.oci.image.index.v1+json" \ --header "Authorization: Bearer ${1}" \ --user-agent "${MOD_UA}" \ "${2}/${3}") if jq -e '.layers // empty' <<< "${MULTIDIGEST}" >/dev/null 2>&1; then # If there's a layer element it's a single-arch manifest so just get that digest jq -r '.layers[0].digest' <<< "${MULTIDIGEST}"; else # Otherwise it's multi-arch or has manifest annotations if jq -e '.manifests[]?.annotations // empty' <<< "${MULTIDIGEST}" >/dev/null 2>&1; then # Check for manifest annotations and delete if found write_mod_debug "Mod has one or more manifest annotations" >&2 MULTIDIGEST=$(jq 'del(.manifests[] | select(.annotations))' <<< "${MULTIDIGEST}") fi if [[ $(jq '.manifests | length' <<< "${MULTIDIGEST}") -gt 1 ]]; then # If there's still more than one digest, it's multi-arch write_mod_debug "Mod has a multi-arch manifest" >&2 MULTIDIGEST=$(jq -r ".manifests[] | select(.platform.architecture == \"${4}\").digest?" <<< "${MULTIDIGEST}") if [[ -z "${MULTIDIGEST}" ]]; then exit 1 fi else # Otherwise it's single arch write_mod_debug "Mod only has a single arch manifest" >&2 MULTIDIGEST=$(jq -r ".manifests[].digest?" <<< "${MULTIDIGEST}") fi if DIGEST=$(curl -f --retry 5 --retry-max-time 30 --retry-connrefused \ ${CURL_NOISE_LEVEL} \ --location \ --header "Accept: application/vnd.docker.distribution.manifest.v2+json" \ --header "Accept: application/vnd.oci.image.manifest.v1+json" \ --header "Authorization: Bearer ${1}" \ --user-agent "${MOD_UA}" \ "${2}/${MULTIDIGEST}"); then jq -r '.layers[0].digest' <<< "${DIGEST}"; fi fi } get_auth_url() { local auth_header local realm_url local service local scope # Call to get manifests and extract www-authenticate header auth_header=$(curl -sLI ${CURL_NOISE_LEVEL} "${1}/${2}" | grep -i www-authenticate | tr -d '\r') if [[ -n "${auth_header}" ]]; then write_mod_debug "${auth_header}" >&2 # Extract realm URL from www-authenticate header realm_url=$(echo "$auth_header" | awk -F'[="]+' '/realm=/{print $2}') service=$(echo "$auth_header" | awk -F'[="]+' '/service=/{print $4}') scope=$(echo "$auth_header" | awk -F'[="]+' '/scope=/{print $6}') echo "$realm_url?service=$service&scope=$scope" else exit 1 fi } get_arch(){ local arch if [[ -f /sbin/apk ]]; then arch=$(apk --print-arch) elif [[ -f /usr/bin/dpkg ]]; then arch=$(dpkg --print-architecture) else arch=$(uname -m) fi case "${arch}" in x86_64 ) arch="amd64" ;; aarch64 ) arch="arm64" ;; esac echo "${arch}" } # Main run logic run_mods() { write_mod_info "Running Docker Modification Logic" write_mod_debug "Running in debug mode" write_mod_debug "Mod script version ${MOD_SCRIPT_VER}" mkdir -p /modcache # Fetch internal fds FD_MAX=3 while IFS= read -r -d '' FD; do if [[ $(cat "${FD}") -gt "${FD_MAX}" ]]; then FD_MAX=$(cat "${FD}") fi done < <(find /etc/s6-overlay/s6-rc.d -type f -name 'notification-fd' -print0) for DOCKER_MOD in $(echo "${DOCKER_MODS}" | tr '|' '\n'); do # Support alternative endpoints case "${DOCKER_MOD}" in linuxserver/* ) [[ ${DOCKER_MODS_FORCE_REGISTRY,,} = "true" ]] && REGISTRY="registry-1.docker.io" || REGISTRY="lscr.io" ;; docker.io/linuxserver/* ) [[ ${DOCKER_MODS_FORCE_REGISTRY,,} = "true" ]] && REGISTRY="registry-1.docker.io" || REGISTRY="lscr.io" DOCKER_MOD="${DOCKER_MOD#docker.io/*}" ;; ghcr.io/linuxserver/* ) [[ ${DOCKER_MODS_FORCE_REGISTRY,,} = "true" ]] && REGISTRY="ghcr.io" || REGISTRY="lscr.io" DOCKER_MOD="${DOCKER_MOD#ghcr.io/*}" ;; docker.io/* ) REGISTRY="registry-1.docker.io" DOCKER_MOD="${DOCKER_MOD#docker.io/*}" ;; * ) # Default assumption is docker.io REGISTRY="registry-1.docker.io" MOD="${DOCKER_MOD%/*}" # If mod still has a / after stripping off the image name it's not docker.io if [[ $MOD == */* ]]; then REGISTRY="${MOD%%/*}" DOCKER_MOD="${DOCKER_MOD#"$REGISTRY"/*}" # If "repo" name has . in it, then assume it's actually a registry with no repo elif [[ ${DOCKER_MOD%%/*} =~ \. ]]; then REGISTRY="${DOCKER_MOD%%/*}" DOCKER_MOD="${DOCKER_MOD##*/}" fi ;; esac # Identify what kind of image name format we're working with if [[ ${DOCKER_MOD} == *:*@* ]]; then ENDPOINT="${DOCKER_MOD%%:*}" USERNAME="${DOCKER_MOD%%/*}" REPO="${ENDPOINT#*/}" FULLTAG="${DOCKER_MOD#*:}" TAG="${FULLTAG%@*}" TAGSHA="${DOCKER_MOD#*@}" elif [[ ${DOCKER_MOD} == *@* ]]; then ENDPOINT="${DOCKER_MOD%%@*}" USERNAME="${DOCKER_MOD%%/*}" REPO="${ENDPOINT#*/}" unset FULLTAG unset TAG TAGSHA="${DOCKER_MOD#*@}" elif [[ ${DOCKER_MOD} == *:* ]]; then ENDPOINT="${DOCKER_MOD%%:*}" USERNAME="${DOCKER_MOD%%/*}" REPO="${ENDPOINT#*/}" unset FULLTAG TAG="${DOCKER_MOD#*:}" unset TAGSHA else # Mod has no tag ENDPOINT="${DOCKER_MOD}" USERNAME="${DOCKER_MOD%%/*}" REPO="${ENDPOINT#*/}" unset FULLTAG TAG="latest" unset TAGSHA fi FILENAME="${USERNAME}.${REPO}${TAG:+.${TAG}}${TAGSHA:+.${TAGSHA#*:}}" MANIFEST_URL="https://${REGISTRY}/v2/${ENDPOINT}/manifests" write_mod_debug "Manifest URL: ${MANIFEST_URL}" BLOB_URL="https://${REGISTRY}/v2/${ENDPOINT}/blobs/" MOD_UA="Mozilla/5.0 (Linux $(uname -m)) linuxserver.io ${REGISTRY}/${ENDPOINT}${TAG:+:${TAG}}${TAGSHA:+@${TAGSHA}}" write_mod_debug "Registry='${REGISTRY}', Repository='${USERNAME}', Image='${ENDPOINT}', Tag='${TAG:-N/A}', TagSHA='${TAGSHA:-N/A}'" case "${REGISTRY}" in "lscr.io") AUTH_URL="https://ghcr.io/token?scope=repository%3A${USERNAME}%2F${REPO}%3Apull";; "ghcr.io") AUTH_URL="https://ghcr.io/token?scope=repository%3A${USERNAME}%2F${REPO}%3Apull";; "quay.io") AUTH_URL="https://quay.io/v2/auth?service=quay.io&scope=repository%3A${USERNAME}%2F${REPO}%3Apull";; "registry-1.docker.io") AUTH_URL="https://auth.docker.io/token?service=registry.docker.io&scope=repository:${ENDPOINT}:pull";; *) AUTH_URL=$(get_auth_url "${MANIFEST_URL}" "${TAG:-${TAGSHA}}") esac # Kill off modification logic if any of the usernames are banned for BANNED in $(curl -s https://raw.githubusercontent.com/linuxserver/docker-mods/master/blacklist.txt); do if [[ "${BANNED,,}" == "${USERNAME,,}" ]]; then if [[ -z ${RUN_BANNED_MODS+x} ]]; then write_mod_info "${DOCKER_MOD} is banned from use due to reported abuse, skipping mod" continue else write_mod_info "You have chosen to run banned mods, ${DOCKER_MOD} will be applied" fi fi done if [[ -n "${AUTH_URL}" ]]; then # Get registry token for api operations TOKEN="$( curl -f --retry 5 --retry-max-time 30 --retry-connrefused \ ${CURL_NOISE_LEVEL} \ "${AUTH_URL}" | jq -r '.token' )" else write_mod_info "Could not fetch auth URL from registry for ${DOCKER_MOD}, attempting unauthenticated fetch" fi write_mod_info "Adding ${DOCKER_MOD} to container" # If we're using lscr try and get the manifest from ghcr, if it fails re-request a token from Docker Hub if [[ "${REGISTRY}" == "lscr.io" ]]; then if [[ -n $(curl --user-agent "${MOD_UA}" -sLH "Authorization: Bearer ${TOKEN}" "${MANIFEST_URL}/${TAG:-${TAGSHA}}" | jq -r '.errors' >/dev/null 2>&1) ]]; then write_mod_debug "Couldn't fetch manifest from ghcr.io, trying docker.io" AUTH_URL="https://auth.docker.io/token?service=registry.docker.io&scope=repository:${ENDPOINT}:pull" TOKEN="$( curl -f --retry 5 --retry-max-time 30 --retry-connrefused \ ${CURL_NOISE_LEVEL} \ "${AUTH_URL}" | jq -r '.token' )" fi fi if [[ -n "${AUTH_URL}" ]]; then write_mod_debug "Using ${AUTH_URL} as auth endpoint" fi ARCH=$(get_arch) write_mod_debug "Arch detected as ${ARCH}" if [[ -z "${TOKEN}" ]] && [[ -z $(curl -sIk "${MANIFEST_URL}") ]]; then write_mod_error "No response from ${REGISTRY}, switching to offline mode for ${DOCKER_MOD}" MOD_OFFLINE="true" else # Determine first and only layer of image SHALAYER=$(get_blob_sha "${TOKEN}" "${MANIFEST_URL}" "${TAGSHA:-$TAG}" "${ARCH:--amd64}") if [[ $? -eq 1 ]]; then write_mod_error "No manifest available for arch ${ARCH:--amd64}, cannot fetch mod" continue elif [[ -z "${SHALAYER}" ]]; then write_mod_info "${DOCKER_MOD} digest could not be fetched from ${REGISTRY}, checking local cache" MOD_OFFLINE="true" fi write_mod_debug "Mod SHA is ${SHALAYER:-unknown, mod may not exist}" fi # Check if we have allready applied this layer if [[ -f "/${FILENAME}" ]] && [[ "${SHALAYER}" == "$(cat /"${FILENAME}")" ]]; then write_mod_info "${DOCKER_MOD} at ${SHALAYER} has been previously applied skipping" unset MOD_OFFLINE continue elif [[ -f "/modcache/${FILENAME}.tar.xz" ]] && [[ "${SHALAYER}" =~ $(sha256sum "/modcache/${FILENAME}.tar.xz" | cut -f1 -d" ") ]]; then write_mod_info "${DOCKER_MOD} at ${SHALAYER} found in modcache, applying" elif [[ -f "/modcache/${FILENAME}.tar.xz" ]] && [[ "${MOD_OFFLINE}" = "true" ]]; then write_mod_info "OFFLINE: ${DOCKER_MOD} found in modcache" elif [[ ! -f "/modcache/${FILENAME}.tar.xz" ]] && [[ "${MOD_OFFLINE}" = "true" ]]; then write_mod_error "OFFLINE: ${DOCKER_MOD} not found in modcache, skipping" unset MOD_OFFLINE continue else write_mod_info "Downloading ${DOCKER_MOD} from ${REGISTRY}" if [[ -f "/modcache/${FILENAME}.lock" ]]; then write_mod_info "${DOCKER_MOD} is already being downloaded by another container, waiting..." for ((i = 5 ; i < 41 ; i=i*2 )); do sleep $i if [[ ! -f "/modcache/${FILENAME}.lock" ]]; then SKIP_MOD_DOWNLOAD=true break elif [[ $i == 40 ]]; then write_mod_error "${DOCKER_MOD} timed out waiting for lock, skipping\n\tIf no other containers are using this mod you may need to delete /modcache/${FILENAME}.lock" SKIP_MOD=true fi done fi if [[ "${SKIP_MOD}" == "true" ]]; then unset MOD_OFFLINE SKIP_MOD_DOWNLOAD SKIP_MOD continue elif [[ "${SKIP_MOD_DOWNLOAD}" != "true" ]]; then # Download and extract layer to / touch "/modcache/${FILENAME}.lock" curl -f --retry 5 --retry-max-time 30 --retry-all-errors \ ${CURL_NOISE_LEVEL} \ --location \ --header "Authorization: Bearer ${TOKEN}" \ --user-agent "${MOD_UA}" \ "${BLOB_URL}${SHALAYER}" -o \ "/modcache/${FILENAME}.tar.xz" fi fi if ! tar -tzf "/modcache/${FILENAME}.tar.xz" >/dev/null 2>&1; then write_mod_error "Invalid tarball for ${DOCKER_MOD}, skipping" if [[ -f "/modcache/${FILENAME}.lock" ]]; then rm "/modcache/${FILENAME}.lock" || write_mod_error "Failed to delete lock file /modcache/${FILENAME}.lock" fi unset MOD_OFFLINE SKIP_MOD_DOWNLOAD SKIP_MOD continue fi if [[ -z "${MODMANAGER_MODONLY}" ]]; then write_mod_info "Installing ${DOCKER_MOD}" mkdir -p /tmp/mod tar xzf "/modcache/${FILENAME}.tar.xz" -C /tmp/mod # Remove any v2 mod elements as they're no longer supported if [[ -d /tmp/mod/etc/cont-init.d ]]; then rm -rf /tmp/mod/etc/cont-init.d fi if [[ -d /tmp/mod/etc/services.d ]]; then rm -rf /tmp/mod/etc/services.d fi # Rationalise fds if [[ ! -f "/tmp/mod/fd-static" ]]; then while IFS= read -r -d '' FD; do (( FD_MAX++ )) echo $FD_MAX > "${FD}" done < <(find /tmp/mod/etc/s6-overlay/s6-rc.d -type f -name 'notification-fd' -print0) fi shopt -s dotglob cp -R /tmp/mod/* / shopt -u dotglob rm -rf /tmp/mod echo "${SHALAYER}" >"/${FILENAME}" else write_mod_debug "Modmanager skipping mod application" fi if [[ -f "/modcache/${FILENAME}.lock" ]]; then rm "/modcache/${FILENAME}.lock" || write_mod_error "Failed to delete lock file /modcache/${FILENAME}.lock" fi if [[ -z "${MODMANAGER_MODONLY}" ]]; then write_mod_info "${DOCKER_MOD} applied to container" fi unset MOD_OFFLINE SKIP_MOD_DOWNLOAD SKIP_MOD done } run_mods_local() { write_mod_info "Running Local Docker Modification Logic" for DOCKER_MOD in $(echo "${DOCKER_MODS}" | tr '|' '\n'); do # Check mod file exists if [[ -f "/mods/${DOCKER_MOD}.tar" ]]; then # Calculate mod bits FILENAME="${DOCKER_MOD}.local" SHALAYER=$(sha256sum "/mods/${DOCKER_MOD}.tar" | cut -d " " -f 1) write_mod_debug "Mod checksum is ${SHALAYER}" # Check if we have allready applied this layer if [[ -f "/${FILENAME}" ]] && [[ "${SHALAYER}" == "$(cat /"${FILENAME}")" ]]; then write_mod_info "${DOCKER_MOD} at ${SHALAYER} has been previously applied, skipping" else write_mod_info "Installing ${DOCKER_MOD} from /mods/${DOCKER_MOD}.tar" mkdir -p "/tmp/mod/${DOCKER_MOD}" tar xf "/mods/${DOCKER_MOD}.tar" -C "/tmp/mod/${DOCKER_MOD}" # Remove any v2 mod elements as they're no longer supported if [[ -d "/tmp/mod/${DOCKER_MOD}/etc/cont-init.d" ]]; then rm -rf "/tmp/mod/${DOCKER_MOD}/etc/cont-init.d" fi if [[ -d "/tmp/mod/${DOCKER_MOD}/etc/services.d" ]]; then rm -rf "/tmp/mod/${DOCKER_MOD}/etc/services.d" fi shopt -s dotglob cp -R "/tmp/mod/${DOCKER_MOD}"/* / shopt -u dotglob rm -rf "/tmp/mod/${DOCKER_MOD}" echo "${SHALAYER}" >"/${FILENAME}" write_mod_info "${DOCKER_MOD} applied to container" fi elif [[ -d "/mods/${DOCKER_MOD}" ]]; then # Calculate mod bits FILENAME="${DOCKER_MOD}.local" SHALAYER=$(tar c "/mods/${DOCKER_MOD}" 2>/dev/null | sha256sum | cut -d " " -f 1) write_mod_debug "Mod checksum is ${SHALAYER}" # Check if we have allready applied this layer if [[ -f "/${FILENAME}" ]] && [[ "${SHALAYER}" == "$(cat /"${FILENAME}")" ]]; then write_mod_info "${DOCKER_MOD} at ${SHALAYER} has been previously applied, skipping" else write_mod_info "Installing ${DOCKER_MOD} from /mods/${DOCKER_MOD}/" mkdir -p "/tmp/mod/${DOCKER_MOD}" cp -R "/mods/${DOCKER_MOD}" "/tmp/mod/" # Remove any v2 mod elements as they're no longer supported if [[ -d "/tmp/mod/${DOCKER_MOD}/etc/cont-init.d" ]]; then rm -rf "/tmp/mod/${DOCKER_MOD}/etc/cont-init.d" fi if [[ -d "/tmp/mod/${DOCKER_MOD}/etc/services.d" ]]; then rm -rf "/tmp/mod/${DOCKER_MOD}/etc/services.d" fi shopt -s dotglob cp -R "/tmp/mod/${DOCKER_MOD}"/* / shopt -u dotglob rm -rf "/tmp/mod/${DOCKER_MOD}" echo "${SHALAYER}" >"/${FILENAME}" write_mod_info "${DOCKER_MOD} applied to container" fi else write_mod_error "${DOCKER_MOD} not found in /mods, skipping" fi done } run_branding() { # intentional tabs in the heredoc cat <<-EOF | tee /run/branding /etc/s6-overlay/s6-rc.d/init-adduser/branding > /dev/null 2>&1 ─────────────────────────────────────── ██╗ ███████╗██╗ ██████╗ ██║ ██╔════╝██║██╔═══██╗ ██║ ███████╗██║██║ ██║ ██║ ╚════██║██║██║ ██║ ███████╗███████║██║╚██████╔╝ ╚══════╝╚══════╝╚═╝ ╚═════╝ Brought to you by linuxserver.io ─────────────────────────────────────── EOF } # Main script loop if [[ -n "${MODMANAGER_MODONLY}" ]]; then run_mods exit 0 fi if grep -qEe ' / \w+ ro' /proc/mounts; then printf '1' > /run/s6/container_environment/LSIO_READ_ONLY_FS LSIO_READ_ONLY_FS=1 fi if [[ ! $(stat /run -c %u) == "0" ]]; then printf '1' > /run/s6/container_environment/LSIO_NON_ROOT_USER LSIO_NON_ROOT_USER=1 fi if [[ "${LSIO_FIRST_PARTY}" = "true" ]]; then run_branding fi if [[ -z ${LSIO_READ_ONLY_FS} ]] && [[ -z ${LSIO_NON_ROOT_USER} ]]; then # Run alias creation functions if [[ ! -f "/usr/bin/with-contenv" ]] || [[ -L "/usr/bin/with-contenv" ]]; then create_with_contenv_alias fi if [[ -d "${SCRIPTS_DIR}" ]] || [[ -d "${SERVICES_DIR}" ]]; then tamper_check process_custom_services fi # Run mod logic if [[ -n "${DOCKER_MODS+x}" ]] && [[ "${DOCKER_MODS_SIDELOAD,,}" = "true" ]]; then run_mods_local elif [[ -n "${DOCKER_MODS+x}" ]]; then curl_check run_mods fi elif [[ -n ${LSIO_READ_ONLY_FS} ]] && [[ -n ${LSIO_NON_ROOT_USER} ]]; then echo "╔═════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ You are running this container read-only and as a non-root user: ║ ║ This combination of settings is not supported ║ ║ and may result in unwanted behaviour. ║ ║ ║ ╚═════════════════════════════════════════════════════════════════════════╝" elif [[ -n ${LSIO_READ_ONLY_FS} ]]; then echo "╔═════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ You are running this container read-only: ║ ║ UMASK, custom services, & docker mod functionality will be disabled ║ ║ and the PUID/PGID environment variables will have no effect. ║ ║ ║ ╚═════════════════════════════════════════════════════════════════════════╝" elif [[ -n ${LSIO_NON_ROOT_USER} ]]; then echo "╔═════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ You are running this container as a non-root user: ║ ║ UMASK, custom services, & docker mod functionality will be disabled ║ ║ and the PUID/PGID environment variables will have no effect. ║ ║ ║ ╚═════════════════════════════════════════════════════════════════════════╝" fi