Last active
December 28, 2020 16:38
-
-
Save QNimbus/cb7a61d4be538f80ccac7186a84e5ed4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
## | |
## Title: deploy_homeassistant.sh | |
## Description: Script to deploy uploaded certificates for use with Home Assistant | |
## Author: B. van wetten | |
## Created date: 28-12-2020 | |
## Updated date: 28-12-2020 | |
## Version: 0.1 | |
## GitHub Gist: https://gist.github.com/cb7a61d4be538f80ccac7186a84e5ed4 | |
## | |
## Usage: deploy_homeassistant.sh -c certificates [-r] [-d destination] | |
## deploy_homeassistant.sh --help | |
## To do: Also allow for a filename to be passed as a positional parameter to the '-c' argument | |
## Notes: For remote use (e.g. SSH) use the following command as example: | |
## /bin/cat | /bin/xargs -0 -I % /home/dsmr/deploy_homeassistant.sh -c --% | |
## | |
## You can use this in your 'authorized_keys' file to run this command remotely: | |
## command="/bin/cat | /usr/bin/xargs -0 -I % /config/scripts/deploy_homeassistant.sh -r -d /home/pi/certs -c % --",no-user-rc,no-port-forwarding,no-x11-forwarding,no-pty,from="<client-ip>" ssh-rsa AAAAB3******** | |
set -o nounset | |
# http://www.dwheeler.com/essays/filenames-in-shell.html | |
IFS=$'\n\t' | |
# Shell utilities | |
CP=$(which cp); [[ $? != 0 ]] && echo "Command 'cp' not found" >&2 && exit 1 | |
MV=$(which mv); [[ $? != 0 ]] && echo "Command 'mv' not found" >&2 && exit 1 | |
RM=$(which rm); [[ $? != 0 ]] && echo "Command 'rm' not found" >&2 && exit 1 | |
FIND=$(which find); [[ $? != 0 ]] && echo "Command 'find' not found" >&2 && exit 1 | |
CHMOD=$(which chmod); [[ $? != 0 ]] && echo "Command 'chmod' not found" >&2 && exit 1 | |
XARGS=$(which xargs); [[ $? != 0 ]] && echo "Command 'xargs' not found" >&2 && exit 1 | |
GETOPT=$(which getopt); [[ $? != 0 ]] && echo "Command 'getopt' not found" >&2 && exit 1 | |
OPENSSL=$(which openssl); [[ $? != 0 ]] && echo "Command 'openssl' not found" >&2 && exit 1 | |
# Initialize variables | |
_PWD=$(pwd) | |
_UID=$(id -u) | |
_GID=$(id -g) | |
_ME=$(basename "${0}") | |
_TMPFILE="tmp-${$}-${RANDOM}" | |
_CERT_FILE="cert.pem" | |
_KEY_FILE="privkey.pem" | |
_CHAIN_FILE="fullchain.pem" | |
_CERT_PERMS="0400" | |
_RFLAG=false | |
_DFLAG=false | |
# Option strings | |
SHORT=hrd: | |
LONG=help,restart,destination: | |
########################################################################## | |
# Init # | |
########################################################################## | |
# Cleanup any temp files after script execution (traps signals: 0, 1, 2, 15) | |
trap '${FIND} ${_PWD} -maxdepth 1 -name "${_TMPFILE}*" -type f -print0 | ${XARGS} -I % -0 ${RM} %' INT TERM HUP EXIT | |
# Read certificate from command line and remove from positional arguments | |
for((i=1; i<=$#; i++)) | |
do | |
_cur=$i | |
_next=$(expr $i + 1) | |
_param=${!_cur} | |
_next_param=${!_next:-} | |
if [[ "${_param}" =~ (^-c$|^--certificate$) && ! -z "${_next_param}" ]] | |
then | |
echo -en "${_next_param}" > "${_TMPFILE}.pem" | |
set -- "${@:1:_cur-1}" "${@:_cur+2}" | |
fi | |
done | |
# Get command line arguments | |
if [[ "${#}" -gt 0 ]] | |
then | |
getopt -T > /dev/null | |
if [ $? -eq 4 ]; then | |
# GNU enhanced getopt is available | |
_ARGS=$(${GETOPT} --name "$_ME" --long ${LONG} --options ${SHORT} -- "$@") | |
else | |
# Original getopt is available (no long option names, no whitespace, no sorting) | |
_ARGS=$(${GETOPT} ${SHORT} "$@") | |
fi | |
if [ $? -ne 0 ]; then | |
echo "$_ME: usage error (use -h for help)" >&2 | |
exit 2 | |
fi | |
fi | |
eval set -- "${_ARGS:-}" | |
############################################################################### | |
# Functions # | |
############################################################################### | |
# _print_usage() | |
# | |
# Usage: | |
# _print_usage | |
# | |
# Print the program usage information. | |
_print_usage() { | |
cat <<HEREDOC | |
___ ____ ___ _ ____ _ _ _ _ ____ _ _ ____ ____ ____ ____ _ ____ ___ ____ _ _ ___ | |
| \ |___ |__] | | | \_/ |__| | | |\/| |___ |__| [__ [__ | [__ | |__| |\ | | | |
|__/ |___ | |___ |__| | ___ | | |__| | | |___ | | ___] ___] | ___] | | | | \| | | |
Usage: | |
${_ME} -c file [-r] [-d destination] | |
${_ME} -h | |
Options: | |
-h, --help Show this screen | |
-r, --restart Restart Home Assistant | |
-c, --certificate Certificate file | |
-d, --destination Destination folder | |
HEREDOC | |
exit ${1:-0} | |
} | |
# _set_variable() | |
# | |
# Usage: | |
# _set_variable variable value | |
# | |
# Sets the variable to a value if not already set. Otherwise exits with an error message | |
_set_variable() | |
{ | |
local varname="$1" | |
shift | |
if [ -z "${!varname:-}" ]; then | |
# eval="${varname}=${@@Q}" | |
eval "$varname=\"$@\"" | |
else | |
echo "Error: $varname already set" | |
usage | |
fi | |
} | |
# _parse_commandline_arguments() | |
# | |
# Usage: | |
# _parse_commandline_arguments | |
# | |
# Parses and validates commandline arguments and populates appropriate variables. | |
_parse_commandline_arguments() | |
{ | |
while true; | |
do | |
case "${1:-}" in | |
-r|--restart) | |
_RFLAG=true | |
shift | |
;; | |
-d|--destination) | |
[[ -z "${2}" || "${2}" == *[[:space:]]* || "${2}" == -* ]] && { echo "$_ME: $1 needs a value" >&2; _print_usage 1; } | |
_set_variable DST_DIR "${2}" | |
_DFLAG=true | |
shift 2 | |
;; | |
\?) echo "$_ME: Unknown option -$1" >&2; | |
exit 1 | |
;; | |
:) echo "$_ME: -$1 needs a value" >&2; | |
exit 1 | |
;; | |
*) shift | |
break | |
;; | |
esac | |
done | |
} | |
# _validate_parameters() | |
# | |
# Usage: | |
# _validate_parameters | |
# | |
# Performs several checks on the supplied command line arguments | |
_validate_parameters() { | |
# Check certificates | |
[[ ! -f "${_TMPFILE}.pem" || ! $(${OPENSSL} rsa -in "${_TMPFILE}.pem" -check 2> /dev/null) ]] && echo -e "$_ME: Invalid or no certificate supplied\n" && _print_usage 1; | |
# Check if destination was given as argument otherwise set destination to source | |
! "$_DFLAG" && DST_DIR="$_PWD" | |
# Remove trailing slash unless root | |
case $DST_DIR in | |
*[!/]*/) DST_DIR=${DST_DIR%"${DST_DIR##*[!/]}"};; | |
*[/]) DST_DIR="/";; | |
esac | |
# Check if destination dir argument exists as a directory | |
[[ ! -d "$DST_DIR" ]] && echo -e "Error: '${DST_DIR}' does not exist or current user does not have writing permissions\n" && _print_usage 1; | |
} | |
############################################################################### | |
# Main # | |
############################################################################### | |
# _main() | |
# | |
# Usage: | |
# _main [<options>] [<arguments>] | |
# | |
# Description: | |
# Entry point for the program, handling basic option parsing and dispatching. | |
_main() { | |
# Avoid complex option parsing when only one program option is expected. | |
if [[ "${@:-}" =~ -h|--help ]] | |
then | |
_print_usage | |
else | |
_parse_commandline_arguments "$@" | |
_validate_parameters | |
# Extract certificate, key and certificate authority chain | |
${OPENSSL} x509 -in "${_TMPFILE}.pem" -outform PEM -out "${_TMPFILE}.cert.pem" || exit 1 | |
${OPENSSL} pkey -out "${_TMPFILE}.key.pem" -in "${_TMPFILE}.pem" || exit 1 | |
${OPENSSL} crl2pkcs7 -nocrl -certfile "${_TMPFILE}.pem" > "${_TMPFILE}.pkcs7" || exit 1 | |
${OPENSSL} pkcs7 -print_certs -out "${_TMPFILE}.chain.pem" -in "${_TMPFILE}.pkcs7" || exit 1 | |
# Move certificate files | |
sudo ${MV} "${_TMPFILE}.cert.pem" ${DST_DIR}/${_CERT_FILE} || exit 1 | |
sudo ${MV} "${_TMPFILE}.key.pem" ${DST_DIR}/${_KEY_FILE} || exit 1 | |
sudo ${MV} "${_TMPFILE}.chain.pem" ${DST_DIR}/${_CHAIN_FILE} || exit 1 | |
# Set the correct permissions | |
sudo ${CHMOD} ${_CERT_PERMS} ${DST_DIR}/${_CERT_FILE} || exit 1 | |
sudo ${CHMOD} ${_CERT_PERMS} ${DST_DIR}/${_KEY_FILE} || exit 1 | |
sudo ${CHMOD} ${_CERT_PERMS} ${DST_DIR}/${_CHAIN_FILE} || exit 1 | |
# Reload Home Assistant | |
# Work around: using 'sudo docker container restart homeassistant' since I have not found a way to restart HA yet) | |
"$_RFLAG" && sudo /usr/local/bin/docker container restart homeassistant | |
fi | |
} | |
# Call `_main` after everything has been defined. | |
_main "$@" | |
exit 0; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment