#!/usr/bin/env bash # Crow's Nest # A multiple Cam and Stream Service for mainsailOS # Written by Stephan Wendel aka KwadFan # Copyright 2021 - 2022 # https://github.com/mainsail-crew/crowsnest # GPL V3 ######## # shellcheck enable=require-variable-braces ## disabled SC2086 for some lines because there we want 'word splitting' # Exit on errors set -Ee # Global Vars BASE_USER=$(whoami) TITLE="crowsnest - A Webcam Daemon for Raspberry Pi OS" ### Non root if [ ${UID} == '0' ]; then echo -e "DO NOT RUN THIS SCRIPT AS ROOT!\nExiting..." exit 1 fi ### noninteractive Check if [ "${DEBIAN_FRONTEND}" != "noninteractive" ]; then export DEBIAN_FRONTEND=noninteractive fi ### Functions ### Messages ### Welcome Message function welcome_msg { echo -e "${TITLE}\n" echo -e "\tSome Parts of the Installer requires 'root' privileges." echo -e "\tYou will be prompted for your 'sudo' password, if needed.\n" } function detect_msg { echo -e "Found an existing 'crowsnest'. This will be removed." echo -e "Since we dont use mjpg-streamer it will also removed." echo -e "You can use KIAUH for example to reinstall.\n" } function goodbye_msg { echo -e "\nInstallation complete.\n\tPlease reboot your machine!" echo -e "I hope you enjoy crowsnest, GoodBye ..." } ### Installer ### General ## These two functions are reused from custompios common.sh ## Credits to guysoft! ## https://github.com/guysoft/CustomPiOS function install_cleanup_trap() { # kills all child processes of the current process on SIGINT or SIGTERM trap 'cleanup' SIGINT SIGTERM } function cleanup() { # make sure that all child processed die when we die echo -e "Killed by user ...\r\nGoodBye ...\r" # shellcheck disable=2046 [ -n "$(jobs -pr)" ] && kill $(jobs -pr) && sleep 5 && kill -9 $(jobs -pr) } ## function err_exit { if [ "${1}" != "0" ]; then echo -e "ERROR: Error ${1} occured on line ${2}" echo -e "ERROR: Stopping $(basename "$0")." echo -e "Goodbye..." fi # shellcheck disable=2046 [ -n "$(jobs -pr)" ] && kill $(jobs -pr) && sleep 5 && kill -9 $(jobs -pr) exit 1 } ### Init ERR Trap trap 'err_exit $? $LINENO' ERR ## helper func ## call get_os_version function get_os_version { if [ -n "${1}" ]; then grep -c "${1}" /etc/os-release fi } ### Import config from custompios. function import_config { ## Source Custom config if present if [ -s tools/config.local ]; then # shellcheck disable=SC1091 source tools/config.local return 0 fi ## X86 machines if [ "$(uname -m)" == "x86_64" ] && [ -f tools/config.x86 ]; then # shellcheck disable=SC1091 source tools/config.x86 return 0 fi ## rpi os buster if [ "$(uname -m)" != "x86_64" ] && [ "$(get_os_version buster)" != "0" ] && [ -f "tools/config.buster" ]; then # shellcheck disable=SC1091 source tools/config.buster return 0 fi ## rpi os bullseye if [ "$(uname -m)" != "x86_64" ] && [ "$(get_os_version bullseye)" != "0" ] && [ -f "tools/config.bullseye" ]; then # shellcheck disable=SC1091 source tools/config.bullseye return 0 fi } ### Detect crowsnest. function detect_existing_crowsnest { local remove if [ -x "/usr/local/bin/crowsnest" ] && [ -d "${HOME}/mjpg-streamer" ]; then detect_msg read -rp "Do you want to remove existing 'crowsnest'? (YES/NO) " remove if [ "${remove}" = "YES" ]; then echo -en "\nStopping crowsnest.service ...\r" sudo systemctl stop crowsnest.service &> /dev/null echo -e "Stopping crowsnest.service ... \t[OK]\r" remove_existing_crowsnest else echo -e "\nYou answered '${remove}'! Installation will be aborted..." echo -e "GoodBye...\n" exit 1 fi fi } ### Remove existing crowsnest function remove_existing_crowsnest { if [ -x "/usr/local/bin/crowsnest" ]; then echo -en "Removing 'crowsnest' ...\r" sudo rm -f /usr/local/bin/crowsnest > /dev/null echo -e "Removing 'crowsnest' ... \t\t[OK]\r" fi if [ -d "${HOME}/mjpg-streamer" ]; then echo -en "Removing 'mjpg-streamer' ...\r" sudo rm -rf "${HOME}"/mjpg-streamer > /dev/null echo -e "Removing 'mjpg-streamer' ... \t[OK]\r" fi if [ -f "/etc/systemd/system/crowsnest.service" ]; then echo -en "Removing 'crowsnest.service' ...\r" sudo rm -f /etc/systemd/system/crowsnest.service > /dev/null echo -e "Removing 'crowsnest.service' ... \t[OK]\r" fi if [ -f "/var/log/crowsnest.log" ]; then echo -en "Removing 'crowsnest.log' ...\r" sudo rm -f /var/log/crowsnest.log > /dev/null sudo rm -f "${HOME}"/klipper_logs/crowsnest.log > /dev/null echo -e "Removing 'crowsnest.log' ... \t[OK]\r" fi if [ -f "/etc/logrotate.d/crowsnest" ]; then echo -en "Removing 'crowsnest' logrotate...\r" sudo rm -f /etc/logrotate.d/crowsnest > /dev/null echo -e "Removing 'crowsnest' logrotate ... \t[OK]\r" fi echo -e "\nOld 'crowsnest' completly removed." echo -e "webcam.txt kept,but no longer necessary ..." } function install_crowsnest { local addconf bin_path logrotatefile moonraker_conf moonraker_update local crowsnest_bin servicefile template bin_path="/usr/local/bin" crowsnest_bin="${HOME}/crowsnest/crowsnest" template="${PWD}/sample_configs/${CROWSNEST_DEFAULT_CONF}" servicefile="${PWD}/file_templates/crowsnest.service" logrotatefile="${HOME}/crowsnest/file_templates/logrotate_crowsnest" moonraker_conf="${HOME}/klipper_config/moonraker.conf" moonraker_update="${PWD}/file_templates/moonraker_update.txt" ## helper func moonraker update_manager function add_update_entry { if [ -f "${moonraker_conf}" ]; then echo -e "Adding [update_manager] entry ..." sudo -u "${BASE_USER}" \ cp "${moonraker_conf}" "${moonraker_conf}.backup" && cat "${moonraker_conf}" "${moonraker_update}" > /tmp/moonraker.conf && cp -rf /tmp/moonraker.conf "${moonraker_conf}" if [ "${UNATTENDED}" == "true" ]; then sudo rm -f "${moonraker_conf}.backup" fi else echo -e "moonraker.conf is missing ... [SKIPPED]" fi } echo -e "\nInstall crowsnest Service ..." ## Install Dependencies echo -e "Installing 'crowsnest' Dependencies ..." # shellcheck disable=2086 sudo apt install --yes --no-install-recommends ${CROWSNEST_CROWSNEST_DEPS} > /dev/null echo -e "Installing 'crowsnest' Dependencies ... [OK]" ## Link crowsnest to $PATH echo -en "Linking crowsnest ...\r" sudo ln -sf "${crowsnest_bin}" "${bin_path}" > /dev/null echo -e "Linking crowsnest ... [OK]\r" ## Copy crowsnest.conf # Make sure config directory exists! if [ ! -d "${CROWSNEST_DEFAULT_CONF_DIR}" ]; then sudo -u "${BASE_USER}" mkdir -p "${CROWSNEST_DEFAULT_CONF_DIR}" fi # Make sure not to overwrite existing! if [ ! -f "${CROWSNEST_DEFAULT_CONF_DIR}/crowsnest.conf" ]; then echo -en "Copying crowsnest.conf ...\r" sudo -u "${BASE_USER}" cp -rf "${template}" "${CROWSNEST_DEFAULT_CONF_DIR}"/crowsnest.conf echo -e "Copying crowsnest.conf ... [OK]\r" fi ## Copy crowsnest.service echo -en "Copying crowsnest.service file ...\r" sudo cp -rf "${servicefile}" /etc/systemd/system/crowsnest.service > /dev/null if [ ! "${BASE_USER}" == "pi" ]; then sudo sed -i 's|pi|'"${BASE_USER}"'|g' /etc/systemd/system/crowsnest.service fi echo -e "Copying crowsnest.service file ... [OK]\r" ## Copy logrotate echo -en "Linking logrotate file ...\r" sudo cp -rf "${logrotatefile}" /etc/logrotate.d/crowsnest if [ ! "${BASE_USER}" == "pi" ]; then sudo sed -i 's|pi|'"${BASE_USER}"'|g' /etc/logrotate.d/crowsnest fi echo -e "Linking logrotate file ... [OK]\r" ## update systemd if not unattended if [ "${UNATTENDED}" == "false" ] && [ "$(stat -c %i /)" == "2" ]; then echo -en "Reload systemd to enable new deamon ...\r" sudo systemctl daemon-reload echo -e "Reload systemd to enable new daemon ... [OK]" fi ## enable crowsnest.service echo -en "Enable crowsnest.service on boot ...\r" sudo systemctl enable crowsnest.service echo -e "Enable crowsnest.service on boot ... [OK]\r" ## Add moonraker update manager entry ## Unattended if [ "${UNATTENDED}" == "true" ] && [ "${CROWSNEST_ADD_CROWSNEST_MOONRAKER}" == "1" ] && [ -f "${moonraker_conf}" ]; then echo -en "Adding Crowsnest Update Manager entry to moonraker.conf ...\r" add_update_entry echo -e "Adding Crowsnest Update Manager entry to moonraker.conf ... [OK]" fi ## Manual install if [ "${UNATTENDED}" != "true" ] && [ "${CROWSNEST_ADD_CROWSNEST_MOONRAKER}" != "0" ]; then read -rp "Do you want to add [update_manager] entry?(y/n) " addconf case "${addconf}" in y*|Y*) add_update_entry ;; n*|N*) echo -e "Adding Crowsnest Update Manager entry to moonraker.conf ... [SKIPPED]" ;; esac fi ## add $USER to group video echo -en "Add User ${BASE_USER} to group 'video' ...\r" if [ "$(groups | grep -c video)" == "0" ]; then sudo usermod -aG video "${BASE_USER}" > /dev/null echo -e "Add User ${BASE_USER} to group 'video' ... [OK]" else echo -e "Add User ${BASE_USER} to group 'video' ... [SKIPPED]" echo -e "==> User ${BASE_USER} is already in group 'video'" fi } function clone_ustreamer { ## remove bin/ustreamer if exist if [ -d bin/ustreamer ]; then rm -rf bin/ustreamer fi git clone "${CROWSNEST_USTREAMER_REPO_SHIP}" \ -b "${CROWSNEST_USTREAMER_REPO_BRANCH}" bin/ustreamer ## Buster workaround ## ustreamer support omx only till version 4.13 ## so stick to that version if [ "$(get_os_version buster)" != "0" ]; then pushd bin/ustreamer &> /dev/null || exit 1 git reset --hard 61ab2a8 popd &> /dev/null || exit 1 fi } function build_apps { echo -e "Build dependend Stream Apps ..." echo -e "Cloning ustreamer repository ..." clone_ustreamer echo -e "Installing 'ustreamer' Dependencies ..." # shellcheck disable=2086 sudo apt install --yes --no-install-recommends ${CROWSNEST_USTREAMER_DEPS} > /dev/null echo -e "Installing 'ustreamer' Dependencies ... [OK]" pushd bin > /dev/null make all popd > /dev/null } function install_raspicam_fix { if [ -f /proc/device-tree/model ] && grep -q "Raspberry" /proc/device-tree/model ; then echo -en "Applying Raspicam Fix ... \r" sudo sh -c 'echo "bcm2835-v4l2" >> /etc/modules' sudo cp file_templates/bcm2835-v4l2.conf /etc/modprobe.d/ echo -e "Applying Raspicam Fix ... [OK]" else echo -e "This is not a Raspberry Pi!" echo -e "Applying Raspicam Fix ... [SKIPPED]" fi } function enable_legacy_cam { local cfg cfg="/boot/config.txt" echo -en "Enable legacy camera stack ... \r" sudo sed -i "s/camera_auto_detect=1/#camera_auto_detect=1/" "${cfg}" if [ "$(grep -c "start_x" "${cfg}")" == "0" ]; then sudo crudini --set --inplace "${cfg}" all start_x 1 &> /dev/null fi if [ "$(grep -c "gpu_mem" "${cfg}")" == "0" ]; then sudo crudini --set --inplace "${cfg}" pi4 gpu_mem 256 &> /dev/null sudo crudini --set --inplace "${cfg}" all gpu_mem 128 &> /dev/null fi echo -e "Enable legacy camera stack ... [OK]" } #### MAIN while getopts "z" arg; do case ${arg} in z) echo "WARN: Running in UNATTENDED Mode ..." set -x UNATTENDED="true" ;; *) UNATTENDED="false" ;; esac done install_cleanup_trap import_config welcome_msg if [ "${UNATTENDED}" != "true" ]; then detect_existing_crowsnest fi echo -e "Running apt update first ..." sudo apt update install_crowsnest build_apps if [ "${UNATTENDED}" != "true" ] && [ "${CROWSNEST_FORCE_RASPICAMFIX}" != "0" ]; then install_raspicam_fix fi if [ "$(get_os_version bullseye)" != "0" ]; then enable_legacy_cam fi goodbye_msg exit 0