From 47b048d0ec2e4c2d7d0be75284a8ef30f64b70ee Mon Sep 17 00:00:00 2001 From: fanyx Date: Mon, 4 Oct 2021 15:03:31 +0200 Subject: [PATCH] Add syncthing-quick-status --- .local/bin/README.md | 5 + .local/bin/sqs | 215 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 .local/bin/README.md create mode 100755 .local/bin/sqs diff --git a/.local/bin/README.md b/.local/bin/README.md new file mode 100644 index 0000000..f928bab --- /dev/null +++ b/.local/bin/README.md @@ -0,0 +1,5 @@ +# Custom binary files + +sqs is not created by me, please take a look at the original creator's repository: + +[syncthing-quick-status](https://github.com/serl/syncthing-quick-status) diff --git a/.local/bin/sqs b/.local/bin/sqs new file mode 100755 index 0000000..b013927 --- /dev/null +++ b/.local/bin/sqs @@ -0,0 +1,215 @@ +#!/bin/bash + +if ((BASH_VERSINFO[0] < 4)); then + echo "This script needs at least bash 4." + missing_deps=1 +fi +for dep in curl jq; do + if ! command -v $dep &>/dev/null; then + echo "$dep is required but not found." + missing_deps=1 + fi +done +[[ $missing_deps ]] && + exit 1 + +if [[ -z $SYNCTHING_API_KEY ]]; then + : "${SYNCTHING_CONFIG_FILE:="$HOME/.config/syncthing/config.xml"}" + apikey_regex='^\s+([^<]+)$' + apikey_line="$(grep -E "$apikey_regex" "$SYNCTHING_CONFIG_FILE")" + [[ $apikey_line =~ $apikey_regex ]] && + SYNCTHING_API_KEY=${BASH_REMATCH[1]} +fi + +if [[ -z $SYNCTHING_API_KEY ]]; then + echo "No API key in env. Set one of the variables SYNCTHING_API_KEY or SYNCTHING_CONFIG_FILE and try again..." + exit 1 +fi + +: "${SYNCTHING_ADDRESS:="localhost:8384"}" + +COLOR_PURPLE='\e[95m' +COLOR_GRAY='\e[90m' +COLOR_GREEN='\e[92m' +COLOR_BLUE='\e[34m' +COLOR_RED='\e[31m' +COLOR_RESET='\e[0m' + +RECENT_CHANGES_LIMIT=5 +LOG_ENTRIES_LIMIT=15 +LOG_MAX_AGE=300 # seconds + +declare -A api_cache=() +function get_api_response() { # $0 api_name + if [[ -z ${api_cache["$1"]} ]]; then + [[ $DEBUG ]] && echo -e "${COLOR_GRAY}CALLING API: $1${COLOR_RESET}" >&2 + api_cache["$1"]="$(curl --silent --insecure -L -H "X-API-Key: $SYNCTHING_API_KEY" "http://$SYNCTHING_ADDRESS/rest/$1")" + fi + RESULT="${api_cache["$1"]}" + # using stdout and piping directly to jq would jump over the cache... not sure why :/ + [[ $RESULT == "CSRF Error" ]] && return 1 + return 0 +} + +function jq_arg() { + echo "$1" | jq -r "$2" +} + +function call_jq() { # $0 api_name jq_commands + get_api_response "$1" && + RESULT="$(jq_arg "$RESULT" "$2")" +} + +function format_time() { + echo "${COLOR_GRAY}$(echo "$1" | cut -d'.' -f1)${COLOR_RESET}" +} + +function get_messages() { # $0 api_name jq_commands message_color_control_code max_age_in_seconds + call_jq "$1" "$2" + RESULT="$(jq_arg "$RESULT" '.when + " " + .message' | tail -n "$LOG_ENTRIES_LIMIT")" + local message_color="$3" + local max_age="${4:-0}" + local min_timestamp="$(($(date +%s) - max_age))" + local result= + local formatted_line= + while IFS= read -r line; do + [[ -z $line ]] && continue + when="$(echo "$line" | cut -d' ' -f1)" + message="$(echo "$line" | cut -d' ' -f2-)" + timestamp="$(date --date="$when" +%s)" + formatted_line="$(format_time "$when") ${message_color}$message${COLOR_RESET}"$'\n' + [[ $max_age -gt 0 ]] && [[ $timestamp -lt $min_timestamp ]] && + continue + result+="$formatted_line" + done <<< "$RESULT" + [[ -z $result ]] && + result="$formatted_line" # take the last log line in any case + RESULT="${result%$'\n'}" +} + +[[ $1 == -v ]] && VERBOSE=true + +if ! call_jq "system/status" '.myID'; then + echo "Error from Syncthing API: $RESULT" + echo "You should probably check and change the variables SYNCTHING_API_KEY or SYNCTHING_CONFIG_FILE." + exit 1 +elif [[ -z $RESULT ]]; then + echo "Empty response from Syncthing API." + echo "You should probably check and change the variables SYNCTHING_ADDRESS, SYNCTHING_API_KEY or SYNCTHING_CONFIG_FILE." + exit 1 +fi +local_device_id="$RESULT" +call_jq "system/config" '.devices | map(select(.deviceID == "'"$local_device_id"'"))[] | .name' +local_device_name="$RESULT" +echo -n "Local device: $local_device_name" +[[ $VERBOSE ]] && echo -en " ${COLOR_GRAY}($local_device_id)${COLOR_RESET}" +echo $'\n' + +echo "Devices:" +call_jq "system/config" '.devices[] | .deviceID' +for device_id in $RESULT; do + [[ $device_id == "$local_device_id" ]] && continue + call_jq "system/config" '.devices | map(select(.deviceID == "'"$device_id"'"))[]' + device_config="$RESULT" + device_name="$(jq_arg "$device_config" '.name')" + echo -n "$device_name: " + call_jq "system/connections" '.connections["'"$device_id"'"]' + device_status="$RESULT" + status="${COLOR_PURPLE}disconnected${COLOR_RESET}" + if [ "$(jq_arg "$device_status" '.paused')" == "true" ]; then + status="${COLOR_GRAY}paused${COLOR_RESET}" + elif [ "$(jq_arg "$device_status" '.connected')" == "true" ]; then + status="${COLOR_GREEN}$(jq_arg "$device_status" '.type')${COLOR_RESET}" + fi + echo -en "$status" + [[ $VERBOSE ]] && echo -en " ${COLOR_GRAY}($device_id)${COLOR_RESET}" + echo +done + +echo -e "\nFolders:" +call_jq "system/config" '.folders[] | .id' +for folder_id in $RESULT; do + call_jq "system/config" '.folders | map(select(.id == "'"$folder_id"'"))[]' + folder_config="$RESULT" + folder_label="$(jq_arg "$folder_config" '.label')" + if [ "$folder_label" ]; then + [[ $VERBOSE ]] && folder_label+=" ${COLOR_GRAY}($folder_id)${COLOR_RESET}" + else + folder_label="$folder_id" + fi + echo -en "$folder_label: " + folder_status= + need_bytes=0 + folder_paused="$(jq_arg "$folder_config" '.paused')" + [[ $folder_paused == true ]] && folder_status="paused" + if [[ -z $folder_status ]]; then + call_jq "db/status?folder=$folder_id" '.state' + folder_status="$RESULT" + call_jq "db/status?folder=$folder_id" '.needBytes' + need_bytes="$RESULT" + need_bytes_formatted= + [[ $need_bytes -gt 0 ]] && + need_bytes_formatted="$(numfmt --to=iec-i --suffix=B "$need_bytes")" + fi + case "$folder_status" in + paused) + folder_status="${COLOR_GRAY}$folder_status${COLOR_RESET}" + ;; + idle) + [[ $need_bytes -eq 0 ]] && + folder_status="${COLOR_GREEN}up to date${COLOR_RESET}" || + folder_status="${COLOR_RED}out of sync${COLOR_RESET}" + ;; + scanning|syncing) + folder_status="${COLOR_BLUE}$folder_status${COLOR_RESET}" + ;; + esac + [[ $need_bytes -gt 0 ]] && folder_status+=" ($need_bytes_formatted)" + echo -e "$folder_status" +done + +echo -e "\nRecent changes:" +call_jq "events/disk?limit=$RECENT_CHANGES_LIMIT&timeout=1" '.[] | .id' +for event_id in $RESULT; do + call_jq "events/disk?limit=$RECENT_CHANGES_LIMIT&timeout=1" '. | map(select(.id == '"$event_id"'))[]' + event="$RESULT" + + when="$(jq_arg "$event" '.time')" + path="$(jq_arg "$event" '.data.path')" + + folder_id="$(jq_arg "$event" '.data.folderID')" + call_jq "system/config" '.folders | map(select(.id == "'"$folder_id"'"))[].label' + folder_label="${RESULT:-$folder_id}" + + action="$(jq_arg "$event" '.data.action')" + action_color='?' + case "$action" in + added) action_color=${COLOR_GREEN}+ ;; + deleted) action_color=${COLOR_RED}- ;; + modified) action_color=${COLOR_BLUE}\# ;; + esac + + device_id_prefix="$(jq_arg "$event" '.data.modifiedBy')" + call_jq "system/config" '.devices | map(select(.deviceID | startswith("'"$device_id_prefix"'")))[].name' + device_name="${RESULT:-$device_id_prefix}" + + echo -e "$(format_time "$when") ${COLOR_GRAY}$device_name${COLOR_RESET} $folder_label ${action_color}$path${COLOR_RESET}" +done + +if [[ $VERBOSE ]]; then + get_messages "system/log" '.messages[]?' '' "$LOG_MAX_AGE" + echo -e "\nLast log entries:" + echo -e "$RESULT" +fi +get_messages "system/error" '.errors[]?' "$COLOR_RED" +if [[ $RESULT ]]; then + echo -e "\nERRORS:" + echo -e "$RESULT" +fi + +if [[ $DEBUG ]]; then + echo -e "\ncached responses:" + for k in "${!api_cache[@]}"; do + echo "$k" + done +fi