diff --git a/.templates/.README.md b/.templates/.README.md new file mode 100644 index 0000000..b13c4f8 --- /dev/null +++ b/.templates/.README.md @@ -0,0 +1,97 @@ +# Home assistant add-on: alexbelgium + + + +[![Donate][donation-badge]](https://www.buymeacoffee.com/alexbelgium) +[![Donate][paypal-badge]](https://www.paypal.com/donate/?hosted_button_id=DZFULJZTP3UQA) + + +[donation-badge]: https://img.shields.io/badge/Buy%20me%20a%20coffee%20(no%20paypal)-%23d32f2f?logo=buy-me-a-coffee&style=flat&logoColor=white +[paypal-badge]: https://img.shields.io/badge/Buy%20me%20a%20coffee%20with%20Paypal-0070BA?logo=paypal&style=flat&logoColor=white + +[](https://www.codacy.com/gh/alexbelgium/hassio-addons/dashboard?utm_source=github.com&utm_medium=referral&utm_content=alexbelgium/hassio-addons&utm_campaign=Badge_Grade) +[](https://github.com/alexbelgium/hassio-addons/actions/workflows/weekly-supelinter.yaml) +[](https://github.com/alexbelgium/hassio-addons/actions/workflows/onpush_builder.yaml) +[](https://github.com/alexbelgium/hassio-addons/actions/workflows/weekly_stats.yaml) + +[support-badge]: https://camo.githubusercontent.com/f4dbb995049f512fdc97fcc9e022ac243fa38c408510df9d46c7467d0970d959/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f537570706f72742d7468726561642d677265656e2e737667 + +_Thanks to everyone having starred my repo! To star it click on the image below, then it will be on top right. Thanks!_ + +[](https://github.com/alexbelgium/hassio-addons/stargazers) + +_Thanks to all contributors !_ + +[](https://github.com/alexbelgium/hassio-addons/graphs/contributors) + +Stargazers locations : + + + +## About + +Home Assistant allows anyone to create add-on repositories to share their +add-ons for Home Assistant easily. This repository is one of those repositories, +providing extra Home Assistant add-ons for your installation. + +The primary goal of this project is to provide you (as a Home Assistant user) +with additional, high quality, add-ons that allow you to take your automated +home to the next level. + +## Installation + +[![Add repository on my Home Assistant][repository-badge]][repository-url] + +If you want to do add the repository manually, please follow the procedure highlighted in the [Home Assistant website](https://home-assistant.io/hassio/installing_third_party_addons). Use the following URL to add this repository: https://github.com/alexbelgium/hassio-addons + +## Statistics + +### Number of addons + +- In the repository : %%STATS_ADDONS%% +- Installed : %%STATS_DOWNLOADS%% + +### Top 3 + +1. %%STATS_ONE%% +2. %%STATS_TWO%% +3. %%STATS_THREE%% + +### Architectures used + +- %%STATS_AMD64%% +- %%STATS_AARCH64%% +- %%STATS_ARMV7%% + +### Stars evolution + +[](https://star-history.com/#alexbelgium/hassio-addons&Date) + +## Add-ons provided by this repository + +%%ADDONS_LIST%% + +## Support + +Got questions? + +You have several options to get them answered: + +- The Home Assistant [Community Forum][forum]. +- This repository issues list + +[aarch64-badge]: https://img.shields.io/badge/aarch64--green.svg?logo=arm +[amd64-badge]: https://img.shields.io/badge/amd64--green.svg?logo=amd +[armv7-badge]: https://img.shields.io/badge/armv7--green.svg?logo=arm +[aarch64no-badge]: https://img.shields.io/badge/aarch64--orange.svg?logo=arm +[amd64no-badge]: https://img.shields.io/badge/amd64--orange.svg?logo=amd +[armv7no-badge]: https://img.shields.io/badge/armv7--orange.svg?logo=arm +[ingress-badge]: https://img.shields.io/badge/-ingress-blueviolet.svg?logo=Ingress +[mariadb-badge]: https://img.shields.io/badge/Service-MariaDB-green.svg?logo=mariadb&logoColor=white +[mqtt-badge]: https://img.shields.io/badge/Service-MQTT-green.svg?logo=chromecast&logoColor=white +[localdisks-badge]: https://img.shields.io/badge/Mounts-localdisks-blue.svg +[smb-badge]: https://img.shields.io/badge/Mounts-networkdisks-blue.svg +[full_access-badge]: https://img.shields.io/badge/Requires-full_access-orange.svg +[forum]: https://community.home-assistant.io/t/alexbelgium-repo-60-addons +[repository-badge]: https://img.shields.io/badge/Add%20repository%20to%20my-Home%20Assistant-41BDF5?logo=home-assistant&style=for-the-badge +[repository-url]: https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons diff --git a/.templates/00-aaa_dockerfile_backup.sh b/.templates/00-aaa_dockerfile_backup.sh new file mode 100644 index 0000000..af8f14d --- /dev/null +++ b/.templates/00-aaa_dockerfile_backup.sh @@ -0,0 +1,51 @@ +#!/bin/bash +set -e + +# If dockerfile failed install manually + +############################## +# Automatic modules download # +############################## +if [ -e "/MODULESFILE" ]; then + MODULES=$(< /MODULESFILE) + MODULES="${MODULES:-00-banner.sh}" + echo "Executing modules script : $MODULES" + + if ! command -v bash > /dev/null 2> /dev/null; then (apt-get update && apt-get install -yqq --no-install-recommends bash || apk add --no-cache bash) > /dev/null; fi \ + && if ! command -v curl > /dev/null 2> /dev/null; then (apt-get update && apt-get install -yqq --no-install-recommends curl || apk add --no-cache curl) > /dev/null; fi \ + && apt-get update && apt-get install -yqq --no-install-recommends ca-certificates || apk add --no-cache ca-certificates > /dev/null || true \ + && mkdir -p /etc/cont-init.d \ + && for scripts in $MODULES; do echo "$scripts" && curl -f -L -s -S "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/$scripts" -o /etc/cont-init.d/"$scripts" && [ "$(sed -n '/\/bin/p;q' /etc/cont-init.d/"$scripts")" != "" ] || (echo "script failed to install $scripts" && exit 1); done \ + && chmod -R 755 /etc/cont-init.d +fi + +####################### +# Automatic installer # +####################### +if [ -e "/ENVFILE" ]; then + PACKAGES=$(< /ENVFILE) + echo "Executing dependency script with custom elements : $PACKAGES" + + if ! command -v bash > /dev/null 2> /dev/null; then (apt-get update && apt-get install -yqq --no-install-recommends bash || apk add --no-cache bash) > /dev/null; fi \ + && if ! command -v curl > /dev/null 2> /dev/null; then (apt-get update && apt-get install -yqq --no-install-recommends curl || apk add --no-cache curl) > /dev/null; fi \ + && curl -f -L -s -S "https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/ha_automatic_packages.sh" --output /ha_automatic_packages.sh \ + && chmod 755 /ha_automatic_packages.sh \ + && eval /./ha_automatic_packages.sh "${PACKAGES:-}" \ + && rm /ha_automatic_packages.sh +fi + +if [ -e "/MODULESFILE" ] && [ ! -f /ha_entrypoint.sh ]; then + for scripts in $MODULES; do + echo "$scripts : executing" + chown "$(id -u)":"$(id -g)" /etc/cont-init.d/"$scripts" + chmod a+x /etc/cont-init.d/"$scripts" + /./etc/cont-init.d/"$scripts" || echo "/etc/cont-init.d/$scripts: exiting $?" + rm /etc/cont-init.d/"$scripts" + done | tac +fi + +####################### +# Correct permissions # +####################### +[ -d /etc/services.d ] && chmod -R 777 /etc/services.d +[ -d /etc/cont-init.d ] && chmod -R 777 /etc/cont-init.d diff --git a/.templates/00-banner.sh b/.templates/00-banner.sh new file mode 100644 index 0000000..8d5d50e --- /dev/null +++ b/.templates/00-banner.sh @@ -0,0 +1,85 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash disable=SC2016 +set -e +# ============================================================================== +# Displays a simple add-on banner on startup +# ============================================================================== + +if ! bashio::supervisor.ping 2> /dev/null; then + # Degraded mode if no homeassistant + bashio::log.blue \ + '-----------------------------------------------------------' + bashio::log.blue "Starting addon without HA support" + bashio::log.blue "Version : ${BUILD_VERSION:-1.0}" + bashio::log.blue "Please use Docker Compose for env variables" + bashio::log.blue \ + '-----------------------------------------------------------' + # Use environment variables instead of addon options + echo "... convert scripts to use environment variables instead of addon options" + while IFS= read -r scripts; do + [[ "$scripts" == *"00-banner.sh"* ]] && continue + sed -i -e 's/bashio::config.has_value[[:space:]]*["'"'"']\([^"'"'"']*\)["'"'"']/[ ! -z "${\1:-}" ]/g' \ + -e 's/bashio::config.true[[:space:]]*["'"'"']\([^"'"'"']*\)["'"'"']/[ ! -z "${\1:-}" ] \&\& [ "${\1:-}" = "true" ]/g' \ + -e 's/\$(bashio::config[[:space:]]*["'"'"']\([^"'"'"']*\)["'"'"'])/${\1:-}/g' \ + -e 's/\$(bashio::addon.port[[:space:]]*["'"'"']\([0-9]*\)["'"'"'])/${\1:-}/g' \ + -e 's/bashio::config.require.ssl/true/g' \ + -e 's/\$(bashio::addon.ingress_port)/""/g' \ + -e 's/\$(bashio::addon.ingress_entry)/""/g' \ + -e 's/\$(bashio::addon.ip_address)/""/g' "$scripts" + done < <(grep -srl "bashio" /etc/cont-init.d /custom-services.d /etc/services.d /etc/s6-overlay) + exit 0 +fi + +bashio::log.blue \ + '-----------------------------------------------------------' +bashio::log.blue " Add-on: $(bashio::addon.name)" +bashio::log.blue " $(bashio::addon.description)" +bashio::log.blue \ + '-----------------------------------------------------------' + +bashio::log.blue " Add-on version: $(bashio::addon.version)" +if bashio::var.true "$(bashio::addon.update_available)"; then + bashio::log.magenta ' There is an update available for this add-on!' + bashio::log.magenta \ + " Latest add-on version: $(bashio::addon.version_latest)" + bashio::log.magenta ' Please consider upgrading as soon as possible.' +else + bashio::log.green ' You are running the latest version of this add-on.' +fi + +bashio::log.blue " System: $(bashio::info.operating_system)" +bashio::log.blue " Architecture: $(bashio::info.arch) / $(bashio::info.machine)" +bashio::log.blue " Home Assistant Core: $(bashio::info.homeassistant)" +bashio::log.blue " Home Assistant Supervisor: $(bashio::info.supervisor)" + +bashio::log.blue \ + '-----------------------------------------------------------' +bashio::log.blue \ + ' Please, share the above information when looking for help' +bashio::log.blue \ + ' or support in, e.g., GitHub, forums' +bashio::log.blue \ + '-----------------------------------------------------------' +bashio::log.green \ + ' Provided by: https://github.com/alexbelgium/hassio-addons ' +bashio::log.blue \ + '-----------------------------------------------------------' + +# ============================================================================== +# Global actions for all addons +# ============================================================================== +if bashio::config.has_value "PUID" && bashio::config.has_value "PGID" && id abc &> /dev/null; then + bashio::log.green ' Defining permissions for main user : ' + PUID="$(bashio::config "PUID")" + PGID="$(bashio::config "PGID")" + usermod -o -u "$PUID" abc + groupmod -o -g "$PGID" abc + bashio::log.blue "User UID: $(id -u abc)" + bashio::log.blue "User GID: $(id -g abc)" + + bashio::log.blue \ + '-----------------------------------------------------------' +fi + +# Clean bashrc file safely +if [ -f ~/.bashrc ]; then : > ~/.bashrc; fi diff --git a/.templates/00-bettercomments.sh b/.templates/00-bettercomments.sh new file mode 100644 index 0000000..32acdb4 --- /dev/null +++ b/.templates/00-bettercomments.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Color comments +#! Red +#? Question +#// Done +#todo To do +#* Green diff --git a/.templates/00-deprecated.sh b/.templates/00-deprecated.sh new file mode 100644 index 0000000..2ac070f --- /dev/null +++ b/.templates/00-deprecated.sh @@ -0,0 +1,19 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e + +# ============================================================================== +# Displays a simple add-on banner on startup +# ============================================================================== + +echo "" +bashio::log.yellow "####################" +bashio::log.yellow "# ADDON deprecated #" +bashio::log.yellow "####################" +echo "" +bashio::log.yellow "This addon is now supported in the official HA community repository. You should migrate your data as soon as possible! This addon will not be supported and updates might stop in the future." +bashio::log.yellow "You'll likely get better support as the official community is supported by the HA devs ! If some features from the official add-on are missing you should raise a request on the ha community add-ons repo" +bashio::log.yellow "Thanks for all users over the years !" +echo "" + +sleep 5 diff --git a/.templates/00-global_var.sh b/.templates/00-global_var.sh new file mode 100644 index 0000000..bfa2af6 --- /dev/null +++ b/.templates/00-global_var.sh @@ -0,0 +1,131 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e + +if ! bashio::supervisor.ping 2> /dev/null; then + echo "..." + exit 0 +fi + +################################### +# Export all addon options as env # +################################### + +# For all keys in options.json +JSONSOURCE="/data/options.json" + +# Define secrets location +if [ -f /homeassistant/secrets.yaml ]; then + SECRETSOURCE="/homeassistant/secrets.yaml" +elif [ -f /config/secrets.yaml ]; then + SECRETSOURCE="/config/secrets.yaml" +else + SECRETSOURCE="false" +fi + +# Export keys as env variables +# echo "All addon options were exported as variables" +mapfile -t arr < <(jq -r 'keys[]' "${JSONSOURCE}") + +# Escape special characters using printf and enclose in double quotes +sanitize_variable() { + local raw="$1" + local escaped + if [[ "$raw" == \[* ]]; then + echo "One of your options is an array, skipping" + return + fi + printf -v escaped '%q' "$raw" + # Do not espace spaces + escaped="${escaped//\\ / }" + if [[ "$raw" == "$escaped" ]]; then + printf '%s' "$raw" + else + printf '%s' "$escaped" + fi +} + +for KEYS in "${arr[@]}"; do + # export key + VALUE=$(jq -r --raw-output ".\"$KEYS\"" "$JSONSOURCE") + # Check if the value is an array + if [[ "$VALUE" == \[* ]]; then + bashio::log.warning "One of your option is an array, skipping" + else + # Sanitize variable + VALUE=$(sanitize_variable "$VALUE") + # If value is empty, returns an empty value + if [[ -z "$VALUE" ]]; then + line="${KEYS}=''" + else + # Continue for single values + line="${KEYS}='${VALUE//\'/\'\\\'\'}'" + fi + # Check if secret + if [[ "${line}" == *"!secret "* ]]; then + echo "secret detected" + # Get argument + secret=${line#*secret } + # Remove trailing ' or " + secret="${secret%[\"\']}" + # Stop if secret file not mounted + if [[ "$SECRETSOURCE" == "false" ]]; then + bashio::log.warning "Homeassistant config not mounted, secrets are not supported" + continue + fi + # Check if single match + secretnum=$(sed -n "/$secret:/=" "$SECRETSOURCE") + [[ "$secretnum" == *' '* ]] && bashio::exit.nok "There are multiple matches for your password name. Please check your secrets.yaml file" + # Get text + secret=$(sed -n "/$secret:/p" "$SECRETSOURCE") + secret=${secret#*: } + line="${line%%=*}='$secret'" + VALUE="$secret" + fi + # text + if bashio::config.false "verbose" || [[ "${KEYS,,}" == *"pass"* ]]; then + bashio::log.blue "${KEYS}=******" + else + bashio::log.blue "$line" + fi + + ###################################### + # Export the variable to run scripts # + ###################################### + # shellcheck disable=SC2163 + export "$line" + # export to python + if command -v "python3" &> /dev/null; then + [ ! -f /env.py ] && echo "import os" > /env.py + # Escape \ + VALUEPY="${VALUE//\\/\\\\}" + # Avoid " and ' + VALUEPY="${VALUEPY//[\"\']/}" + echo "os.environ['${KEYS}'] = '$VALUEPY'" >> /env.py + python3 /env.py + fi + # set .env + echo "$line" >> /.env || true + # set /etc/environment + mkdir -p /etc + echo "$line" >> /etc/environment + # For non s6 + if cat /etc/services.d/*/*run* &> /dev/null; then sed -i "1a export $line" /etc/services.d/*/*run* 2> /dev/null; fi + if cat /etc/cont-init.d/*.sh &> /dev/null; then sed -i "1a export $line" /etc/cont-init.d/*.sh 2> /dev/null; fi + # For s6 + if [ -d /var/run/s6/container_environment ]; then printf "%s" "${VALUE}" > /var/run/s6/container_environment/"${KEYS}"; fi + echo "export ${KEYS}='${VALUE}'" >> ~/.bashrc + fi +done + +################ +# Set timezone # +################ +set +eu + +if [ -n "$TZ" ] && [ -f /etc/localtime ]; then + if [ -f /usr/share/zoneinfo/"$TZ" ]; then + echo "Timezone set from $(cat /etc/timezone) to $TZ" + ln -snf /usr/share/zoneinfo/"$TZ" /etc/localtime && echo "$TZ" > /etc/timezone + fi +fi diff --git a/.templates/00-local_mounts.sh b/.templates/00-local_mounts.sh index e67ff5d..ca21679 100644 --- a/.templates/00-local_mounts.sh +++ b/.templates/00-local_mounts.sh @@ -109,4 +109,4 @@ if bashio::config.has_value 'localdisks'; then ) done -fi \ No newline at end of file +fi diff --git a/.templates/00-smb_mounts.sh b/.templates/00-smb_mounts.sh new file mode 100644 index 0000000..5bc8782 --- /dev/null +++ b/.templates/00-smb_mounts.sh @@ -0,0 +1,357 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +# shellcheck disable= +set -e + +if ! bashio::supervisor.ping 2> /dev/null; then + bashio::log.blue "Disabled : please use another method" + exit 0 +fi + +bashio::log.notice "This script is used to mount remote smb/cifs/nfs shares. Instructions here : https://github.com/alexbelgium/hassio-addons/wiki/Mounting-remote-shares-in-Addons" + +#################### +# DEFINE FUNCTIONS # +#################### + +test_mount() { + + # Set initial test + MOUNTED=false + ERROR_MOUNT=false + + # Exit if not mounted + if ! mountpoint -q /mnt/"$diskname"; then + return 0 + fi + + # Exit if can't write + [[ -e "/mnt/$diskname/testaze" ]] && rm -r "/mnt/$diskname/testaze" + # shellcheck disable=SC2015 + mkdir "/mnt/$diskname/testaze" && touch "/mnt/$diskname/testaze/testaze" && rm -r "/mnt/$diskname/testaze" || ERROR_MOUNT=true + + # Only CIFS has the noserverino fallback + if [[ "$ERROR_MOUNT" == "true" && "$FSTYPE" == "cifs" ]]; then + # Test write permissions + if [[ "$MOUNTOPTIONS" == *"noserverino"* ]]; then + bashio::log.fatal "Disk is mounted, however unable to write in the shared disk. Please check UID/GID for permissions, and if the share is rw" + else + MOUNTOPTIONS="$MOUNTOPTIONS,noserverino" + echo "... testing with noserverino" + mount_drive "$MOUNTOPTIONS" + return 0 + fi + fi + + # Set correctly mounted bit + MOUNTED=true + return 0 + +} + +mount_drive() { + + # Define options + MOUNTED=true + MOUNTOPTIONS="$1" + + # Try mounting (type depends on detected FSTYPE) + if [[ "$FSTYPE" == "cifs" ]]; then + mount -t cifs -o "$MOUNTOPTIONS" "$disk" /mnt/"$diskname" 2> ERRORCODE || MOUNTED=false + elif [[ "$FSTYPE" == "nfs" ]]; then + mount -t nfs -o "$MOUNTOPTIONS" "$disk" /mnt/"$diskname" 2> ERRORCODE || MOUNTED=false + fi + + # Test if successful + if [[ "$MOUNTED" == "true" ]]; then + # shellcheck disable=SC2015 + test_mount + fi + +} + +#################### +# MOUNT NETWORK SHARES # +#################### + +if bashio::config.has_value 'networkdisks'; then + + # Alert message that it is a new code + if [[ "$(date +"%Y%m%d")" -lt "20240201" ]]; then + bashio::log.warning "------------------------" + bashio::log.warning "This is a new code, please report any issues on https://github.com/alexbelgium/hassio-addons" + bashio::log.warning "------------------------" + fi + + echo 'Mounting network share(s)...' + + #################### + # Define variables # + #################### + + # Set variables + MOREDISKS=$(bashio::config 'networkdisks') + USERNAME=$(bashio::config 'cifsusername') + PASSWORD=$(bashio::config 'cifspassword') + SMBVERS="" + SECVERS="" + CHARSET=",iocharset=utf8" + + # Clean data (keeps NFS entries intact) + MOREDISKS=${MOREDISKS// \/\//,\/\/} + MOREDISKS=${MOREDISKS//, /,} + MOREDISKS=${MOREDISKS// /"\040"} + + # Is domain set (CIFS only) + DOMAIN="" + DOMAINCLIENT="" + if bashio::config.has_value 'cifsdomain'; then + echo "... using domain $(bashio::config 'cifsdomain')" + DOMAIN=",domain=$(bashio::config 'cifsdomain')" + DOMAINCLIENT="--workgroup=$(bashio::config 'cifsdomain')" + fi + + # Is UID/GID set (used for CIFS mount options) + PUID=",uid=$(id -u)" + PGID=",gid=$(id -g)" + if bashio::config.has_value 'PUID' && bashio::config.has_value 'PGID'; then + echo "... using PUID $(bashio::config 'PUID') and PGID $(bashio::config 'PGID')" + PUID=",uid=$(bashio::config 'PUID')" + PGID=",gid=$(bashio::config 'PGID')" + fi + + ################## + # Mounting disks # + ################## + + # shellcheck disable=SC2086 + for disk in ${MOREDISKS//,/ }; do # Separate comma separated values + + # Clean name of network share + # shellcheck disable=SC2116,SC2001 + disk=$(echo $disk | sed "s,/$,,") # Remove / at end of name + disk="${disk//"\040"/ }" # replace \040 with space + + # Detect filesystem type by pattern (CIFS: //ip/share ; NFS: ip:/export[/path] or nfs://ip:/export[/path]) + FSTYPE="cifs" + if [[ "$disk" =~ ^nfs:// ]]; then + FSTYPE="nfs" + disk="${disk#nfs://}" + elif [[ "$disk" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:/.+ ]]; then + FSTYPE="nfs" + fi + + # Determine server for reachability checks + if [[ "$FSTYPE" == "cifs" ]]; then + server="$(echo "$disk" | grep -E -o "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+")" + else + server="${disk%%:*}" + fi + + diskname="$disk" + diskname="${diskname//\\//}" # replace \ with / + diskname="${diskname##*/}" # Get only last part of the name + MOUNTED=false + SMBVERS_FORCE="" + SECVERS_FORCE="" + + # Start + echo "... mounting ($FSTYPE) $disk" + + # Data validation + if [[ "$FSTYPE" == "cifs" ]]; then + if [[ ! "$disk" =~ ^.*+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[/]+.*+$ ]]; then + bashio::log.fatal "...... the structure of your \"networkdisks\" option : \"$disk\" doesn't seem correct, please use a structure like //123.12.12.12/sharedfolder,//123.12.12.12/sharedfolder2. If you don't use it, you can simply remove the text, this will avoid this error message in the future." + touch ERRORCODE + continue + fi + else + if [[ ! "$disk" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:/.+ ]]; then + bashio::log.fatal "...... invalid NFS path \"$disk\". Use a structure like 123.12.12.12:/export/path" + touch ERRORCODE + continue + fi + fi + + # Prepare mount point + mkdir -p /mnt/"$diskname" + chown root:root /mnt/"$diskname" + + # Quickly try to mount with defaults + if [[ "$FSTYPE" == "cifs" ]]; then + mount_drive "rw,file_mode=0775,dir_mode=0775,username=${USERNAME},password=${PASSWORD},nobrl${SMBVERS}${SECVERS}${PUID}${PGID}${CHARSET}${DOMAIN}" + elif [[ "$FSTYPE" == "nfs" ]]; then + mount_drive "rw,nfsvers=4.2,proto=tcp,hard,timeo=600,retrans=2" + fi + + # Deeper analysis if failed + if [ "$MOUNTED" = false ]; then + + if [[ "$FSTYPE" == "cifs" ]]; then + + # Does server exist (SMB port 445) + output="$(nmap -F $server -T5 -oG -)" + if ! echo "$output" | grep 445/open &> /dev/null; then + if echo "$output" | grep /open &> /dev/null; then + bashio::log.fatal "...... $server is reachable but SMB port not opened, stopping script" + touch ERRORCODE + continue + else + bashio::log.fatal "...... fatal : $server not reachable, is it correct" + touch ERRORCODE + continue + fi + else + echo "...... $server is confirmed reachable" + fi + + # Are credentials correct + OUTPUT="$(smbclient -t 2 -L "$disk" -U "$USERNAME"%"$PASSWORD" -c "exit" $DOMAINCLIENT 2>&1 || true)" + if echo "$OUTPUT" | grep -q "LOGON_FAILURE"; then + bashio::log.fatal "...... incorrect Username, Password, or Domain! Script will stop." + touch ERRORCODE + # Should there be a workgroup + if ! smbclient -t 2 -L $disk -N $DOMAINCLIENT -c "exit" &> /dev/null; then + bashio::log.fatal "...... perhaps a workgroup must be specified" + touch ERRORCODE + fi + continue + elif echo "$OUTPUT" | grep -q "tree connect failed" || echo "$OUTPUT" | grep -q "NT_STATUS_CONNECTION_DISCONNECTED"; then + echo "... using SMBv1" + bashio::log.warning "...... share reachable only with legacy SMBv1 (NT1) negotiation. Forcing SMBv1 options." + SMBVERS_FORCE=",vers=1.0" + SECVERS_FORCE=",sec=ntlm" + elif ! echo "$OUTPUT" | grep -q "Disk"; then + echo "... testing path" + bashio::log.fatal "...... no shares found. Invalid or inaccessible SMB path?" + else + echo "...... credentials are valid" + fi + + # Extracting SMB versions and normalize output + # shellcheck disable=SC2210,SC2094 + SMBVERS="$(nmap --script smb-protocols "$server" -p 445 2> 1 | awk '/ [0-9]/' | awk '{print $NF}' | cut -c -3 | sort -V | tail -n 1 || true)" + # Avoid : + SMBVERS="${SMBVERS/:/.}" + # Manage output + if [ -n "$SMBVERS" ]; then + case $SMBVERS in + "202" | "200" | "20") + SMBVERS="2.0" + ;; + 21) + SMBVERS="2.1" + ;; + 302) + SMBVERS="3.02" + ;; + 311) + SMBVERS="3.1.1" + ;; + "3.1") + echo "SMB 3.1 detected, converting to 3.0" + SMBVERS="3.0" + ;; + esac + echo "...... SMB version detected : $SMBVERS" + SMBVERS=",vers=$SMBVERS" + elif smbclient -t 2 -L "$server" -m NT1 -N $DOMAINCLIENT &> /dev/null; then + echo "...... SMB version : only SMBv1 is supported, this can lead to issues" + SECVERS=",sec=ntlm" + SMBVERS=",vers=1.0" + else + echo "...... SMB version : couldn't detect, default used" + SMBVERS="" + fi + + # Apply forced SMBv1 options when initial connection required NT1 fallback + if [[ -n "$SMBVERS_FORCE" ]]; then + if [[ -z "$SMBVERS" ]]; then + SMBVERS="$SMBVERS_FORCE" + fi + if [[ -z "$SECVERS" ]]; then + SECVERS="$SECVERS_FORCE" + fi + fi + + # Ensure the Samba client allows SMBv1 when those options are required + if [[ "${SMBVERS}${SMBVERS_FORCE}" == *"vers=1.0"* ]]; then + if [[ -f /etc/samba/smb.conf ]]; then + bashio::log.warning "...... enabling SMBv1 support in Samba client configuration" + sed -i '/\[global\]/!b;n;/client min protocol = NT1/!a\ + client min protocol = NT1' /etc/samba/smb.conf + fi + fi + + # Test with different security versions + ####################################### + for SECVERS in "$SECVERS" ",sec=ntlmv2" ",sec=ntlmssp" ",sec=ntlmsspi" ",sec=krb5i" ",sec=krb5" ",sec=ntlm" ",sec=ntlmv2i"; do + if [ "$MOUNTED" = false ]; then + mount_drive "rw,file_mode=0775,dir_mode=0775,username=${USERNAME},password=${PASSWORD},nobrl${SMBVERS}${SECVERS}${PUID}${PGID}${CHARSET}${DOMAIN}" + fi + done + + elif [[ "$FSTYPE" == "nfs" ]]; then + # Add NFS-specific port check (2049) similar to SMB (445) + output="$(nmap -F $server -T5 -oG -)" + if ! echo "$output" | grep -E '(2049|111)/open' &> /dev/null; then + bashio::log.fatal "...... $server is reachable but NFS ports not open" + continue + fi + # NFS fallback attempts: try common versions until one works + for NFVER in 4.2 4.1 4 3; do + if [ "$MOUNTED" = false ]; then + mount_drive "rw,nfsvers=${NFVER},proto=tcp" + fi + done + fi + fi + + # Messages + if [ "$MOUNTED" = true ]; then + + bashio::log.info "...... $disk successfully mounted to /mnt/$diskname with options ${MOUNTOPTIONS/$PASSWORD/XXXXXXXXXX}" + # Remove errorcode + if [ -f ERRORCODE ]; then + rm ERRORCODE + fi + + # Alert if smbv1 + if [[ "$FSTYPE" == "cifs" && "$MOUNTOPTIONS" == *"1.0"* ]]; then + bashio::log.warning "" + bashio::log.warning "Your smb system requires smbv1. This is an obsolete protocol. Please correct this to prevent issues." + bashio::log.warning "" + fi + + else + # Mounting failed messages + if [[ "$FSTYPE" == "cifs" ]]; then + bashio::log.fatal "Error, unable to mount $disk to /mnt/$diskname with username $USERNAME, $PASSWORD. Please check your remote share path, username, password, domain, try putting 0 in UID and GID" + bashio::log.fatal "Here is some debugging info :" + smbclient -t 2 -L $disk -U "$USERNAME%$PASSWORD" -c "exit" + SMBVERS="" + SECVERS="" + PUID="" + PGID="" + CHARSET="" + mount_drive "rw,file_mode=0775,dir_mode=0775,username=${USERNAME},password=${PASSWORD},nobrl${SMBVERS}${SECVERS}${PUID}${PGID}${CHARSET}${DOMAIN}" + elif [[ "$FSTYPE" == "nfs" ]]; then + bashio::log.fatal "Error, unable to mount NFS share $disk to /mnt/$diskname. Please check the export path and that NFS server allows this client (and NFSv4)." + # last-ditch try with very basic options + mount_drive "rw" + fi + + bashio::log.fatal "Error read : $(< ERRORCODE), addon will stop in 1 min" + + # clean folder + umount "/mnt/$diskname" 2> /dev/null || true + rmdir "/mnt/$diskname" || true + + # Stop addon + bashio::addon.stop + + fi + + done + +fi diff --git a/.templates/01-config_yaml.sh b/.templates/01-config_yaml.sh new file mode 100644 index 0000000..ec9d690 --- /dev/null +++ b/.templates/01-config_yaml.sh @@ -0,0 +1,202 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash + +################## +# INITIALIZATION # +################## + +# Disable if config not present +if [ ! -d /config ] || ! bashio::supervisor.ping 2> /dev/null; then + echo "..." + exit 0 +fi + +# Define slug +slug="${HOSTNAME/-/_}" +slug="${slug#*_}" + +# Check type of config folder +if [ ! -f /config/configuration.yaml ] && [ ! -f /config/configuration.json ]; then + # New config location + CONFIGLOCATION="/config" + CONFIGFILEBROWSER="/addon_configs/${HOSTNAME/-/_}/config.yaml" +else + # Legacy config location + CONFIGLOCATION="/config/addons_config/${slug}" + CONFIGFILEBROWSER="/homeassistant/addons_config/$slug/config.yaml" +fi + +# Default location +mkdir -p "$CONFIGLOCATION" || true +CONFIGSOURCE="$CONFIGLOCATION"/config.yaml + +# Is there a custom path +if bashio::config.has_value 'CONFIG_LOCATION'; then + CONFIGSOURCE=$(bashio::config "CONFIG_LOCATION") + if [[ "$CONFIGSOURCE" == *"."* ]]; then + CONFIGSOURCE=$(dirname "$CONFIGSOURCE") + fi + # If does not end by config.yaml, remove trailing slash and add config.yaml + if [[ "$CONFIGSOURCE" != *".yaml" ]]; then + CONFIGSOURCE="${CONFIGSOURCE%/}"/config.yaml + fi + # Check if config is located in an acceptable location + LOCATIONOK="" + for location in "/share" "/config" "/data"; do + if [[ "$CONFIGSOURCE" == "$location"* ]]; then + LOCATIONOK=true + fi + done + if [ -z "$LOCATIONOK" ]; then + bashio::log.red "Watch-out: your CONFIG_LOCATION values can only be set in /share, /config or /data (internal to addon). It will be reset to the default location: $CONFIGLOCATION/config.yaml" + CONFIGSOURCE="$CONFIGLOCATION"/config.yaml + fi +fi + +# Migrate if needed +if [[ "$CONFIGLOCATION" == "/config" ]]; then + # Migrate file + if [ -f "/homeassistant/addons_config/${slug}/config.yaml" ] && [ ! -L "/homeassistant/addons_config/${slug}" ]; then + echo "Migrating config.yaml to new config location" + mv "/homeassistant/addons_config/${slug}/config.yaml" /config/config.yaml + fi + # Migrate option + if [[ "$(bashio::config "CONFIG_LOCATION")" == "/config/addons_config"* ]] && [ -f /config/config.yaml ]; then + bashio::addon.option "CONFIG_LOCATION" "/config/config.yaml" + CONFIGSOURCE="/config/config.yaml" + fi +fi + +if [[ "$CONFIGSOURCE" != *".yaml" ]]; then + bashio::log.error "Something is going wrong in the config location, quitting" + exit 1 +fi + +# Permissions +if [[ "$CONFIGSOURCE" == *".yaml" ]]; then + echo "Setting permissions for the config.yaml directory" + mkdir -p "$(dirname "${CONFIGSOURCE}")" + chmod -R 755 "$(dirname "${CONFIGSOURCE}")" 2> /dev/null +fi + +#################### +# LOAD CONFIG.YAML # +#################### + +echo "" +bashio::log.green "Load environment variables from $CONFIGSOURCE if existing" +if [[ "$CONFIGSOURCE" == "/config"* ]]; then + bashio::log.green "If accessing the file with filebrowser it should be mapped to $CONFIGFILEBROWSER" +else + bashio::log.green "If accessing the file with filebrowser it should be mapped to $CONFIGSOURCE" +fi +bashio::log.green "---------------------------------------------------------" +bashio::log.notice "This script is used to export custom environment variables at start of the addon. Instructions here : https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon" +echo "" + +# Check if config file is there, or create one from template +if [ ! -f "$CONFIGSOURCE" ]; then + echo "... no config file, creating one from template. Please customize the file in $CONFIGSOURCE before restarting." + # Create folder + mkdir -p "$(dirname "${CONFIGSOURCE}")" + # Placing template in config + if [ -f /templates/config.yaml ]; then + # Use available template + cp /templates/config.yaml "$(dirname "${CONFIGSOURCE}")" + else + # Download template + TEMPLATESOURCE="https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/config.template" + curl -f -L -s -S "$TEMPLATESOURCE" --output "$CONFIGSOURCE" + fi +fi + +# Check if there are lines to read +cp "$CONFIGSOURCE" /tempenv +sed -i '/^#/d' /tempenv +sed -i '/^[[:space:]]*$/d' /tempenv +sed -i '/^$/d' /tempenv +echo "" >> /tempenv + +# Exit if empty +if [ ! -s /tempenv ]; then + bashio::log.green "... no env variables found, exiting" + exit 0 +fi + +# Check if yaml is valid +EXIT_CODE=0 +yamllint -d relaxed /tempenv &> ERROR || EXIT_CODE=$? +if [ "$EXIT_CODE" != 0 ]; then + cat ERROR + bashio::log.yellow "... config file has an invalid yaml format. Please check the file in $CONFIGSOURCE. Errors list above." +fi + +# converts yaml to variables +sed -i 's/: /=/' /tempenv + +# Look where secrets.yaml is located +SECRETSFILE="/config/secrets.yaml" +if [ ! -f "$SECRETSFILE" ]; then SECRETSFILE="/homeassistant/secrets.yaml"; fi + +while IFS= read -r line; do + # Skip empty lines + if [[ -z "$line" ]]; then + continue + fi + + # Check if secret + if [[ "$line" == *!secret* ]]; then + echo "Secret detected" + if [ ! -f "$SECRETSFILE" ]; then + bashio::log.fatal "Secrets file not found in $SECRETSFILE, $line skipped" + continue + fi + secret=$(echo "$line" | sed 's/.*!secret \(.*\)/\1/') + # Check if single match + secretnum=$(sed -n "/$secret:/=" "$SECRETSFILE") + if [[ $(echo "$secretnum" | grep -q ' ') ]]; then + bashio::exit.nok "There are multiple matches for your password name. Please check your secrets.yaml file" + fi + # Get text + secret_value=$(sed -n "/$secret:/s/.*: //p" "$SECRETSFILE") + line="${line%%=*}='$secret_value'" + fi + + # Data validation + if [[ "$line" =~ ^[^[:space:]]+.+[=].+$ ]]; then + # extract keys and values + KEYS="${line%%=*}" + VALUE="${line#*=}" + # Check if VALUE is quoted + #if [[ "$VALUE" != \"*\" ]] && [[ "$VALUE" != \'*\' ]]; then + # VALUE="\"$VALUE\"" + #fi + line="${KEYS}=${VALUE}" + export "$line" + # export to python + if command -v "python3" &> /dev/null; then + [ ! -f /env.py ] && echo "import os" > /env.py + # Escape single quotes in VALUE + VALUE_ESCAPED="${VALUE//\'/\'\"\'\"\'}" + echo "os.environ['${KEYS}'] = '${VALUE_ESCAPED}'" >> /env.py + python3 /env.py + fi + # set .env + echo "$line" >> /.env + # set environment + mkdir -p /etc + echo "$line" >> /etc/environment + # Export to scripts + if cat /etc/services.d/*/*run* &> /dev/null; then sed -i "1a export $line" /etc/services.d/*/*run* 2> /dev/null; fi + if cat /etc/cont-init.d/*run* &> /dev/null; then sed -i "1a export $line" /etc/cont-init.d/*run* 2> /dev/null; fi + # For s6 + if [ -d /var/run/s6/container_environment ]; then printf "%s" "${VALUE}" > /var/run/s6/container_environment/"${KEYS}"; fi + echo "export $line" >> ~/.bashrc + # Show in log + if ! bashio::config.false "verbose"; then bashio::log.blue "$line"; fi + else + bashio::log.red "Skipping line that does not follow the correct structure: $line" + fi +done < "/tempenv" + +rm /tempenv diff --git a/.templates/01-custom_script.sh b/.templates/01-custom_script.sh new file mode 100644 index 0000000..f647754 --- /dev/null +++ b/.templates/01-custom_script.sh @@ -0,0 +1,66 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e + +################## +# INITIALIZATION # +################## + +# Exit if /config is not mounted or HA not used +if [ ! -d /config ] || ! bashio::supervisor.ping 2> /dev/null; then + echo "..." + exit 0 +fi + +# Define slug +slug="${HOSTNAME/-/_}" +slug="${slug#*_}" + +# Check type of config folder +if [ ! -f /config/configuration.yaml ] && [ ! -f /config/configuration.json ]; then + # New config location + CONFIGLOCATION="/config" + CONFIGFILEBROWSER="/addon_configs/${HOSTNAME/-/_}/$slug.sh" +else + # Legacy config location + CONFIGLOCATION="/config/addons_autoscripts" + CONFIGFILEBROWSER="/homeassistant/addons_autoscripts/$slug.sh" +fi + +# Default location +mkdir -p "$CONFIGLOCATION" || true +CONFIGSOURCE="$CONFIGLOCATION/$slug.sh" + +bashio::log.notice "This script is used to run custom commands at start of the addon. Instructions here : https://github.com/alexbelgium/hassio-addons/wiki/Running-custom-scripts-in-Addons" +bashio::log.green "Execute $CONFIGFILEBROWSER if existing" + +# Download template if no script found and exit +if [ ! -f "$CONFIGSOURCE" ]; then + TEMPLATESOURCE="https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates/script.template" + curl -f -L -s -S "$TEMPLATESOURCE" --output "$CONFIGSOURCE" || true + exit 0 +fi + +# Convert scripts to linux +dos2unix "$CONFIGSOURCE" &> /dev/null || true +chmod +x "$CONFIGSOURCE" + +# Get current shebang, if not available use another +currentshebang="$(sed -n '1{s/^#![[:blank:]]*//p;q}' "$CONFIGSOURCE")" +if [ ! -f "${currentshebang%% *}" ]; then + for shebang in "/command/with-contenv bashio" "/usr/bin/env bashio" "/usr/bin/bashio" "/bin/bash" "/bin/sh"; do if [ -f "${shebang%% *}" ]; then break; fi; done + sed -i "s|$currentshebang|$shebang|g" "$CONFIGSOURCE" +fi + +# Check if there is actual commands +while IFS= read -r line; do + # Remove leading and trailing whitespaces + line="$(echo "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + + # Check if line is not empty and does not start with # + if [[ -n "$line" ]] && [[ ! "$line" =~ ^# ]]; then + bashio::log.green "... script found, executing" + /."$CONFIGSOURCE" + break + fi +done < "$CONFIGSOURCE" diff --git a/.templates/19-json_repair.sh b/.templates/19-json_repair.sh new file mode 100644 index 0000000..20bbb3f --- /dev/null +++ b/.templates/19-json_repair.sh @@ -0,0 +1,46 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e +# shellchek disable=SC2015 + +JSONTOCHECK='/config/transmission/settings.json' +JSONSOURCE='/defaults/settings.json' + +# If json already exists +if [ -f "${JSONTOCHECK}" ]; then + # Variables + echo "Checking settings.json format" + + # Check if json file valid or not + jq . -S "${JSONTOCHECK}" &> /dev/null && ERROR=false || ERROR=true + if [ "$ERROR" = true ]; then + bashio::log.fatal "Settings.json structure is abnormal, restoring options from scratch. Your old file is renamed as settings.json_old" + mv "${JSONSOURCE}" "${JSONSOURCE}"_old + cp "${JSONSOURCE}" "${JSONTOCHECK}" + exit 0 + fi + + # Get the default keys from the original file + mapfile -t arr < <(jq -r 'keys[]' "${JSONSOURCE}") + + # Check if all keys are still there, or add them + # spellcheck disable=SC2068 + for KEYS in "${arr[@]}"; do + # Check if key exists + KEYSTHERE=$(jq "has(\"${KEYS}\")" "${JSONTOCHECK}") + if [ "$KEYSTHERE" != "true" ]; then + #Fetch initial value + JSONSOURCEVALUE=$(jq -r ".\"$KEYS\"" "${JSONSOURCE}") + #Add key + sed -i "3 i\"${KEYS}\": \"${JSONSOURCEVALUE}\"," "${JSONTOCHECK}" + # Message + bashio::log.warning "${KEYS} was missing from your settings.json, it was added with the default value ${JSONSOURCEVALUE}" + fi + done + + # Show structure in a nice way + jq . -S "${JSONTOCHECK}" | cat > temp.json && mv temp.json "${JSONTOCHECK}" + + # Message + bashio::log.info "Your settings.json was checked and seems perfectly normal!" +fi diff --git a/.templates/90-disable_ingress.sh b/.templates/90-disable_ingress.sh new file mode 100644 index 0000000..a0be50b --- /dev/null +++ b/.templates/90-disable_ingress.sh @@ -0,0 +1,35 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e + +# Disables ingress and sets a default index + +# Disable Ingress +if bashio::config.true "ingress_disabled"; then + bashio::log.warning "Ingress is disabled. You'll need to connect using ip:port" + + # Adapt ingress.conf + sed -i "/root/d" /etc/nginx/servers/ingress.conf + sed -i "/proxy_pass/i root /etc;" /etc/nginx/servers/ingress.conf + sed -i "/proxy_pass/i try_files '' /ingress.html =404;" /etc/nginx/servers/ingress.conf + sed -i "/proxy_pass/d" /etc/nginx/servers/ingress.conf + + # Create index.html + touch /etc/ingress.html + cat > /etc/ingress.html << EOF + + +
++ Ingress was disabled by the user. Please connect using ip:port or + re-enable in the addons options. +
+ + + +EOF +fi diff --git a/.templates/90-dns_set.sh b/.templates/90-dns_set.sh new file mode 100644 index 0000000..a0b67e9 --- /dev/null +++ b/.templates/90-dns_set.sh @@ -0,0 +1,43 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e + +############### +# DNS SETTING # +############### + +# Avoid usage of local dns such as adguard home or pihole\n" + +if bashio::config.has_value 'DNS_server'; then + # Define variables + DNSSERVER=$(bashio::config 'DNS_server') + DNS="" + DNSLIST="" + + # Get DNS servers + # shellcheck disable=SC2086 + for server in ${DNSSERVER//,/ }; do # Separate comma separated values + # Only add DNS if successful + if ping -c 1 "$server" &> /dev/null; then + DNS="${DNS}nameserver $server\n" + DNSLIST="$server $DNSLIST" + else + bashio::log.warning "DNS $server was requested but can't be pinged. It won't be used" + fi + done + + # Only add DNS if there are DNS set + # shellcheck disable=SC2236 + if [[ -n "${DNS:-}" ]]; then + # Write resolv.conf + # shellcheck disable=SC2059 + printf "${DNS}" > /etc/resolv.conf + chmod 644 /etc/resolv.conf + bashio::log.info "DNS SERVERS set to $DNSLIST" + else + bashio::log.warning "No valid DNS were found. Using default router (or HA) dns servers." + fi + +else + bashio::log.info "DNS Servers option empty. Using default router (or HA) dns servers." +fi diff --git a/.templates/91-silent.sh b/.templates/91-silent.sh new file mode 100644 index 0000000..da13793 --- /dev/null +++ b/.templates/91-silent.sh @@ -0,0 +1,14 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e + +############### +# SILENT MODE # +############### + +if bashio::config.true 'silent'; then + APPEND=' > /dev/null' + sed -i '$s|$|'"$APPEND"'|' /etc/services.d/*/run &> /dev/null || true + sed -i '$s|$|'"$APPEND"'|' /etc/cont-init.d/*/*run* &> /dev/null || true + bashio::log.info 'Silent mode activated, all logs from emby server are hidden. Disable this option if you need to troubleshoot the addon.' +fi diff --git a/.templates/91-universal_graphic_drivers.sh b/.templates/91-universal_graphic_drivers.sh new file mode 100644 index 0000000..a05b7fd --- /dev/null +++ b/.templates/91-universal_graphic_drivers.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bashio +set -e + +if bashio::config.has_value "graphic_driver"; then + + # Origin : https://github.com/wumingjieno1/photoprism-test/blob/main/scripts/dist/install-gpu.sh + # abort if not executed as root + if [[ $(id -u) != "0" ]]; then + # shellcheck disable=SC2128 + bashio::log.fatal "Error: Run $(basename "${BASH_SOURCE}") as root" 1>&2 + exit 1 + fi + + # Get installer type + if [ -f /usr/bin/apt ]; then + bashio::log.info "... Distribution detected : Debian/Ubuntu" + apt-get install -yqq software-properties-common > /dev/null + add-apt-repository ppa:kisak/kisak-mesa > /dev/null + apt-get update > /dev/null + apt-get install -yqq mesa + elif [ -f /usr/bin/apk ]; then + bashio::log.info "... Distribution detected : Alpine" + fi + + # Detect GPU + # shellcheck disable=SC2207 + GPU_DETECTED=($(lshw -c display -json 2> /dev/null | jq -r '.[].configuration.driver')) + bashio::log.info "... GPU detected: ${GPU_DETECTED[*]}" + graphic_driver="" + + # Get arch type + BUILD_ARCH="$(uname -m)" + case "$BUILD_ARCH" in + amd64 | AMD64 | x86_64 | x86-64) + BUILD_ARCH=amd64 + ;; + + arm64 | ARM64 | aarch64) + BUILD_ARCH=arm64 + graphic_driver=aarch64_rpi + ;; + + arm | ARM | aarch | armv7l | armhf) + bashio::log.fatal "Unsupported Machine Architecture: $BUILD_ARCH" 1>&2 + exit 1 + ;; + + *) + bashio::log.fatal "Unsupported Machine Architecture: $BUILD_ARCH" 1>&2 + exit 1 + ;; + esac + bashio::log.info "... architecture detected: ${BUILD_ARCH}" + + #graphic_driver="$(bashio::config "graphic_driver")" + case "$graphic_driver" in + x64_AMD) + if [[ "$BUILD_ARCH" != amd64 ]]; then bashio::log.fatal "Wrong architecture, $graphic_driver doesn't support $BUILD_ARCH"; fi + [ -f /usr/bin/apt ] && DOCKER_MODS=linuxserver/mods:jellyfin-amd && run_mods > /dev/null && bashio::log.green "... done" + [ -f /usr/bin/apk ] && apk add --no-cache mesa-dri-classic mesa-vdpau-gallium linux-firmware-radeon > /dev/null && bashio::log.green "... done" + ;; + + x64_NVIDIA) + if [[ "$BUILD_ARCH" != amd64 ]]; then bashio::log.fatal "Wrong architecture, $graphic_driver doesn't support $BUILD_ARCH"; fi + [ -f /usr/bin/apk ] && apk add --no-cache linux-firmware-radeon > /dev/null && bashio::log.green "... done" + [ -f /usr/bin/apt ] && apt-get -yqq install libcuda1 libnvcuvid1 libnvidia-encode1 nvidia-opencl-icd nvidia-vdpau-driver nvidia-driver-libs nvidia-kernel-dkms libva2 vainfo libva-wayland2 > /dev/null && bashio::log.green "... done" + ;; + + x64_Intel) + if [[ "$BUILD_ARCH" != amd64 ]]; then bashio::log.fatal "Wrong architecture, $graphic_driver doesn't support $BUILD_ARCH"; fi + [ -f /usr/bin/apk ] && apk add --no-cache opencl mesa-dri-gallium mesa-vulkan-intel mesa-dri-intel intel-media-driver > /dev/null && bashio::log.green "... done" + [ -f /usr/bin/apt ] && DOCKER_MODS=linuxserver/mods:jellyfin-opencl-intel && run_mods && apt-get -yqq install intel-opencl-icd intel-media-va-driver-non-free i965-va-driver-shaders mesa-va-drivers libmfx1 libva2 vainfo libva-wayland2 > /dev/null && bashio::log.green "... done" + ;; + + aarch64_rpi) + if [[ "$BUILD_ARCH" != arm64 ]]; then bashio::log.fatal "Wrong architecture, $graphic_driver doesn't support $BUILD_ARCH"; fi + bashio::log.info "Installing Rpi graphic drivers" + [ -f /usr/bin/apk ] && apk add --no-cache mesa-dri-vc4 mesa-dri-swrast mesa-gbm xf86-video-fbdev > /dev/null && bashio::log.green "... done" + [ -f /usr/bin/apt ] && apt-get -yqq install libgles2-mesa libgles2-mesa-dev xorg-dev > /dev/null && bashio::log.green "... done" + ;; + + esac + + # Main run logic + run_mods() { + echo "[mod-init] Attempting to run Docker Modification Logic" + for DOCKER_MOD in $(echo "${DOCKER_MODS}" | tr '|' '\n'); do + # Support alternative endpoints + if [[ ${DOCKER_MOD} == ghcr.io/* ]] || [[ ${DOCKER_MOD} == linuxserver/* ]]; then + DOCKER_MOD="${DOCKER_MOD#ghcr.io/*}" + ENDPOINT="${DOCKER_MOD%%:*}" + USERNAME="${DOCKER_MOD%%/*}" + REPO="${ENDPOINT#*/}" + TAG="${DOCKER_MOD#*:}" + if [[ ${TAG} == "${DOCKER_MOD}" ]]; then + TAG="latest" + fi + FILENAME="${USERNAME}.${REPO}.${TAG}" + AUTH_URL="https://ghcr.io/token?scope=repository%3A${USERNAME}%2F${REPO}%3Apull" + MANIFEST_URL="https://ghcr.io/v2/${ENDPOINT}/manifests/${TAG}" + BLOB_URL="https://ghcr.io/v2/${ENDPOINT}/blobs/" + MODE="ghcr" + else + ENDPOINT="${DOCKER_MOD%%:*}" + USERNAME="${DOCKER_MOD%%/*}" + REPO="${ENDPOINT#*/}" + TAG="${DOCKER_MOD#*:}" + if [[ ${TAG} == "${DOCKER_MOD}" ]]; then + TAG="latest" + fi + FILENAME="${USERNAME}.${REPO}.${TAG}" + AUTH_URL="https://auth.docker.io/token?service=registry.docker.io&scope=repository:${ENDPOINT}:pull" + MANIFEST_URL="https://registry-1.docker.io/v2/${ENDPOINT}/manifests/${TAG}" + BLOB_URL="https://registry-1.docker.io/v2/${ENDPOINT}/blobs/" + MODE="dockerhub" + fi + # 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 + echo "[mod-init] ${DOCKER_MOD} is banned from use due to reported abuse aborting mod logic" + return + else + echo "[mod-init] You have chosen to run banned mods ${DOCKER_MOD} will be applied" + fi + fi + done + echo "[mod-init] Applying ${DOCKER_MOD} files to container" + # Get Dockerhub token for api operations + TOKEN="$( + curl -f --retry 10 --retry-max-time 60 --retry-connrefused \ + --silent \ + --header 'GET' \ + "${AUTH_URL}" \ + | jq -r '.token' + )" + # Determine first and only layer of image + SHALAYER=$(get_blob_sha "${MODE}" "${TOKEN}" "${MANIFEST_URL}") + # Check if we have allready applied this layer + if [[ -f "/${FILENAME}" ]] && [[ "${SHALAYER}" == "$(cat /"${FILENAME}")" ]]; then + echo "[mod-init] ${DOCKER_MOD} at ${SHALAYER} has been previously applied skipping" + else + # Download and extract layer to / + curl -f --retry 10 --retry-max-time 60 --retry-connrefused \ + --silent \ + --location \ + --request GET \ + --header "Authorization: Bearer ${TOKEN}" \ + "${BLOB_URL}${SHALAYER}" -o \ + /modtarball.tar.xz + mkdir -p /tmp/mod + tar xzf /modtarball.tar.xz -C /tmp/mod + if [[ -d /tmp/mod/etc/s6-overlay ]]; then + 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 + fi + shopt -s dotglob + cp -R /tmp/mod/* / + shopt -u dotglob + rm -rf /tmp/mod + rm -rf /modtarball.tar.xz + echo "${SHALAYER}" > "/${FILENAME}" + echo "[mod-init] ${DOCKER_MOD} applied to container" + fi + done + } + +fi diff --git a/.templates/99-custom_script.sh b/.templates/99-custom_script.sh new file mode 100644 index 0000000..c3c8bce --- /dev/null +++ b/.templates/99-custom_script.sh @@ -0,0 +1,10 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e + +CONFIGSOURCE=$(bashio::config "CONFIG_LOCATION") +CONFIGSOURCE="$(dirname "${CONFIGSOURCE}")" + +if [ -f "$CONFIGSOURCE"/script.sh ]; then + "$CONFIGSOURCE"./script.sh +fi diff --git a/.templates/bashio-standalone.sh b/.templates/bashio-standalone.sh new file mode 100644 index 0000000..ff4da02 --- /dev/null +++ b/.templates/bashio-standalone.sh @@ -0,0 +1,444 @@ +# /usr/local/lib/bashio-standalone.sh +# shellcheck shell=bash +# Minimal bashio compatibility layer for running Home Assistant add-ons +# as standalone containers (no Supervisor). It overrides common bashio::* +# functions to source config from ENV (and optionally a JSON file). +# Load it conditionally in your entry script when supervisor isn't reachable. + +# -------- internals ---------------------------------------------------------- + +# Whether to emit ANSI colors (disabled if not a TTY) +if [ -t 1 ]; then + _BASHIO_COLOR=1 +else + _BASHIO_COLOR=0 +fi + +_bashio_color() { + # $1=name; returns ANSI sequence or empty + if [ "$_BASHIO_COLOR" != "1" ]; then return 0; fi + case "$1" in + blue) printf '\033[34m' ;; + green) printf '\033[32m' ;; + yellow) printf '\033[33m' ;; + red) printf '\033[31m' ;; + magenta) printf '\033[35m' ;; + reset) printf '\033[0m' ;; + esac +} + +_bashio_log() { + # $1=color name, $2...=msg + local c="$1" + shift + local pre + pre="$(_bashio_color "$c")" + local rst + rst="$(_bashio_color reset)" + printf '%s%s%s\n' "$pre" "$*" "$rst" +} + +# Optional JSON options source (single flat object or nested). +# Set STANDALONE_OPTIONS_JSON to a path (e.g., /data/options.json). +# If jq is present, keys can be fetched as .key or .nested.key +_bashio_json_get() { + # $1=key (dot.notation). echoes value or empty; returns 0 always + local key="${1:-}" + local file="${STANDALONE_OPTIONS_JSON:-}" + if [ -z "$file" ] || [ ! -f "$file" ] || ! command -v jq > /dev/null 2>&1; then + return 0 + fi + # jq -r returns "null" for missing; convert to empty + local val + val="$(jq -er --arg k "$key" '. as $r | getpath(($k|split("."))) // empty' "$file" 2> /dev/null || true)" + [ "$val" = "null" ] && val="" + printf '%s' "$val" +} + +# Map a bashio "key" to an env var name. +# Order tried: +# 1) exact as-is +# 2) uppercase exact +# 3) dot->underscore, dash->underscore (upper & lower) +# 4) with prefixes: CFG_, CONFIG_, ADDON_, OPTION_, OPT_ +_bashio_env_get() { + # $1=key + local key="${1:-}" + [ -z "$key" ] && return 0 + + local variants=() + variants+=("$key") + variants+=("$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')") + variants+=("$(printf '%s' "$key" | tr '.' '_' | tr '-' '_')") + variants+=("$(printf '%s' "$key" | tr '.' '_' | tr '-' '_' | tr '[:lower:]' '[:upper:]')") + + local prefixes=("" + "CFG_" "CONFIG_" "ADDON_" "OPTION_" "OPT_") + + local v p name + for v in "${variants[@]}"; do + for p in "${prefixes[@]}"; do + name="${p}${v}" + if [ -n "${!name+x}" ]; then + printf '%s' "${!name}" + return 0 + fi + done + done +} + +# Helper: true/false parsing +_bashio_is_true() { + # $1=value + case "${1:-}" in + 1 | true | TRUE | yes | YES | on | On) return 0 ;; + *) return 1 ;; + esac +} + +# Net wait using /dev/tcp (POSIX bash) with a timeout +_bashio_tcp_wait() { + # $1=host $2=port $3=timeout(s, default 30) + local host="$1" port="$2" to="${3:-30}" + local start now + start="$(date +%s)" + while :; do + if exec 3<> "/dev/tcp/${host}/${port}" 2> /dev/null; then + exec 3>&- 3<&- + return 0 + fi + now="$(date +%s)" + if [ $((now - start)) -ge "$to" ]; then + return 1 + fi + sleep 1 + done +} + +# -------- logs --------------------------------------------------------------- + +bashio::log.blue() { _bashio_log blue "$*"; } +bashio::log.green() { _bashio_log green "$*"; } +bashio::log.yellow() { _bashio_log yellow "$*"; } +bashio::log.red() { _bashio_log red "$*"; } +bashio::log.magenta() { _bashio_log magenta "$*"; } + +# compatibility aliases often used +bashio::log.info() { bashio::log.blue "$@"; } +bashio::log.warning() { bashio::log.yellow "$@"; } +bashio::log.error() { bashio::log.red "$@"; } +bashio::log.debug() { printf '%s\n' "$*"; } + +# -------- supervisor & addon meta ------------------------------------------- + +# In standalone, "ping" always fails unless forced +bashio::supervisor.ping() { + if _bashio_is_true "${STANDALONE_FORCE_SUPERVISOR_PING:-}"; then + return 0 + fi + return 1 +} + +# Add-on metadata (use env or sensible defaults) +bashio::addon.name() { printf '%s' "${ADDON_NAME:-Standalone container}"; } +bashio::addon.description() { printf '%s' "${ADDON_DESCRIPTION:-Running without Home Assistant Supervisor}"; } +bashio::addon.version() { printf '%s' "${BUILD_VERSION:-1.0}"; } +bashio::addon.version_latest() { printf '%s' "${ADDON_VERSION_LATEST:-${BUILD_VERSION:-1.0}}"; } +bashio::addon.update_available() { + if [ "${ADDON_VERSION_LATEST:-}" != "" ] && [ "${ADDON_VERSION_LATEST:-}" != "${BUILD_VERSION:-}" ]; then + printf '%s' "true" + return 0 + fi + printf '%s' "false" +} +bashio::addon.ingress_port() { printf '%s' "${ADDON_INGRESS_PORT:-}"; } +bashio::addon.ingress_entry() { printf '%s' "${ADDON_INGRESS_ENTRY:-}"; } +bashio::addon.ip_address() { printf '%s' "${ADDON_IP_ADDRESS:-}"; } + +# Ports: +# - numeric arg "8080" -> env PORT_8080 or ADDON_PORT_8080, falling back to the number +# - non-numeric "WEB_PORT" -> resolve as config/env key +bashio::addon.port() { + local arg="${1:-}" + if [[ "$arg" =~ ^[0-9]+$ ]]; then + local v + v="$(_bashio_env_get "PORT_${arg}")" + [ -z "$v" ] && v="$(_bashio_env_get "ADDON_PORT_${arg}")" + printf '%s' "${v:-$arg}" + else + printf '%s' "$(_bashio_env_get "$arg")" + fi +} + +# -------- system info -------------------------------------------------------- + +bashio::info.operating_system() { + if [ -r /etc/os-release ]; then + . /etc/os-release + printf '%s' "${PRETTY_NAME:-${NAME:-Linux}}" + else + printf '%s' "Linux" + fi +} +bashio::info.arch() { uname -m; } +bashio::info.machine() { uname -m; } +bashio::info.homeassistant() { printf '%s' "standalone"; } +bashio::info.supervisor() { printf '%s' "standalone"; } + +# -------- config ------------------------------------------------------------- + +# Primary getter: +# 1) ENV (several name variants/prefixes) +# 2) JSON file via STANDALONE_OPTIONS_JSON (jq required) +bashio::config() { + local key="${1:-}" + local v + v="$(_bashio_env_get "$key")" + if [ -z "$v" ]; then + v="$(_bashio_json_get "$key")" + fi + printf '%s' "${v:-}" +} + +bashio::config.has_value() { + local k="$1" + [ -n "$(bashio::config "$k")" ] +} +bashio::config.true() { + local k="$1" + _bashio_is_true "$(bashio::config "$k")" +} + +# Some add-ons call "require.ssl" (noop by default) +bashio::config.require.ssl() { printf '%s' "${REQUIRE_SSL:-true}"; } + +# -------- variables & fs helpers -------------------------------------------- + +bashio::var.true() { _bashio_is_true "${1:-}"; } +bashio::var.has_value() { [ -n "${1:-}" ]; } + +bashio::fs.directory_exists() { [ -d "$1" ]; } + +# -------- network/services --------------------------------------------------- + +# Wait for TCP service: bashio::net.wait_for host port [timeout] +bashio::net.wait_for() { + local host="$1" port="$2" to="${3:-30}" + _bashio_tcp_wait "$host" "$port" "$to" +} + +# Discovery stubs; map to common env names, or JSON: +# Usage patterns seen: +# bashio::services "mqtt" "host" +# bashio::services "mysql" "port" +bashio::services() { + local svc="${1:-}" key="${2:-}" + [ -z "$svc" ] && return 0 + local upper svc_upper var v + upper="$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')" + svc_upper="$(printf '%s' "$svc" | tr '[:lower:]' '[:upper:]')" + + # Common mappings + case "$svc_upper:$upper" in + MQTT:HOST) var="MQTT_HOST" ;; + MQTT:PORT) var="MQTT_PORT" ;; + MQTT:USERNAME) var="MQTT_USER" ;; + MQTT:PASSWORD) var="MQTT_PASSWORD" ;; + MQTT:TLS) var="MQTT_TLS" ;; + MYSQL:HOST | MARIADB:HOST) var="DB_HOST" ;; + MYSQL:PORT | MARIADB:PORT) var="DB_PORT" ;; + MYSQL:USERNAME | MARIADB:USERNAME) var="DB_USER" ;; + MYSQL:PASSWORD | MARIADB:PASSWORD) var="DB_PASSWORD" ;; + MYSQL:DATABASE | MARIADB:DATABASE) var="DB_NAME" ;; + *) var="${svc_upper}_${upper}" ;; + esac + + v="$(_bashio_env_get "$var")" + if [ -z "$v" ] && [ -n "${STANDALONE_OPTIONS_JSON:-}" ]; then + v="$(_bashio_json_get "services.${svc}.${key}")" + [ -z "$v" ] && v="$(_bashio_json_get "${svc}.${key}")" + fi + printf '%s' "${v:-}" +} + +# ----- extras for broader compatibility -------------------------------------- + +# Simple cache (used by add-ons & bashio itself) +_BASHIO_CACHE_DIR="${BASHIO_CACHE_DIR:-/tmp/.bashio}" +mkdir -p "$_BASHIO_CACHE_DIR" + +bashio::cache.exists() { [ -f "$_BASHIO_CACHE_DIR/${1}.cache" ]; } +bashio::cache.get() { [ -f "$_BASHIO_CACHE_DIR/${1}.cache" ] && cat "$_BASHIO_CACHE_DIR/${1}.cache"; } +bashio::cache.set() { + mkdir -p "$_BASHIO_CACHE_DIR" + printf '%s' "${2:-}" > "$_BASHIO_CACHE_DIR/${1}.cache" +} + +# Filesystem helpers frequently used +bashio::fs.file_exists() { [ -f "$1" ]; } +bashio::fs.directory_exists() { [ -d "$1" ]; } # already defined earlier; keep if present +bashio::fs.file_contains() { + local f="$1" p="$2" + [ -f "$f" ] && grep -q -- "$p" "$f" +} + +# jq wrapper (some add-ons call bashio::jq) +bashio::jq() { command -v jq > /dev/null 2>&1 && jq "$@"; } + +# env presence (even if empty) used by config.exists +_bashio_env_has() { + local key="$1" p v name + [ -z "$key" ] && return 1 + local variants=( + "$key" + "$(printf '%s' "$key" | tr '.' '_')" + "$(printf '%s' "$key" | tr '.' '_' | tr '[:lower:]' '[:upper:]')" + "$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')" + ) + for v in "${variants[@]}"; do + for p in "" "CFG_" "CONFIG_" "ADDON_" "OPTION_" "OPT_"; do + name="${p}${v}" + if [ -n "${!name+x}" ]; then # defined, even if empty + printf '%s' "$name" + return 0 + fi + done + done + return 1 +} + +# config.exists : key is present (env or JSON), even if value is empty +bashio::config.exists() { + local key="$1" file="${STANDALONE_OPTIONS_JSON:-}" + _bashio_env_has "$key" && return 0 + if [ -n "$file" ] && command -v jq > /dev/null 2>&1; then + jq -e --arg k "$key" 'haspath(($k|split(".")))' "$file" > /dev/null 2>&1 + return $? + fi + return 1 +} + +# addon.option : write/delete option in JSON when possible; fallback no-op/env +bashio::addon.option() { + local key="$1" value="${2-__BASHIO_UNSET__}" file="${STANDALONE_OPTIONS_JSON:-}" + if [ -n "$file" ] && command -v jq > /dev/null 2>&1; then + local tmp + tmp="$(mktemp)" + if [ "$value" = "__BASHIO_UNSET__" ]; then + jq --arg k "$key" 'delpath(($k|split(".")))' "$file" > "$tmp" && mv "$tmp" "$file" + else + jq --arg k "$key" --arg v "$value" 'setpath(($k|split(".")); $v)' "$file" > "$tmp" && mv "$tmp" "$file" + fi + return 0 + fi + # Fallbacks: export as env or treat delete as no-op + if [ "$value" != "__BASHIO_UNSET__" ]; then + export "$(printf '%s' "$key" | tr '.' '_' | tr '-' '_')"="$value" + fi +} + +# services.available : check if we can resolve at least a host for the service +bashio::services.available() { + local svc="$1" host + host="$(bashio::services "$svc" "host")" + [ -n "$host" ] +} + +# var helpers +bashio::var.false() { ! _bashio_is_true "${1:-}"; } +bashio::var.has_value() { [ -n "${1:-}" ]; } # already present; keep if defined + +# exits used by many add-ons +bashio::exit.ok() { exit 0; } +bashio::exit.nok() { + local m="${1:-}" + [ -n "$m" ] && bashio::log.red "$m" + exit 1 +} + +# core.check : Supervisor does a config check; allow an overridable command +# Set STANDALONE_CORE_CHECK_CMD="hass --script check_config -c /config" to enable +bashio::core.check() { + if [ -n "${STANDALONE_CORE_CHECK_CMD:-}" ]; then + eval "$STANDALONE_CORE_CHECK_CMD" + else + return 0 + fi +} + +# --- improvements & extra shims --------------------------------------------- + +# Respect NO_COLOR and dumb terminals +if [ -n "${NO_COLOR:-}" ] || [ "${TERM:-}" = "dumb" ]; then + _BASHIO_COLOR=0 +fi + +# net.wait_for: prefer nc if available, fallback to /dev/tcp +_bashio_tcp_wait_nc() { + # $1=host $2=port $3=timeout(s) + command -v nc > /dev/null 2>&1 || return 1 + local host="$1" port="$2" to="${3:-30}" + # BusyBox and OpenBSD nc differ; cover both styles + nc -z -w "$to" "$host" "$port" 2> /dev/null || nc -z "$host" "$port" 2> /dev/null +} +bashio::net.wait_for() { + local host="$1" port="$2" to="${3:-30}" + _bashio_tcp_wait_nc "$host" "$port" "$to" && return 0 + _bashio_tcp_wait "$host" "$port" "$to" +} + +# DNS helper: bashio::dns.host