⬆️ Updates Hassio Bash library to the latest

This commit is contained in:
Franck Nijhof 2018-08-13 00:23:56 +02:00 committed by Franck Nijhof
parent e6adb872e7
commit c2c7d91d90
14 changed files with 609 additions and 87 deletions

View file

@ -15,11 +15,14 @@ ENV \
# Copy root filesystem
COPY rootfs /
# Copy yq
ARG BUILD_ARCH=amd64
COPY bin/yq_${BUILD_ARCH} /usr/bin/yq
# Set shell
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Install base system
ARG BUILD_ARCH=amd64
RUN \
apt-get update \
\

BIN
base/bin/yq_aarch64 Executable file

Binary file not shown.

BIN
base/bin/yq_amd64 Executable file

Binary file not shown.

BIN
base/bin/yq_armhf Executable file

Binary file not shown.

BIN
base/bin/yq_i386 Executable file

Binary file not shown.

View file

@ -33,6 +33,8 @@ source "${__LIB_DIR}/modules/config.sh"
source "${__LIB_DIR}/modules/jq.sh"
#shellcheck source=base/rootfs/usr/lib/hassio-addons/modules/log.sh
source "${__LIB_DIR}/modules/log.sh"
#shellcheck source=base/rootfs/usr/lib/hassio-addons/modules/pwned.sh
source "${__LIB_DIR}/modules/pwned.sh"
#shellcheck source=base/rootfs/usr/lib/hassio-addons/modules/string.sh
source "${__LIB_DIR}/modules/string.sh"

View file

@ -17,6 +17,8 @@ readonly HASS_API_ENDPOINT='http://hassio'
source "${__LIB_DIR}/modules/api/addons.sh"
# shellcheck source=base/rootfs/usr/lib/hassio-addons/modules/api/hardware.sh
source "${__LIB_DIR}/modules/api/hardware.sh"
# shellcheck source=base/rootfs/usr/lib/hassio-addons/modules/api/hassos.sh
source "${__LIB_DIR}/modules/api/hassos.sh"
# shellcheck source=base/rootfs/usr/lib/hassio-addons/modules/api/homeassistant.sh
source "${__LIB_DIR}/modules/api/homeassistant.sh"
# shellcheck source=base/rootfs/usr/lib/hassio-addons/modules/api/host.sh
@ -72,8 +74,8 @@ hass.api.call() {
hass.log.debug "Requested API resource: ${HASS_API_ENDPOINT}${resource}"
hass.log.debug "API HTTP Response code: ${status}"
hass.log.debug "API Response: ${response}"
hass.log.debug "API Response: ${response}"
if [[ "${status}" -eq 401 ]]; then
hass.log.error "Unable to authenticate with the API, permission denied"
return "${EX_NOK}"

View file

@ -400,20 +400,6 @@ hass.api.addons.info.privileged() {
hass.api.addons.info "${addon}" ".privileged // empty"
}
# ------------------------------------------------------------------------------
# Returns the current seccomp state of this add-on
#
# Arguments:
# $1 Add-on slug
# Returns:
# Seccomp state: disable, default or profile
# ------------------------------------------------------------------------------
hass.api.addons.info.seccomp() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.addons.info "${addon}" ".seccomp"
}
# ------------------------------------------------------------------------------
# Returns the current apparmor state of this add-on
#
@ -568,6 +554,34 @@ hass.api.addons.info.gpio() {
hass.api.addons.info "${addon}" ".gpio // false"
}
# ------------------------------------------------------------------------------
# Returns whether or not this add-on can access the devicetree
#
# Arguments:
# $1 Add-on slug
# Returns:
# Whether or not this add-on can access the devicetree
# ------------------------------------------------------------------------------
hass.api.addons.info.devicetree() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.addons.info "${addon}" ".devicetree // false"
}
# ------------------------------------------------------------------------------
# Returns whether or not this add-on can access the Docker socket
#
# Arguments:
# $1 Add-on slug
# Returns:
# Whether or not this add-on can access the Docker socket
# ------------------------------------------------------------------------------
hass.api.addons.info.docker_api() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.addons.info "${addon}" ".docker_api // false"
}
# ------------------------------------------------------------------------------
# Returns whether or not this add-on can access an audio device
#
@ -638,34 +652,6 @@ hass.api.addons.info.discovery() {
hass.api.addons.info "${addon}" ".discovery // empty"
}
# ------------------------------------------------------------------------------
# Install an add-on onto your Hass.io instance
#
# Arguments:
# $1 Add-on slug
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.addons.install() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.call POST "/addons/${addon}/install"
}
# ------------------------------------------------------------------------------
# Uninstall an add-on from your Hass.io instance
#
# Arguments:
# $1 Add-on slug
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.addons.uninstall() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.call POST "/addons/${addon}/uninstall"
}
# ------------------------------------------------------------------------------
# Starts an add-on
#
@ -695,17 +681,31 @@ hass.api.addons.stop() {
}
# ------------------------------------------------------------------------------
# Restarts an add-on
# Install an add-on onto your Hass.io instance
#
# Arguments:
# $1 Add-on slug
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.addons.restart() {
hass.api.addons.install() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.call POST "/addons/${addon}/restart"
hass.api.call POST "/addons/${addon}/install"
}
# ------------------------------------------------------------------------------
# Uninstall an add-on from your Hass.io instance
#
# Arguments:
# $1 Add-on slug
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.addons.uninstall() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.call POST "/addons/${addon}/uninstall"
}
# ------------------------------------------------------------------------------
@ -722,20 +722,6 @@ hass.api.addons.update() {
hass.api.call POST "/addons/${addon}/update"
}
# ------------------------------------------------------------------------------
# Rebuilds an add-on locally
#
# Arguments:
# $1 Add-on slug
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.addons.rebuild() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.call POST "/addons/${addon}/rebuild"
}
# ------------------------------------------------------------------------------
# Returns the logs created by an add-on
#
@ -750,6 +736,34 @@ hass.api.addons.logs() {
hass.api.call GET "/addons/${addon}/logs" true
}
# ------------------------------------------------------------------------------
# Restarts an add-on
#
# Arguments:
# $1 Add-on slug
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.addons.restart() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.call POST "/addons/${addon}/restart"
}
# ------------------------------------------------------------------------------
# Rebuilds an add-on locally
#
# Arguments:
# $1 Add-on slug
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.addons.rebuild() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.call POST "/addons/${addon}/rebuild"
}
# ------------------------------------------------------------------------------
# Checks if there is an update available for an add-on
#
@ -815,7 +829,7 @@ hass.api.addons.stats.cpu_percent() {
hass.api.addons.stats.memory_usage() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.addons.stats "${addon}" ".memory_usage"
hass.api.addons.stats "${addon}" ".memory_usage"
}
# ------------------------------------------------------------------------------
@ -829,7 +843,7 @@ hass.api.addons.stats.memory_usage() {
hass.api.addons.stats.memory_limit() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.addons.stats "${addon}" ".memory_limit"
hass.api.addons.stats "${addon}" ".memory_limit"
}
# ------------------------------------------------------------------------------
@ -843,7 +857,7 @@ hass.api.addons.stats.memory_limit() {
hass.api.addons.stats.network_tx() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.addons.stats "${addon}" ".network_tx"
hass.api.addons.stats "${addon}" ".network_tx"
}
# ------------------------------------------------------------------------------
@ -857,7 +871,7 @@ hass.api.addons.stats.network_tx() {
hass.api.addons.stats.network_rx() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.addons.stats "${addon}" ".network_rx"
hass.api.addons.stats "${addon}" ".network_rx"
}
# ------------------------------------------------------------------------------
@ -871,7 +885,7 @@ hass.api.addons.stats.network_rx() {
hass.api.addons.stats.blk_read() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.addons.stats "${addon}" ".blk_read"
hass.api.addons.stats "${addon}" ".blk_read"
}
# ------------------------------------------------------------------------------
@ -885,5 +899,5 @@ hass.api.addons.stats.blk_read() {
hass.api.addons.stats.blk_write() {
local addon=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.addons.stats "${addon}" ".blk_write"
hass.api.addons.stats "${addon}" ".blk_write"
}

View file

@ -0,0 +1,128 @@
#!/usr/bin/env bash
# ==============================================================================
# Community Hass.io Add-ons: Bash functions library
#
# Provides access to the API functions of Hass.io: HassOS
# ==============================================================================
# ==============================================================================
# FUNCTIONS
# ==============================================================================
# ------------------------------------------------------------------------------
# List all available information about the HassOS host system
#
# Arguments:
# None
# Returns:
# JSON object with HassOS information
# ------------------------------------------------------------------------------
hass.api.hassos.info() {
local filter=${1:-}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.call GET /hassos/info false "${filter}"
}
# ------------------------------------------------------------------------------
# Returns the version of HassOS
#
# Arguments:
# None
# Returns:
# Version
# ------------------------------------------------------------------------------
hass.api.hassos.info.version() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.hassos.info ".version"
}
# ------------------------------------------------------------------------------
# Returns the CLI version of HassOS
#
# Arguments:
# None
# Returns:
# CLI version
# ------------------------------------------------------------------------------
hass.api.hassos.info.version_cli() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.hassos.info ".version_cli"
}
# ------------------------------------------------------------------------------
# Returns the latest version of HassOS
#
# Arguments:
# None
# Returns:
# Latest version
# ------------------------------------------------------------------------------
hass.api.hassos.info.version_latest() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.hassos.info ".version_latest"
}
# ------------------------------------------------------------------------------
# Returns the latest CLI version of HassOS
#
# Arguments:
# None
# Returns:
# Latest CLI version
# ------------------------------------------------------------------------------
hass.api.hassos.info.version_cli_latest() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.hassos.info ".version_cli_latest"
}
# ------------------------------------------------------------------------------
# Returns the board running HassOS
#
# Arguments:
# None
# Returns:
# The board
# ------------------------------------------------------------------------------
hass.api.hassos.info.board() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.hassos.info ".board"
}
# ------------------------------------------------------------------------------
# Updates HassOS to the latest version
#
# Arguments:
# None
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.hassos.update() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.call POST /hassos/update
}
# ------------------------------------------------------------------------------
# Updates HassOS CLI to the latest version
#
# Arguments:
# None
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.hassos.update.cli() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.call POST /hassos/update/cli
}
# ------------------------------------------------------------------------------
# Load HassOS host configuration from USB stick
#
# Arguments:
# None
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.hassos.config.sync() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.call POST /hassos/config/sync
}

View file

@ -49,6 +49,19 @@ hass.api.homeassistant.info.last_version() {
hass.api.homeassistant.info ".last_version"
}
# ------------------------------------------------------------------------------
# Returns the machine info runningHome Assistant
#
# Arguments:
# None
# Returns:
# Machine
# ------------------------------------------------------------------------------
hass.api.homeassistant.info.machine() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.homeassistant.info ".machine"
}
# ------------------------------------------------------------------------------
# Returns the Docker image of Home Assistant
#
@ -254,7 +267,7 @@ hass.api.homeassistant.stats.cpu_percent() {
# ------------------------------------------------------------------------------
hass.api.homeassistant.stats.memory_usage() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.homeassistant.stats ".memory_usage"
hass.api.homeassistant.stats ".memory_usage"
}
# ------------------------------------------------------------------------------
@ -267,7 +280,7 @@ hass.api.homeassistant.stats.memory_usage() {
# ------------------------------------------------------------------------------
hass.api.homeassistant.stats.memory_limit() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.homeassistant.stats ".memory_limit"
hass.api.homeassistant.stats ".memory_limit"
}
# ------------------------------------------------------------------------------
@ -280,7 +293,7 @@ hass.api.homeassistant.stats.memory_limit() {
# ------------------------------------------------------------------------------
hass.api.homeassistant.stats.network_tx() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.homeassistant.stats ".network_tx"
hass.api.homeassistant.stats ".network_tx"
}
# ------------------------------------------------------------------------------
@ -293,7 +306,7 @@ hass.api.homeassistant.stats.network_tx() {
# ------------------------------------------------------------------------------
hass.api.homeassistant.stats.network_rx() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.homeassistant.stats ".network_rx"
hass.api.homeassistant.stats ".network_rx"
}
# ------------------------------------------------------------------------------
@ -306,7 +319,7 @@ hass.api.homeassistant.stats.network_rx() {
# ------------------------------------------------------------------------------
hass.api.homeassistant.stats.blk_read() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.homeassistant.stats ".blk_read"
hass.api.homeassistant.stats ".blk_read"
}
# ------------------------------------------------------------------------------
@ -319,5 +332,5 @@ hass.api.homeassistant.stats.blk_read() {
# ------------------------------------------------------------------------------
hass.api.homeassistant.stats.blk_write() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.homeassistant.stats ".blk_write"
hass.api.homeassistant.stats ".blk_write"
}

View file

@ -114,6 +114,19 @@ hass.api.host.info.deployment() {
hass.api.host.info ".deployment"
}
# ------------------------------------------------------------------------------
# Returns the cpe of the system
#
# Arguments:
# None
# Returns:
# Version
# ------------------------------------------------------------------------------
hass.api.host.info.deployment() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.host.info ".cpe"
}
# ------------------------------------------------------------------------------
# Returns the version of the software running on the host
#
@ -191,3 +204,58 @@ hass.api.host.reload() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.call POST /host/reload
}
# ------------------------------------------------------------------------------
# Returns host services
#
# Arguments:
# None
# Returns:
# List of host services.
# ------------------------------------------------------------------------------
hass.api.host.services() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.call GET /host/services false ".services[]"
}
# ------------------------------------------------------------------------------
# Stops a host service
#
# Arguments:
# $1 unit name
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.host.service.stop() {
local unit=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.call POST "/host/service/${unit}/stop"
}
# ------------------------------------------------------------------------------
# Starts a host service
#
# Arguments:
# $1 unit name
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.host.service.start() {
local unit=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.call POST "/host/service/${unit}/start"
}
# ------------------------------------------------------------------------------
# Reloads a host service
#
# Arguments:
# $1 unit name
# Returns:
# None
# ------------------------------------------------------------------------------
hass.api.host.service.reload() {
local unit=${1}
hass.log.trace "${FUNCNAME[0]}" "$@"
hass.api.call POST "/host/service/${unit}/reload"
}

View file

@ -59,16 +59,16 @@ hass.api.supervisor.info.last_version() {
}
# ------------------------------------------------------------------------------
# Returns whether or not the Supervisor is on the beta channel
# Returns the stability channel of the setup.
#
# Arguments:
# None
# Returns:
# Whether or not the Supervisor is on the beta channel
# The currently in use stability channel
# ------------------------------------------------------------------------------
hass.api.supervisor.info.beta_channel() {
hass.api.supervisor.info.channel() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.supervisor.info ".beta_channel // false"
hass.api.supervisor.info ".channel // false"
}
# ------------------------------------------------------------------------------
@ -212,7 +212,7 @@ hass.api.supervisor.stats.cpu_percent() {
# ------------------------------------------------------------------------------
hass.api.supervisor.stats.memory_usage() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.supervisor.stats ".memory_usage"
hass.api.supervisor.stats ".memory_usage"
}
# ------------------------------------------------------------------------------
@ -225,7 +225,7 @@ hass.api.supervisor.stats.memory_usage() {
# ------------------------------------------------------------------------------
hass.api.supervisor.stats.memory_limit() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.supervisor.stats ".memory_limit"
hass.api.supervisor.stats ".memory_limit"
}
# ------------------------------------------------------------------------------
@ -238,7 +238,7 @@ hass.api.supervisor.stats.memory_limit() {
# ------------------------------------------------------------------------------
hass.api.supervisor.stats.network_tx() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.supervisor.stats ".network_tx"
hass.api.supervisor.stats ".network_tx"
}
# ------------------------------------------------------------------------------
@ -251,7 +251,7 @@ hass.api.supervisor.stats.network_tx() {
# ------------------------------------------------------------------------------
hass.api.supervisor.stats.network_rx() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.supervisor.stats ".network_rx"
hass.api.supervisor.stats ".network_rx"
}
# ------------------------------------------------------------------------------
@ -264,7 +264,7 @@ hass.api.supervisor.stats.network_rx() {
# ------------------------------------------------------------------------------
hass.api.supervisor.stats.blk_read() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.supervisor.stats ".blk_read"
hass.api.supervisor.stats ".blk_read"
}
# ------------------------------------------------------------------------------
@ -277,5 +277,5 @@ hass.api.supervisor.stats.blk_read() {
# ------------------------------------------------------------------------------
hass.api.supervisor.stats.blk_write() {
hass.log.trace "${FUNCNAME[0]}"
hass.api.supervisor.stats ".blk_write"
hass.api.supervisor.stats ".blk_write"
}

View file

@ -30,6 +30,11 @@ hass.config.get() {
return "${EX_OK}"
fi
if hass.config.is_secret "${key}"; then
hass.config.get_secret "${key}"
return "${EX_OK}"
fi
if hass.jq.is_string "${ADDON_CONFIG_PATH}" ".${key}"; then
hass.jq "${ADDON_CONFIG_PATH}" ".${key} // empty"
return "${EX_OK}"
@ -49,16 +54,95 @@ hass.config.get() {
if hass.jq.is_object "${ADDON_CONFIG_PATH}" ".${key}"; then
if hass.jq.has_value "${ADDON_CONFIG_PATH}" ".${key}"; then
hass.jq "${ADDON_CONFIG_PATH}" ".${key}{}"
hass.jq "${ADDON_CONFIG_PATH}" ".${key}[]"
fi
return "${EX_OK}"
fi
if hass.jq.is_number "${ADDON_CONFIG_PATH}" ".${key}"; then
hass.jq "${ADDON_CONFIG_PATH}" ".${key}"
return "${EX_OK}"
fi
return "${EX_NOK}"
}
# ------------------------------------------------------------------------------
# Gets a configuration option value by getting it from secrets.yaml
#
# Arguments:
# $1 Key of the config option
# Returns:
# Value of the key in the referenced to the secrets file
# ------------------------------------------------------------------------------
hass.config.get_secret() {
local key=${1}
local secret
local value
hass.log.trace "${FUNCNAME[0]}:" "$@"
if ! hass.directory_exists "/config"; then
hass.log.error "This add-on does not support secrets!"
return "${EX_NOK}"
fi
if ! hass.file_exists "/config/secrets.yaml"; then
hass.log.error "A secret was requested, but could not find a secrets.yaml"
return "${EX_NOK}"
fi
if ! hass.config.is_secret "${key}"; then
hass.log.error "The requested secret does not reference the secrets.yaml"
return "${EX_NOK}"
fi
secret=$(hass.jq "${ADDON_CONFIG_PATH}" ".${key} // empty")
secret="${secret#'!secret '}"
value=$(yq read "/config/secrets.yaml" "${secret}" )
if [[ "${value}" = "null" ]]; then
hass.log.error "Secret ${secret} not found in secrets.yaml file."
return "${EX_NOK}"
fi
echo "${value}"
return "${EX_OK}"
}
# ------------------------------------------------------------------------------
# Checks if a password is safe to use, using IHaveBeenPwned
#
# Arguments:
# $1 Key of the config option
# Returns:
# None
# ------------------------------------------------------------------------------
hass.config.is_safe_password() {
local key=${1}
local password
hass.log.trace "${FUNCNAME[0]}:" "$@"
# If the password is safe, we'll accept it anyways.
password=$(hass.config.get "${key}")
if hass.pwned.is_safe_password "${password}"; then
return "${EX_OK}"
fi
# If the bypass is not configured, we'll fail.
if ! hass.config.exists "i_like_to_be_pwned"; then
return "${EX_NOK}"
fi
# If the bypass is enabled, we'll return OK.
if hass.config.true "i_like_to_be_pwned"; then
hass.log.warning "Have I Been Pwned bypass enabled."
return "${EX_OK}"
fi
# If we reach this point, we'll just fail.
return "${EX_NOK}"
}
@ -78,6 +162,12 @@ hass.config.exists() {
return "${EX_NOK}"
fi
if hass.config.is_secret "${key}" \
&& ! hass.config.get_secret "${key}" > /dev/null;
then
return "${EX_NOK}"
fi
return "${EX_OK}"
}
@ -91,12 +181,25 @@ hass.config.exists() {
# ------------------------------------------------------------------------------
hass.config.has_value() {
local key=${1}
local value
hass.log.trace "${FUNCNAME[0]}:" "$@"
if ! hass.jq.has_value "${ADDON_CONFIG_PATH}" ".${key}"; then
return "${EX_NOK}"
fi
if hass.config.is_secret "${key}"; then
# Could not retrieve secret
if ! value=$(hass.config.get_secret "${key}"); then
return "${EX_NOK}"
fi
# Resolved secret does not contain a value
if ! hass.has_value "${value}"; then
return "${EX_NOK}"
fi
fi
return "${EX_OK}"
}
@ -145,3 +248,28 @@ hass.config.false() {
return "${EX_NOK}"
}
# ------------------------------------------------------------------------------
# Checks if a configuration option is refering to a secret
#
# Arguments:
# $1 Key of the config option
# Returns:
# None
# ------------------------------------------------------------------------------
hass.config.is_secret() {
local key=${1}
hass.log.trace "${FUNCNAME[0]}:" "$@"
if ! hass.jq.is_string "${ADDON_CONFIG_PATH}" ".${key}"; then
return "${EX_NOK}"
fi
if [[
"$(hass.jq "${ADDON_CONFIG_PATH}" ".${key} // empty")" != '!secret '*
]]; then
return "${EX_NOK}"
fi
return "${EX_OK}"
}

View file

@ -0,0 +1,164 @@
#!/usr/bin/env bash
# ==============================================================================
# Community Hass.io Add-ons: Bash functions library
#
# Provides interface with the password checks from HaveIBeenPwned.com
# ==============================================================================
# ==============================================================================
# GLOBALS
# ==============================================================================
readonly HIBP_ENDPOINT='https://api.pwnedpasswords.com/range'
declare -A CACHE
# ==============================================================================
# FUNCTIONS
# ==============================================================================
# ------------------------------------------------------------------------------
# Checks if a given password is safe to use
#
# Arguments:
# $1 The password to check
# Returns:
# None
# ------------------------------------------------------------------------------
function hass.pwned.is_safe_password () {
local password="${1}"
local occurances
hass.log.trace "${FUNCNAME[0]}" "<REDACTED PASSWORD>"
if ! occurances=$(hass.pwned.call "${password}"); then
hass.log.warning "Could not check password, assuming it is safe."
return "${EX_OK}"
fi
if [[ "${occurances}" -ne 0 ]]; then
return "${EX_NOK}"
fi
return "${EX_OK}"
}
# ------------------------------------------------------------------------------
# Gets the number of occurances of the password in the list.
#
# Arguments:
# $1 The password to check
# Returns:
# Number of occurance.
# ------------------------------------------------------------------------------
function hass.pwned.occurances() {
local password="${1}"
local occurances
hass.log.trace "${FUNCNAME[0]}" "<REDACTED PASSWORD>"
if ! occurances=$(hass.pwned.call "${password}"); then
occurances="0"
fi
echo "${occurances}"
return "${EX_OK}"
}
# ------------------------------------------------------------------------------
# Makes a call to the Have I Been Pwned password database
#
# Arguments:
# $1 The password to check
# Returns:
# Number of occurances
# ------------------------------------------------------------------------------
function hass.pwned.call() {
local password="${1}"
local response
local status
local hibp_hash
local count
hass.log.trace "${FUNCNAME[0]}" "${password//./x}"
# Do not check empty password
if ! hass.has_value "${password}"; then
hass.log.warning 'Cannot check empty password against HaveIBeenPwned.'
return "${EX_OK}"
fi
# Has the password
password=$(echo -n "${password}" \
| sha1sum \
| tr '[:lower:]' '[:upper:]' \
| awk -F' ' '{ print $1 }'
)
hass.log.debug "Password SHA1: ${password}"
# Check if response is cached
if [[ "${CACHE[${password}]+isset}" ]]; then
echo "${CACHE[${password}]}"
return "${EX_OK}"
fi
# Check with have I Been Powned, only send the first 5 chars of the hash
if ! response=$(curl \
--silent \
--show-error \
--write-out '\n%{http_code}' \
--request GET \
"${HIBP_ENDPOINT}/${password:0:5}"
); then
hass.log.debug "${response}"
hass.log.error "Something went wrong contacting the HIBP API"
return "${EX_NOK}"
fi
status=${response##*$'\n'}
response=${response%$status}
hass.log.debug "Requested API resource: ${HIBP_ENDPOINT}/${password:0:5}"
hass.log.debug "API HTTP Response code: ${status}"
hass.log.trace "API Response: ${response}"
if [[ "${status}" -eq 429 ]]; then
hass.log.error "HIBP Rate limit exceeded."
return "${EX_NOK}"
fi
if [[ "${status}" -eq 503 ]]; then
hass.log.error "HIBP Service unavailable."
return "${EX_NOK}"
fi
if [[ "${status}" -ne 200 ]]; then
hass.log.error "Unknown HIBP HTTP error occured."
return "${EX_NOK}"
fi
# Check the list of returned hashes for a match
for hibp_hash in ${response}; do
if [[ "${password:5:35}" == "${hibp_hash%%:*}" ]]; then
# Found a match! This is bad :(
count=$(echo "${hibp_hash#*:}" | tr -d '\r')
hass.log.warning \
"Password is in the Have I Been Pwned database!"
hass.log.warning \
"Password appeared ${count} times!"
echo "${count}"
# Store response in case it is asked again
CACHE[${password}]="${count}"
# Well, at least the execution of this function succeeded.
return "${EX_OK}"
fi
done
# Password was not found
echo "0"
CACHE[${password}]="0"
hass.log.info "Password is NOT in the Have I Been Pwned database! Nice!"
return "${EX_OK}"
}