Skip to content

Instantly share code, notes, and snippets.

@robvanoostenrijk
Last active February 14, 2024 16:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robvanoostenrijk/0978f3f919a540cb0b280a6978cbe8aa to your computer and use it in GitHub Desktop.
Save robvanoostenrijk/0978f3f919a540cb0b280a6978cbe8aa to your computer and use it in GitHub Desktop.
Prepare FreeBSD Jail for Home Assistant
#!/bin/sh
# Based on: https://github.com/tprelog/iocage-homeassistant/issues/64
install_packages() {
echo "[i] Installing required packages for Home Assistant"
pkg install -y \
autoconf \
bash \
ca_root_nss \
cmake \
curl \
ffmpeg \
gcc \
git \
gmake \
mariadb1011-client \
libjpeg-turbo \
libxml2 \
libxslt \
openblas \
pkgconf \
rust \
sqlite3 \
wget \
zip
}
install_python() {
# Python version to install
PYTHON_RELEASE=$(fetch -qo - https://www.python.org/downloads/source/ | sed -nr 's/^.*Latest Python.*([0-9]+.[0-9]+.[0-9]+).*$/\1/p')
echo "[i] Installing Python: ${PYTHON_RELEASE}"
cd /root
fetch -o - https://www.python.org/ftp/python/${PYTHON_RELEASE}/Python-${PYTHON_RELEASE}.tar.xz | tar -xJ
cd /root/Python-${PYTHON_RELEASE}/
./configure --disable-test-modules --enable-optimizations
make -j $(nproc)
make altinstall
rm -f -R /root/Python-${PYTHON_RELEASE}
}
install_homeassistant() {
rm -f -R /usr/local/share/homeassistant
mkdir -p /usr/local/share/homeassistant
echo "[i] Installing Home Assistant"
cd /usr/local/share/homeassistant
/usr/local/bin/bash -s <<-'EOF'
set -x
/usr/local/bin/python3.12 -m venv .
source bin/activate
pip install --upgrade pip
python -m pip install wheel
pip install homeassistant
# Optional Database drivers
pip install mysqlclient
#pip install psycopg2-binary
# webrtc-noise-gain: https://github.com/rhasspy/webrtc-noise-gain/issues/6#issuecomment-1836810982
echo "[i] webrtc-noise-gain"
pip install git+https://github.com/rhasspy/webrtc-noise-gain.git
# numpy-v1.26.0: https://github.com/numpy/numpy/issues/24873#issuecomment-1753093585
echo "[i] numpy v1.26.0 for google-cloud-texttospeech"
cd /root
git clone --recurse-submodules --branch v1.26.0 https://github.com/numpy/numpy.git numpy-git
cd numpy-git
git cherry-pick 040ed2d
cd ..
pip install numpy-git/
rm -f -R /root/numpy-git
# Optional: grpcio for google-cloud-texttospeech
echo "[i] grpcio for google-cloud-texttospeech"
cd /root
fetch -o - https://files.pythonhosted.org/packages/7d/6d/919fd5886882c066122e69fbd938c1df2dc0aa22ab8de3e047c6aff5ac58/grpcio-1.60.1.tar.gz | tar xz
cd /root/grpcio-1.60.1
# Apply FreeBSD patches for grpcio
fetch -o - https://raw.githubusercontent.com/freebsd/freebsd-ports/e8761ae4485b80b6cda12ace5fa669697c91065f/devel/py-grpcio/files/patch-src_core_tsi_ssl__transport__security.cc | patch -p0
fetch -o - https://raw.githubusercontent.com/freebsd/freebsd-ports/e8761ae4485b80b6cda12ace5fa669697c91065f/devel/py-grpcio/files/patch-third__party_abseil-cpp_absl_base_internal_sysinfo.cc | patch -p0
fetch -o - https://raw.githubusercontent.com/freebsd/freebsd-ports/e8761ae4485b80b6cda12ace5fa669697c91065f/devel/py-grpcio/files/patch-third__party_abseil-cpp_absl_time_internal_cctz_src_time__zone__format.cc | patch -p0
fetch -o - https://raw.githubusercontent.com/freebsd/freebsd-ports/e8761ae4485b80b6cda12ace5fa669697c91065f/devel/py-grpcio/files/patch-third__party_boringssl-with-bazel_src_include_openssl_base.h | patch -p0
patch -p0 <<-'EOF_PATCH'
--- third_party/abseil-cpp/absl/status/status.cc.orig
+++ third_party/abseil-cpp/absl/status/status.cc
@@ -29,6 +29,19 @@
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
+#ifndef ENOSTR
+#define ENOSTR 9924 /* Device not a stream */
+#endif
+#ifndef ETIME
+#define ETIME 9935 /* Timer expired */
+#endif
+#ifndef ENODATA
+#define ENODATA 9919 /* No data available */
+#endif
+#ifndef ENOSR
+#define ENOSR 9922 /* Out of streams resources */
+#endif
+
namespace absl {
ABSL_NAMESPACE_BEGIN
EOF_PATCH
cd /root
pip install --use-pep517 grpcio-1.60.1/
rm -f -R /root/grpcio-1.60.1
EOF
}
install_packages
install_python
install_homeassistant
sysrc homeassistant_config_dir="/home/homeassistant"
sysrc homeassistant_enable="yes"
sysrc homeassistant_log_file="/var/log/ha/homeassistant.log"
sysrc homeassistant_log_rotate_days="1"
sysrc homeassistant_path="${PATH}:/usr/local/bin"
sysrc homeassistant_python="/usr/local/bin/python3.12"
sysrc homeassistant_ssl_bundle="/usr/local/share/certs/ca-bundle.crt"
sysrc homeassistant_tz="Asia/Dubai"
sysrc homeassistant_user="homeassistant"
sysrc homeassistant_venv="/usr/local/share/homeassistant"
#!/bin/sh
#
# PROVIDE: homeassistant
# REQUIRE: LOGIN mysql
# KEYWORD: shutdown
#
# VERSION: 20220713-mod
#
# homeassistant_enable: Enable the Home Assistant Core service.
# Default: "NO"
# Enable: sysrc homeassistant_enable="YES"
# Reset: sysrc -x homeassistant_enable
#
# homeassistant_user: The user account used to run the homeassistant daemon.
# Default: "homeassistant"
#
# homeassistant_group: The group account used to run the homeassistant daemon.
# Default: The primary group of the ${homeassistant_user}
#
# homeassistant_user_dir: Path to directory, where ".cache/pip" will be located. This may also be the
# location for the user's files and ${homeassistant_config_dir}.
# Default: The HOME directory for the ${homeassistant_user}
# Alternate: If HOME is not set or does not exist -- ${homeassistant_venv}
#
# homeassistant_config_dir: Path to directory, where the Home Assistant "configuration.yaml" is located.
# Default: ${homeassistant_user_dir}/.homeassistant"
# Alternate: If HOME is not set or does not exist -- "/usr/local/etc/homeassistant"
#
# homeassistant_venv: Path to directory, where the Home Assistant Core virtualenv is located or will be created.
# https://github.com/home-assistant/architecture/blob/master/adr/0016-home-assistant-core.md
# Default: "/usr/local/share/homeassistant"
#
# homeassistant_python: Set a supported version of Python to be used when creating the virtualenv for Home Assistant Core.
# https://github.com/home-assistant/architecture/blob/master/adr/0016-home-assistant-core.md#supported-python-versions
# After changing the Python version, you must recreate the virtualenv for the updated version to take effect.
# Default: "NOT_SET"
#
name=homeassistant
rcvar=${name}_enable
. /etc/rc.subr && load_rc_config ${name}
: "${homeassistant_enable:="NO"}"
: "${homeassistant_rc_debug:="NO"}"
: "${homeassistant_user:="homeassistant"}"
: "${homeassistant_python:="NOT_SET"}"
: "${homeassistant_venv:="/usr/local/share/homeassistant"}"
: "${homeassistant_safe_mode:="NO"}"
: "${homeassistant_debug:="NO"}"
: "${homeassistant_skip_pip:="NO"}"
: "${homeassistant_verbose:="NO"}"
: "${homeassistant_color_log:="YES"}"
: "${homeassistant_restart_delay:=1}"
if [ ! "$(id ${homeassistant_user} 2>/dev/null)" ]; then
err 1 "user not found: ${homeassistant_user}"
else
: "${homeassistant_group:="$(id -gn ${homeassistant_user})"}"
HOME="$(getent passwd "${homeassistant_user}" | cut -d: -f6)"
fi
if [ -z "${HOME}" ] || [ ! -d "${HOME}" ] || [ "${HOME}" == "/nonexistent" ] || [ "${HOME}" == "/var/empty" ]; then
: "${homeassistant_config_dir:="/usr/local/etc/${name}"}"
: "${homeassistant_user_dir:="${homeassistant_venv}"}"
export HOME="${homeassistant_user_dir}"
else
: "${homeassistant_user_dir:="${HOME}"}"
: "${homeassistant_config_dir:="${homeassistant_user_dir}/.${name}"}"
fi
[ -n "${homeassistant_cpath:-}" ] && export CPATH="${homeassistant_cpath}"
[ -n "${homeassistant_library_path:-}" ] && export LIBRARY_PATH="${homeassistant_library_path}"
[ -n "${homeassistant_path:-}" ] && export PATH="${homeassistant_path}"
[ -n "${homeassistant_ssl_bundle:-}" ] && export REQUESTS_CA_BUNDLE="${homeassistant_ssl_bundle}"
[ -n "${homeassistant_tz:-}" ] && export TZ="${homeassistant_tz}"
umask "${homeassistant_umask:-022}"
logfile="/var/log/${name}_daemon.log"
pidfile="/var/run/${name}_daemon.pid"
pidfile_child="/var/run/${name}.pid"
command="/usr/sbin/daemon"
extra_commands="check_config ensure_config upgrade install reinstall logs script test"
homeassistant_chdir="${HOME}"
homeassistant_precmd() {
local _srv_ _own_ _msg_
local _venv_="${homeassistant_venv}"
local _user_="${homeassistant_user}"
if [ ! -d "${_venv_}" ]; then
_msg_="${_venv_} not found"
elif [ ! -f "${_venv_}/bin/activate" ]; then
_msg_="${_venv_}/bin/activate is not found"
elif [ ! -x "${_srv_:="${_venv_}/bin/hass"}" ]; then
_msg_="${_srv_} is not found or is not executable"
elif [ "${_own_:="$(stat -f '%Su' ${_srv_})"}" != ${_user_} ]; then
warn "${_srv_} is not owned by ${_user_}"
_msg_="${_srv_} is currently owned by ${_own_}"
else
HA_CMD="${_srv_}"
cd "${_venv_}" || err 1 "cd ${_venv_}"
return 0
fi
err 1 "${_msg_}"
}
start_precmd=${name}_prestart
homeassistant_prestart() {
homeassistant_precmd \
&& install -g "${homeassistant_group}" -m 664 -o ${homeassistant_user} -- /dev/null "${logfile}" \
&& install -g "${homeassistant_group}" -m 664 -o ${homeassistant_user} -- /dev/null "${pidfile}" \
&& install -g "${homeassistant_group}" -m 664 -o ${homeassistant_user} -- /dev/null "${pidfile_child}" \
|| return 1
homeassistant_ensure_config "${homeassistant_config_dir}"
HA_ARGS="--ignore-os-check --config ${homeassistant_config_dir}"
if [ -n "${homeassistant_log_file:-}" ]; then
install -g "${homeassistant_group}" -m 664 -o ${homeassistant_user} -- /dev/null "${homeassistant_log_file}" \
&& HA_ARGS="${HA_ARGS} --log-file ${homeassistant_log_file}"
fi
if [ -n "${homeassistant_log_rotate_days:-}" ]; then
HA_ARGS="${HA_ARGS} --log-rotate-days ${homeassistant_log_rotate_days}"
fi
checkyesno homeassistant_color_log || HA_ARGS="${HA_ARGS} --log-no-color"
checkyesno homeassistant_debug && HA_ARGS="${HA_ARGS} --debug"
checkyesno homeassistant_safe_mode && HA_ARGS="${HA_ARGS} --safe_mode"
checkyesno homeassistant_skip_pip && HA_ARGS="${HA_ARGS} --skip_pip"
checkyesno homeassistant_verbose && HA_ARGS="${HA_ARGS} --verbose"
rc_flags="-f -o ${logfile} -P ${pidfile} -p ${pidfile_child} -R ${homeassistant_restart_delay} ${HA_CMD} ${HA_ARGS}"
}
start_postcmd=${name}_poststart
homeassistant_poststart() {
sleep 1 ; run_rc_command status
}
restart_precmd="${name}_prerestart"
homeassistant_prerestart() {
homeassistant_check_config "${homeassistant_config_dir}"
}
stop_precmd=${name}_prestop
homeassistant_prestop() {
local _owner_
# shellcheck disable=SC2154
if [ -n "${rc_pid}" ] && [ "${_owner_:="$(stat -f '%Su' ${pidfile_child})"}" != ${homeassistant_user} ]; then
err 1 "${homeassistant_user} can not stop a process owned by ${_owner_}"
fi
}
stop_postcmd=${name}_poststop
homeassistant_poststop() {
rm -f -- "${pidfile_child}"
rm -f -- "${pidfile}"
}
status_cmd=${name}_status
homeassistant_status() {
local _http_ _ip_
if [ -n "${rc_pid}" ]; then
: "${homeassistant_secure:="NO"}" # This is only a cosmetic variable - used by the status_cmd
_ip_="$(ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p')"
checkyesno homeassistant_secure && _http_="https" || _http_="http"
echo "${name} is running as pid ${rc_pid}."
echo "${_http_}://${_ip_}:${homeassistant_port:-"8123"}" # This is only a cosmetic variable
else
echo "${name} is not running."
return 1
fi
}
check_config_cmd="${name}_check_config ${1} ${2}"
homeassistant_check_config() {
[ "${1}" == "check_config" ] || [ "${1}" == "onecheck_config" ] && shift
homeassistant_script check_config --config "${1:-"${homeassistant_config_dir}"}"
}
ensure_config_cmd="${name}_ensure_config ${1} ${2}"
homeassistant_ensure_config() {
[ "${1}" == "ensure_config" ] || [ "${1}" == "oneensure_config" ] && shift
local _config_dir_="${1:-"${homeassistant_config_dir}"}"
debug "config_dir: ${_config_dir_}"
if [ ! -d "${_config_dir_}" ]; then
install -d -g "${homeassistant_group}" -m 775 -o ${homeassistant_user} -- "${_config_dir_}" \
|| err 1 "unable to create directory: ${_config_dir_}"
fi
homeassistant_script ensure_config --config "${_config_dir_}"
}
script_cmd="${name}_script ${*}"
homeassistant_script() {
[ "${1}" == "script" ] || [ "${1}" == "onescript" ] && shift
local _action_="${1}" ; shift
local _args_="${*}"
homeassistant_precmd
# shellcheck disable=SC2016
su - ${homeassistant_user} -c '
source ${1}/bin/activate || exit 1
hass --script ${2} ${3}
deactivate
' _ ${homeassistant_venv} "${_action_}" "${_args_}"
}
logs_cmd="${name}_logs ${*}"
homeassistant_logs() {
case "${2}" in
-f )
tail -F "${logfile}" ;;
-h )
head -n "${3:-"100"}" "${logfile}" ;;
-n | -t )
tail -n "${3:-"100"}" "${logfile}" ;;
-l )
less -R "${logfile}" ;;
* )
cat "${logfile}" ;;
esac
}
upgrade_cmd="${name}_upgrade"
homeassistant_upgrade() {
homeassistant_precmd
run_rc_command stop 2>/dev/null ; local _rcstop_=${?}
homeassistant_install --upgrade "${name}"
homeassistant_check_config && [ ${_rcstop_} == 0 ] && run_rc_command start
}
install_cmd="${name}_install ${*}"
homeassistant_install() {
[ "${1}" == "install" ] || [ "${1}" == "oneinstall" ] && shift
local _create_ _arg_
_arg_="${*:-"${name}"}"
debug "install: ${_arg_}"
if [ "${1}" == "${name}" ] && { [ ! -d "${homeassistant_venv}" ] || [ ! "$(ls -A ${homeassistant_venv})" ]; }; then
debug "creating virtualenv: ${homeassistant_venv}"
install -d -g "${homeassistant_group}" -m 775 -o ${homeassistant_user} -- ${homeassistant_venv} \
|| err 1 "failed to create directory: ${homeassistant_venv}"
_create_="YES"
elif [ -d "${homeassistant_venv}" ]; then
debug "found existing directory: ${homeassistant_venv}"
homeassistant_precmd
else
echo "failed to install: ${_arg_}"
err 1 "${name} is not installed: ${homeassistant_venv}"
fi
# shellcheck disable=SC2016
su - ${homeassistant_user} -c '
if [ ${1} == "YES" ]; then
${2} -m venv ${3}
source ${3}/bin/activate || exit 1
shift 3
pip install wheel
pip install ${@}
else
source ${3}/bin/activate || exit 1
shift 3
pip install ${@}
fi
deactivate
' _ ${_create_:-"NO"} ${homeassistant_python} ${homeassistant_venv} "${_arg_}" || err 1 "install function failed"
}
reinstall_cmd="${name}_reinstall ${*}"
homeassistant_reinstall() {
[ "${1}" == "reinstall" ] || [ "${1}" == "onereinstall" ] && shift
local _ans1_ _ans2_ _rcstop_ _version_ _arg_
homeassistant_precmd
if [ "${1%==*}" == "${name}" ]; then
_arg_="${*}"
elif [ -z "${_arg_}" ]; then
if [ -n "${_version_:=$(cat ${homeassistant_config_dir}/.HA_VERSION 2>/dev/null)}" ]; then
_arg_="${name}==${_version_}"
else
_arg_="${name}"
fi
else
warn "expecting ${name} to be listed first"
err 1 "check args: ${*}"
fi
echo -e "\n${orn}You are about to recreate the virtualenv:${end}\n ${homeassistant_venv}\n"
echo -e "${orn}The following package(s) will be installed:${end}\n ${_arg_}\n"
read -rp " Type 'YES' to continue: " _ans1_
run_rc_command stop 2>/dev/null ; _rcstop_=${?}
cd / ; rm -r -- "${homeassistant_venv}" || err 1 "failed to remove ${homeassistant_venv}"
{ homeassistant_install ${_arg_} ; homeassistant_check_config ; } \
&& [ ${_rcstop_} == 0 ] && run_rc_command start
}
test_cmd="${name}_test"
homeassistant_test() {
echo -e "\nTesting virtualenv...\n"
homeassistant_precmd
## Switch users / activate virtualenv / run a command
# shellcheck disable=SC2016
su "${homeassistant_user}" -c '
echo -e " $(pwd)\n"
source ${1}/bin/activate
echo " $(python --version)"
echo " Home Assistant $(pip show homeassistant | grep Version | cut -d" " -f2)"
deactivate
' _ ${homeassistant_venv}
echo
}
colors () {
export red=$'\e[1;31m'
export orn=$'\e[38;5;208m'
export end=$'\e[0m'
} ; colors
checkyesno homeassistant_rc_debug && rc_debug="ON"
run_rc_command "${1}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment