I was using the old sunwait script on this forum and decided to jazz it up a little bit. This script assumes a few things and will break otherwise.
Only usable with API 2.0 access/refresh tokens
Requires: jq, sunwait, curl, bc
ZM needs to be setup with a hash passphrase, not append ips to hash, and login with authenticated 2.0 users (like eventserver install calls for)
Obviously API enabled
Run state setup for daytime named Day
Run state setup for night named Night
I run it with sudo -u www-data
You can see where to edit the user, passwd, logging, etc.. logging is a bool true/false, if false it only echos out to console.
This gets an access token and saves when it expires so it can keep using the access token or refresh it using the refresh token. This way you're not logging in all the time. can call this script with arguments "auth" (ex: sudo -u www-data ./zmsunwait.sh auth) to just get/refresh/display your tokens. call it with change <Night/Day> to force a state change. Though if you have a cronjob or systemd timer running this script it will change it on you anyways.
I had a root cronjob using sudo to run this script with user www-data (sudo -u www-data) every 10 or 15 minutes. This way if you reboot or anything happens your run states are still followed. I now have a systemd .timer linked to a .service file that runs its every 586 seconds (why not?).
#!/bin/bash
# call this with a cronjob every 10 mins from root but assigned www-data user - *10 * * * * /usr/bin/sudo -u www-data /path/to/this/script OR
# make a systemd .timer and .service file and use systemctl enable --now yourfileyoumade.timer. --now executes script now and starts timer
# instead of at next boot. Make sure systemd .service file uses sudo -u www-data as well.
# call this script with no args to check if run state is correct for time of day (involves logging into API)
# Must be using authenticated login and api 2.0
# call this script with "change <Night/Day>" to force a run state change
# call with "auth" to check auth and echo info out to console
# REQUIRES: sunwait, bc, curl, and jq. most repos have everything but sunwait -> https://github.com/risacher/sunwait
[[ ! $(command -v jq) ]] && echo "*** DEPENDANCY ISSUE: You need to install: jq (JSON Parser/Filter) ***" && echo "Exiting..." && exit 1
[[ ! $(command -v sunwait) ]] && echo "*** DEPENDANCY ISSUE: You need to install: sunwait ***" && echo "Exiting..." && exit 1
[[ ! $(command -v bc) ]] && echo "*** DEPENDANCY ISSUE: You need to install: bc ***" && echo "Exiting..." && exit 1
[[ ! $(command -v curl) ]] && echo "*** DEPENDANCY ISSUE: You need to install: curl ***" && echo "Exiting..." && exit 1
### "EDIT THESE"
user=api_user #user name for zoneminder login
pass=api_password #password for zoneminder login
api_url='https://urZMinstall.duckdns.org' #your base url NOTICE NO TRAILING SLASH!!!
logging=false #log to file and echo or echo only
logfile='/home/jimmyjoebob/.local/zm/zm_check_sunwait.log'
auth_file='/home/jimmyjoebob/.local/zm/zm_auth.json'
lat= #Latitude for sunwait
lon= #Longitude for sunwait
offset=00:05 #Offset towards noon (x time after sunrise/before sunset)
###
##" Your run state in zm must be Day for daytime and Night for night. only these 2 supported for now with this script"
-------------------------------------------------------------------------------------------------------------------
want_state=(Night Day) #which states we want to watch for stored in an array
zm_status=$(sudo -u www-data $(which zmpkg.pl) status 2>&1)
sunwait=$(which sunwait)
auto='Forced'
[[ ! -e $logfile ]] && install -m 664 /dev/null $logfile
function logout() { if [[ "$logging" == true ]]; then echo -e "$@" >> $logfile; fi; echo -e "$@"; }
#api auth wrapper
function api_auth() {
curl_url=$api_url'/zm/api/host/login.json' #api login
curl=$(which curl)
curl_args=(-XPOST -d user=$user\&pass=$pass\&stateful=1 -sSf $curl_url) #Initial AUTH request format
function create_tknfile() {
[[ -e $auth_file ]] && $(which rm) $auth_file
install -m 640 /dev/null $auth_file
[[ $? -ne 0 ]] && logout "$(date '+%c'):[$$]:ERR:${FUNCNAME[0]}: While creating $auth_file - exiting..." && return 1
}
function convsec {
secs=$2
[[ $1 =~ (day|long) ]] && ret_val=$(printf '%dd:%dh:%dm:%ds\n' $((secs/86400)) $((secs%86400/3600)) $((secs%3600/60)) $((secs%60)))
[[ $1 =~ (nano|sec) ]] && ret_val=$(printf '%02dh:%02dm:%02fs\n' $(echo -e "$secs/3600\n$secs%3600/60\n$secs%60"| bc))
[[ $1 =~ (reg|hour|min) ]] && ret_val=$(printf '%dh:%dm:%ds\n' $((secs/3600)) $((secs%3600/60)) $((secs%60)))
echo "$ret_val"
}
function get_tokens () {
auth_token=($(cat $auth_file)) #read token file into array so we can do things with it
[[ $? -ne 0 ]] && logout "$(date '+%c'):[$$]:DBG:${FUNCNAME[0]}: couldnt read auth_file, get_tokens() auth_token: $auth_token" && return 1
auth_key=$(jq -r '.access_token' <<< ${auth_token[0]})
refresh_key=$(jq -r '.refresh_token' <<< ${auth_token[0]})
auth_howlong=$(jq -r '.access_token_expires' <<< ${auth_token[0]})
refresh_howlong=$(jq -r '.refresh_token_expires' <<< ${auth_token[0]})
api_ver=$(jq -r '.apiversion' <<< ${auth_token[0]})
auth_exp=$(jq -r '.auth_expires' <<< ${auth_token[0]})
refresh_exp=$(jq -r '.refresh_expires' <<< ${auth_token[0]})
grace=0 #set a grace period in seconds, set to 0 for none
auth_grace=$(expr $auth_exp - $grace)
if [[ $? -ne 0 ]]; then
logout "$(date '+%c'):[$$]:DBG:${FUNCNAME[0]}: couldnt do math for auth_grace: auth_exp=$auth_exp - grace=$grace = auth_grace=$auth_grace"
logout "$(date '+%c'):[$$]:DBG:${FUNCNAME[0]}: access_token_expires = $auth_howlong - refresh_token_expires = $refresh_howlong - auth_expires = $auth_exp"
return 1
fi
}
function auth() {
get_tokens
current=$(date -d $(date '+%T') '+%s')
if [[ $current -ge $refresh_exp ]]; then #is refresh expired?
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: REFRESH ($(convsec reg $(expr $current - $refresh_exp)) past) is expired! Getting new tokens (this should happen every $(convsec reg $refresh_howlong)"
get_auth
return 0
elif [[ $current -ge $auth_grace ]]; then #is auth expired? with grace period
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: AUTH ($(convsec reg $(expr $current - $auth_exp)) past) is expired! - REFRESH ($(convsec reg $(expr $refresh_exp - $current))) is valid!"
get_refresh
return 0
else #keep using the VALID auth token
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: AUTH ($(convsec reg $(expr $auth_exp - $current))) is valid! - REFRESH ($(convsec reg $(expr $refresh_exp - $current))) is valid!"
return 0
fi
}
function get_auth() {
auth_token=($($curl ${curl_args[@]}))
curl_good=$(jq -r '.success' <<< "${auth_token[0]}")
curl_good2=$(jq -r '. | length' <<< ${auth_token[0]})
if [[ $curl_good = 'false' ]] || [[ $curl_good2 = 0 ]]; then
logout "$(date '+%c'):[$$]:ERR:${FUNCNAME[0]}: AUTH request FAILED: ${auth_token[@]} - exiting..."
return 1
fi
create_tknfile
auth_key=$(jq -r '.access_token' <<< "${auth_token[0]}")
refresh_key=$(jq -r '.refresh_token' <<< "${auth_token[0]}")
auth_howlong=$(jq -r '.access_token_expires' <<< "${auth_token[0]}")
refresh_howlong=$(jq -r '.refresh_token_expires' <<< "${auth_token[0]}")
api_ver=$(jq -r '.apiversion' <<< "${auth_token[0]}")
current=$(date -d $(date '+%T') '+%s')
auth_exp=$(expr $current + $auth_howlong)
refresh_exp=$(expr $current + $refresh_howlong)
echo "{\"access_token\":\"$auth_key\",\"access_token_expires\":$auth_howlong,\"refresh_token\":\"$refresh_key\",\"refresh_token_expires\":$refresh_howlong,\"apiversion\":\"$api_ver\",\"auth_expires\":\"$auth_exp\",\"refresh_expires\":\"$refresh_exp\"}" > $auth_file
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: New AUTH ($(convsec reg $auth_howlong) lifetime) and REFRESH ($(convsec reg $refresh_howlong) lifetime) tokens grabbed!"
}
function get_refresh() {
curl_args=(-XPOST -d token=$refresh_key -sSf $curl_url)
refresh_auth_tkn=$($curl ${curl_args[@]})
[[ $? -ne 0 ]] && logout "$(date '+%c'):[$$]:ERR:${FUNCNAME[0]}: curl ${curl_args[@]} - exiting..." && return 1
curl_good=$(jq -r '.success' <<< "$refresh_auth_tkn")
curl_good2=$(jq -r '. | length' <<< ${refresh_auth_tkn[0]})
if [[ $curl_good = 'false' ]] || [[ $curl_good2 = 0 ]]; then
logout "$(date '+%c'):[$$]:ERR:${FUNCNAME[0]}: AUTH request FAILED: ${auth_token[@]} - exiting..."
return 1
fi
#Do new access_token calculations
auth_key=$(jq -r '.access_token' <<< "$refresh_auth_tkn")
auth_howlong=$(jq -r '.access_token_expires' <<< "$refresh_auth_tkn")
current=$(date -d $(date '+%T') '+%s')
new_auth_exp=$(expr $current + $auth_howlong)
create_tknfile
echo "{\"access_token\":\"$auth_key\",\"access_token_expires\":$auth_howlong,\"refresh_token\":\"$refresh_key\",\"refresh_token_expires\":$refresh_howlong,\"apiversion\":\"$api_ver\",\"auth_expires\":\"$new_auth_exp\",\"refresh_expires\":\"$refresh_exp\"}" > $auth_file
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: AUTH ($(convsec hour $auth_howlong) lifetime) token refreshed!"
}
if [[ -e $auth_file ]]; then
auth
return 0
else
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: No AUTH file ($auth_file), creating now with new AUTH/REFRESH tokens"
get_auth
return 0
fi
return 0
}
function zmsunwait() {
run_state=$2
if [[ $run_state =~ ^(r|R)ise ]] || [[ $run_state =~ ^(d|D)ay ]]; then
run_state="Day"
elif [[ $run_state =~ ^(s|S)et ]] || [[ $run_state =~ ^(n|N)ight ]]; then
run_state="Night"
else
echo "Usage: zm_check_sunwait.sh change <Day/Night>"
exit 1
fi
if [[ $zm_status == "running" ]]; then
$(which zmpkg.pl) $run_state
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: $auto set ZoneMinder run state to $run_state"
else
logout "$(date '+%c'):[$$]:WAR:${FUNCNAME[0]}: ZoneMinder is not set to 'running', not changing run state, exiting..."
fi
return 0
}
function arg_eval() {
[[ $1 = 'auth' ]] && api_auth && echo -e "Auth token ($(convsec reg $(expr $auth_exp - $current))): $auth_key" && echo -e "Refresh token ($(convsec reg $(expr $refresh_exp - $current))): $refresh_key" && echo -e "Tokens saved to: $auth_file" && exit 0
zmsunwait $@
[[ $? -ne 0 ]] && exit 1 || exit 0
}
#"check if there are any arguments, if so evaluate"
[[ $# > 0 ]] && arg_eval $@
# "Main var section"
sunrise=$(date -d $($sunwait list rise offset $offset $lat $lon) '+%T') # get sunrise and sunset times with x min offset
sunset=$(date -d $($sunwait list set offset $offset $lat $lon) '+%T')
#this is how we do math, with "unix/epoch time"
epoch_current=$(date -d $(date '+%T') '+%s')
epoch_sunrise=$(date -d $sunrise '+%s')
epoch_sunset=$(date -d $sunset '+%s')
epoch_before_rise=$(date -d 00:59:30 '+%s')
epoch_after_rise=$(date -d 01:00:30 '+%s')
epoch_before_set=$(date -d 12:59:30 '+%s')
epoch_after_set=$(date -d 13:00:30 '+%s')
epoch_before_midnight=$(date -d 23:59:59 '+%s')
epoch_after_midnight=$(date -d 00:00:59 '+%s')
# "do API auth token stuff"
api_auth
[[ $? -ne 0 ]] && logout "$(date '+%c'):[$$]:ERR:${FUNCNAME[0]}: Function api_auth returned an error: \$? = $?" && exit 1
# "Create request to get the current run state (also lists all states available)"
curl=$(which curl)
curl_url=$api_url"/zm/api/states.json?token=$auth_key" #url to query states with auth
curl_args=(-XGET $curl_url -sSf)
states_JSON=$($curl ${curl_args[@]}) #execute curl GET and store return in variable
[[ $? -ne 0 ]] && logout "$(date '+%c'):[$$]:ERR:${FUNCNAME[0]}: Curl: trying to query for states (AUTH problem most likely). states_JSON= $states_JSON"
#echo "states_JSON = $states_JSON" #debug
# "Parse JSON Data (specific to states, only to impliment DAY/NIGHT); can expand later?"
state_avail=$(jq -r '.[] | length' <<< $states_JSON)
[[ $states_avail = 0 ]] && logout "$(date '+%c'):[$$]:DBG:${FUNCNAME[0]}: states_avail = 0, whats wrong here? states_JSON = $states_JSON" && exit 1
#echo "Total # of States Available: $state_avail" #debug
state_names=($(jq -r '.states[].State.Name' <<< $states_JSON))
#echo "state_names in ARRAY = ${state_names[@]}" #debug
state_isactive=($(jq -r '.states[].State.IsActive' <<< $states_JSON))
#echo "state_isactive in ARRAY = ${state_isactive[@]}" #debug
# "loop to find which indice is active, this correlates to a name"
iter=0
for i in ${state_isactive[@]}
do
if [[ $i = '1' ]]; then
curr_state="${state_names[$iter]}"
break
fi
iter=$(expr $iter + 1)
done
# "make sure we have the info needed to continue on"
[[ -z $curr_state ]] && logout "$(date '+%c'):[$$]:ERR:${FUNCNAME[0]}: curr_state is null, exiting..." && exit 1
#echo "curr_state = $curr_state" #debug
function zmsunwaitcheck {
current=$(date '+%T') #HH:MM:SS
if [[ $epoch_current -ge $epoch_after_midnight ]] && [[ $epoch_current -le $epoch_sunrise ]]; then # is it between midnight sunrise? it should be Night
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: It's currently ($current) between Midnight and sunrise ($sunrise). Run state ($curr_state) should be Night, checking now"
if [[ $curr_state != ${want_state[0]} ]]; then #if runstate isnt set to Night (1st [0] in array), theres an issue
auto='Automatically'
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: State ($curr_state) is not set to Night, changing now and exiting..."
zmsunwait change Night
return 0
else
return 0
fi
elif [[ $epoch_current -ge $epoch_sunrise ]] && [[ $epoch_current -le $epoch_sunset ]]; then # is it between sunrise and sunset? should be Day
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: It's currently ($current) between sunrise ($sunrise) and sunset ($sunset). Run state ($curr_state) should be Day, checking now"
if [[ $curr_state != ${want_state[1]} ]]; then #if runstate isnt set to Day (2nd [1] in array), theres an issue
auto='Automatically'
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: State ($curr_state) is not set to Day, changing now and exiting..."
zmsunwait change Day
return 0
else
return 0
fi
elif [[ $epoch_current -ge $epoch_sunset ]] && [[ $epoch_current -le $epoch_before_midnight ]]; then #is it between sunset and midnight? should be Night
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: It's currently ($current) between sunset ($sunset) and Midnight. Run state ($curr_state) should be Night, checking now"
if [[ $curr_state != ${want_state[0]} ]]; then # if current state isnt set to Night (1st [0] in array), theres an issue
auto='Automatically'
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: State ($curr_state) is not set to Night, changing now and exiting..."
zmsunwait change Night
return 0
elif [[ $curr_state = ${want_state[0]} ]]; then
return 0
else
logout "$(date '+%c'):[$$]:INF:${FUNCNAME[0]}: State ($curr_state) is not Night or Day! Expand me to handle this"
return 0
fi
else
logout "$(date '+%c'):[$$]:ERR:${FUNCNAME[0]}: Time stuff is messed up, come check script! Exiting with ERROR!"
exit 1
fi
}
#--------- MAIN LOGIC -----------
zmsunwaitcheck