diff --git a/.gitattributes b/.gitattributes index bdb0cabc..205021e4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,17 +1,2 @@ -# Auto detect text files and perform LF normalization -* text=auto - -# Custom for Visual Studio -*.cs diff=csharp - -# Standard to msysgit -*.doc diff=astextplain -*.DOC diff=astextplain -*.docx diff=astextplain -*.DOCX diff=astextplain -*.dot diff=astextplain -*.DOT diff=astextplain -*.pdf diff=astextplain -*.PDF diff=astextplain -*.rtf diff=astextplain -*.RTF diff=astextplain +# Enforce Unix newlines +* text=auto eol=lf diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..73d2a262 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# see https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +* @pi-hole/padd-maintainers diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 42be6905..be18a8f3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,5 +8,3 @@ updates: time: "10:00" open-pull-requests-limit: 10 target-branch: development - reviewers: - - "pi-hole/padd-maintainers" diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 00000000..a33b9ecd --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,32 @@ +name: CI Tests +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + shellcheck: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + steps: + - + name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 + with: + fetch-depth: 0 # Differential ShellCheck requires full git history + + - name: Differential ShellCheck + uses: redhat-plumbers-in-action/differential-shellcheck@0d9e5b29625f871e6a4215380486d6f1a7cb6cdd #v5.5.5 + with: + severity: warning + display-engine: sarif-fmt + + - name: Spell-Checking + uses: codespell-project/actions-codespell@406322ec52dd7b488e48c1c4b82e2a8b3a1bf630 #v2.1 + with: + ignore_words_file: .codespellignore + + - name: Get editorconfig-checker + uses: editorconfig-checker/action-editorconfig-checker@1a41284d59c6fe7f1b21ddc4a2b36400a33dc1b4 # tag v2 is really out of date + + - name: Run editorconfig-checker + run: editorconfig-checker diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml deleted file mode 100644 index f81d0e56..00000000 --- a/.github/workflows/codespell.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Codespell -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - -jobs: - spell-check: - if: github.event.pull_request.draft == false - runs-on: ubuntu-latest - steps: - - - name: Checkout repository - uses: actions/checkout@v4.2.2 - - - name: Spell-Checking - uses: codespell-project/actions-codespell@master - with: - ignore_words_file: .codespellignore diff --git a/.github/workflows/editorconfig-checker.yml b/.github/workflows/editorconfig-checker.yml deleted file mode 100644 index 8f104c24..00000000 --- a/.github/workflows/editorconfig-checker.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: editorconfig-checker - -on: - pull_request: - push: - -jobs: - build: - name: editorconfig-checker - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4.2.2 - - uses: editorconfig-checker/action-editorconfig-checker@main # current tag v1.0.0 is really out-of-date - - run: editorconfig-checker diff --git a/.github/workflows/merge-conflict.yml b/.github/workflows/merge-conflict.yml index ea30b655..c7ceb93e 100644 --- a/.github/workflows/merge-conflict.yml +++ b/.github/workflows/merge-conflict.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check if PRs are have merge conflicts - uses: eps1lon/actions-label-merge-conflict@v3.0.3 + uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 #v3.0.3 with: dirtyLabel: "Merge Conflict" repoToken: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 558223f5..1ee84f17 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -17,7 +17,7 @@ jobs: issues: write steps: - - uses: actions/stale@v9.1.0 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 #v10.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 30 @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - name: Remove 'stale' label run: gh issue edit ${{ github.event.issue.number }} --remove-label ${{ env.stale_label }} env: diff --git a/.github/workflows/stale_pr.yml b/.github/workflows/stale_pr.yml index 2441271e..123488cb 100644 --- a/.github/workflows/stale_pr.yml +++ b/.github/workflows/stale_pr.yml @@ -17,7 +17,7 @@ jobs: pull-requests: write steps: - - uses: actions/stale@v9.1.0 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 #v10.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} # Do not automatically mark PR/issue as stale diff --git a/.github/workflows/sync-back-to-dev.yml b/.github/workflows/sync-back-to-dev.yml index d9c7ed19..602035fd 100644 --- a/.github/workflows/sync-back-to-dev.yml +++ b/.github/workflows/sync-back-to-dev.yml @@ -11,7 +11,7 @@ jobs: name: Syncing branches steps: - name: Checkout - uses: actions/checkout@v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 - name: Opening pull request run: gh pr create -B development -H master --title 'Sync master back into development' --body 'Created by Github action' --label 'Internal' env: diff --git a/.github/workflows/version_bump.yml b/.github/workflows/version_bump.yml index a4031e90..81893d9b 100644 --- a/.github/workflows/version_bump.yml +++ b/.github/workflows/version_bump.yml @@ -22,7 +22,7 @@ jobs: shell: bash - name: Checkout code - uses: actions/checkout@v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 with: ref: 'development' diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 00000000..c4711a8f --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1,6 @@ +external-sources=true # allow shellcheck to read external sources +disable=SC3043 #disable SC3043: In POSIX sh, local is undefined. +enable=useless-use-of-cat # disabled by default as of shellcheck 0.11.0 +enable=avoid-negated-conditions # avoid-negated-conditions is optional as of shellcheck 0.11.0 +enable=require-variable-braces +enable=deprecate-which diff --git a/README.md b/README.md index 1165c559..fae1b034 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PADD -PADD (formerly Chronometer2) is a more expansive version of the original chronometer.sh that is included with [Pi-Hole](https://pi-hole.net). PADD provides in-depth information about your Pi-hole. +PADD (formerly Chronometer2) is a more expansive version of the original chronometer.sh that was included with [Pi-Hole](https://pi-hole.net). PADD provides in-depth information about your Pi-hole. ![PADD Screenshot](https://pi-hole.github.io/graphics/Screenshots/padd.png) @@ -27,6 +27,11 @@ PADD (formerly Chronometer2) is a more expansive version of the original chronom ```bash sudo chmod +x padd.sh ``` +### Dependencies + - curl + - jq + - dig + - tput ## Using PADD @@ -40,7 +45,7 @@ PADD (formerly Chronometer2) is a more expansive version of the original chronom ### PADD from other machine -With PADD v4.0.0 and Pi-hole v6 it is also possible to run PADD from a machine that is not running Pi-hole +- With PADD v4.0.0 and Pi-hole v6 it is also possible to run PADD from a machine that is not running Pi-hole ```bash ./padd.sh --server @@ -50,7 +55,7 @@ With PADD v4.0.0 and Pi-hole v6 it is also possible to run PADD from a machine t Pi-hole v6 uses a completely new API with a new authentication mechanism -If you run PADD on the same machine as Pi-hole, it's possible to bypass authentication when your local user is member of the `pihole` group (specifically, if you can access `/etc/pihole/cli_pw). +If you run PADD on the same machine as Pi-hole, it's possible to bypass authentication when your local user is member of the `pihole` group (specifically, if you can access `/etc/pihole/cli_pw`). For details see [https://github.com/pi-hole/FTL/pull/1999](https://github.com/pi-hole/FTL/pull/1999) If this is not the case, PADD will ask you for your password and (if configured) your two factor authentication token. You can also pass those as arguments diff --git a/padd.sh b/padd.sh index 8fa5ef86..4c184f4f 100755 --- a/padd.sh +++ b/padd.sh @@ -1,9 +1,4 @@ #!/usr/bin/env sh -# shellcheck disable=SC1091 - -# Ignore warning about `local` being undefinded in POSIX -# shellcheck disable=SC3043 -# https://github.com/koalaman/shellcheck/wiki/SC3043#exceptions # PADD # A more advanced version of the chronometer provided with Pihole @@ -25,6 +20,9 @@ LastCheckNetworkInformation=$(date +%s) # padd_data holds the data returned by FTL's /padd endpoint globally padd_data="" +# should PADD run only once? +runOnce=false + # COLORS CSI="$(printf '\033')[" # Control Sequence Introducer red_text="${CSI}91m" # Red @@ -92,41 +90,44 @@ tiny_status_dns_down="${check_box_bad} DNS is off!" padd_text="${green_text}${bold_text}PADD${reset_text}" # PADD logos - regular and retro -padd_logo_1="${bold_text}${green_text} __ __ __ ${reset_text}" -padd_logo_2="${bold_text}${green_text}|__) /\\ | \\| \\ ${reset_text}" -padd_logo_3="${bold_text}${green_text}| /--\\|__/|__/ ${reset_text}" -padd_logo_retro_1="${bold_text} ${yellow_text}_${green_text}_ ${blue_text}_${magenta_text}_ ${yellow_text}_${green_text}_ ${reset_text}" -padd_logo_retro_2="${bold_text}${yellow_text}|${green_text}_${blue_text}_${cyan_text}) ${red_text}/${yellow_text}\\ ${blue_text}| ${red_text}\\${yellow_text}| ${cyan_text}\\ ${reset_text}" -padd_logo_retro_3="${bold_text}${green_text}| ${red_text}/${yellow_text}-${green_text}-${blue_text}\\${cyan_text}|${magenta_text}_${red_text}_${yellow_text}/${green_text}|${blue_text}_${cyan_text}_${magenta_text}/ ${reset_text}" +padd_logo_1="${bold_text} ${yellow_text}_${green_text}_ ${blue_text}_${magenta_text}_ ${yellow_text}_${green_text}_ ${reset_text}" +padd_logo_2="${bold_text}${yellow_text}|${green_text}_${blue_text}_${cyan_text}) ${red_text}/${yellow_text}\\ ${blue_text}| ${red_text}\\${yellow_text}| ${cyan_text}\\ ${reset_text}" +padd_logo_3="${bold_text}${green_text}| ${red_text}/${yellow_text}-${green_text}-${blue_text}\\${cyan_text}|${magenta_text}_${red_text}_${yellow_text}/${green_text}|${blue_text}_${cyan_text}_${magenta_text}/ ${reset_text}" ############################################# FTL ################################################## TestAPIAvailability() { - local chaos_api_list authResponse cmdResult digReturnCode authStatus authData + local chaos_api_list authResponse cmdResult digReturnCode authStatus authData apiAvailable - # Query the API URLs from FTL using CHAOS TXT - # The result is a space-separated enumeration of full URLs - # e.g., "http://localhost:80/api" or "https://domain.com:443/api" - if [ -z "${SERVER}" ] || [ "${SERVER}" = "localhost" ] || [ "${SERVER}" = "127.0.0.1" ]; then - # --server was not set or set to local, assuming we're running locally - cmdResult="$(dig +short chaos txt local.api.ftl @localhost 2>&1; echo $?)" + # Check if an API location was specified with --api + if [ -n "${API_LOCATION}" ]; then + # The list of available API URLs is just the provided URL + chaos_api_list="${API_LOCATION}" else - # --server was set, try to get response from there - cmdResult="$(dig +short chaos txt domain.api.ftl @"${SERVER}" 2>&1; echo $?)" - fi + # Query the API URLs from FTL using CHAOS TXT + # The result is a space-separated enumeration of full URLs + # e.g., "http://localhost:80/api" or "https://domain.com:443/api" + if [ -z "${SERVER}" ] || [ "${SERVER}" = "localhost" ] || [ "${SERVER}" = "127.0.0.1" ]; then + # --server was not set or set to local, assuming we're running locally + cmdResult="$(dig +short chaos txt local.api.ftl @localhost 2>&1; echo $?)" + else + # --server was set, try to get response from there + cmdResult="$(dig +short chaos txt domain.api.ftl @"${SERVER}" 2>&1; echo $?)" + fi - # Gets the return code of the dig command (last line) - # We can't use${cmdResult##*$'\n'*} here as $'..' is not POSIX - digReturnCode="$(echo "${cmdResult}" | tail -n 1)" + # Gets the return code of the dig command (last line) + # We can't use${cmdResult##*$'\n'*} here as $'..' is not POSIX + digReturnCode="$(echo "${cmdResult}" | tail -n 1)" - if [ ! "${digReturnCode}" = "0" ]; then - # If the query was not successful - moveXOffset; echo "API not available. Please check server address and connectivity" - exit 1 - else - # Dig returned 0 (success), so get the actual response (first line) - chaos_api_list="$(echo "${cmdResult}" | head -n 1)" + if [ "${digReturnCode}" != "0" ]; then + # If the query was not successful + moveXOffset; echo "API not available. Please check server address and connectivity" + exit 1 + else + # Dig returned 0 (success), so get the actual response (first line) + chaos_api_list="$(echo "${cmdResult}" | head -n 1)" + fi fi # Iterate over space-separated list of URLs @@ -137,48 +138,65 @@ TestAPIAvailability() { API_URL="${API_URL%\"}" API_URL="${API_URL#\"}" - # Test if the API is available at this URL - authResponse=$(curl --connect-timeout 2 -skS -w "%{http_code}" "${API_URL}auth") + # If $SERVER is user-specified by IP, the returned API_URL might contain a domain which can't be resolved by the host + # Therefore, we substitute the domain with the IP + if [ -n "${SERVER}" ]; then + # Check if SERVER is an IPv6 + case "${SERVER}" in + *:*) + # Replace the domain with the IP + # Add square brackets for IPv6 (as recommended by RFC2732) + API_URL=$(echo "${API_URL}" | sed -E "s#(https?://)[^/:]+(:[0-9]+)#\1[${SERVER}]\2#");; + *) + # Replace the domain with the IP + API_URL=$(echo "${API_URL}" | sed -E "s#(https?://)[^/:]+(:[0-9]+)#\1${SERVER}\2#");; + esac + fi - # authStatus are the last 3 characters - # not using ${authResponse#"${authResponse%???}"}" here because it's extremely slow on big responses - authStatus=$(printf "%s" "${authResponse}" | tail -c 3) - # data is everything from response without the last 3 characters - authData=$(printf %s "${authResponse%???}") + # Test if the API is available at this URL, include delimiter for ease in splitting payload + authResponse=$(curl --connect-timeout 2 -skS -w ">>%{http_code}" "${API_URL}auth") - # Test if http status code was 200 (OK) or 401 (authentication required) - if [ ! "${authStatus}" = 200 ] && [ ! "${authStatus}" = 401 ]; then - # API is not available at this port/protocol combination - API_PORT="" - else - # API is available at this URL combination + # authStatus is the response http_code, eg. 200, 401. + # Shell parameter expansion, remove everything up to and including the >> delim + authStatus=${authResponse#*>>} + # data is everything from response + # Shell parameter expansion, remove the >> delim and everything after + authData=${authResponse%>>*} - if [ "${authStatus}" = 200 ]; then - # API is available without authentication - needAuth=false - fi + # Test if http status code was 200 (OK) or 401 (authentication required) + if [ "${authStatus}" = 200 ]; then + # API is available without authentication + apiAvailable=true + needAuth=false + break + elif [ "${authStatus}" = 401 ]; then + # API is available with authentication + apiAvailable=true + needAuth=true # Check if 2FA is required needTOTP=$(echo "${authData}"| jq --raw-output .session.totp 2>/dev/null) - break - fi + else + # API is not available at this port/protocol combination + apiAvailable=false - # Remove the first URL from the list - local last_api_list - last_api_list="${chaos_api_list}" - chaos_api_list="${chaos_api_list#* }" + # Remove the first URL from the list + local last_api_list + last_api_list="${chaos_api_list}" + chaos_api_list="${chaos_api_list#* }" - # If the list did not change, we are at the last element - if [ "${last_api_list}" = "${chaos_api_list}" ]; then - # Remove the last element - chaos_api_list="" + # If the list did not change, we are at the last element + if [ "${last_api_list}" = "${chaos_api_list}" ]; then + # Remove the last element + chaos_api_list="" + fi fi done - # if API_PORT is empty, no working API port was found - if [ -n "${API_PORT}" ]; then - moveXOffset; echo "API not available at: ${API_URL}" + # if apiAvailable is false, no working API was found + if [ "${apiAvailable}" = false ]; then + moveXOffset; echo "API not available. Please check FTL.log" moveXOffset; echo "Exiting." exit 1 fi @@ -191,11 +209,14 @@ LoginAPI() { return fi - # Try to read the CLI password (if enabled and readable by the current user) - if [ -r /etc/pihole/cli_pw ]; then - password=$(cat /etc/pihole/cli_pw) - # If we can read the CLI password, we can skip 2FA even when it's required otherwise - needTOTP=false + # Check if we are running locally before checking for the CLI password + if [ -z "${SERVER}" ] || [ "${SERVER}" = "localhost" ] || [ "${SERVER}" = "127.0.0.1" ]; then + # Try to read the CLI password (if enabled and readable by the current user) + if [ -r /etc/pihole/cli_pw ]; then + password=$(cat /etc/pihole/cli_pw) + # If we can read the CLI password, we can skip 2FA even when it's required otherwise + needTOTP=false + fi fi if [ -z "${password}" ]; then @@ -216,15 +237,15 @@ LoginAPI() { Authenticate # Try to login again until the session is valid - while [ ! "${validSession}" = true ] ; do + while [ "${validSession}" != true ] ; do moveXOffset; echo "Authentication failed." # Print the error message if there is one - if [ ! "${sessionError}" = "null" ]; then + if [ "${sessionError}" != "null" ]; then moveXOffset; echo "Error: ${sessionError}" fi # Print the session message if there is one - if [ ! "${sessionMessage}" = "null" ]; then + if [ "${sessionMessage}" != "null" ]; then moveXOffset; echo "Error: ${sessionMessage}" fi @@ -251,7 +272,7 @@ LoginAPI() { DeleteSession() { # if a valid Session exists (no password required or successful authenthication) and # SID is not null (successful authenthication only), delete the session - if [ "${validSession}" = true ] && [ ! "${SID}" = null ]; then + if [ "${validSession}" = true ] && [ "${SID}" != null ]; then # Try to delete the session. Omit the output, but get the http status code deleteResponse=$(curl --connect-timeout 2 -skS -o /dev/null -w "%{http_code}" -X DELETE "${API_URL}auth" -H "Accept: application/json" -H "sid: ${SID}") @@ -261,82 +282,83 @@ DeleteSession() { "401") moveXOffset; printf "%b" "Logout attempt without a valid session. Unauthorized!\n";; esac; else - # no session to delete, just print a newline for nicer output - echo + # no session to delete, just print a newline for nicer output + echo fi } Authenticate() { - sessionResponse="$(curl --connect-timeout 2 -skS -X POST "${API_URL}auth" --user-agent "PADD ${padd_version}" --data "{\"password\":\"${password}\", \"totp\":${totp:-null}}" )" + sessionResponse="$(curl --connect-timeout 2 -skS -X POST "${API_URL}auth" --user-agent "PADD ${padd_version}" --data "{\"password\":\"${password}\", \"totp\":${totp:-null}}" )" - if [ -z "${sessionResponse}" ]; then - moveXOffset; echo "No response from FTL server. Please check connectivity and use the options to set the API URL" - moveXOffset; echo "Usage: $0 [--server ]" - exit 1 - fi - # obtain validity, session ID and sessionMessage from session response - validSession=$(echo "${sessionResponse}"| jq .session.valid 2>/dev/null) - SID=$(echo "${sessionResponse}"| jq --raw-output .session.sid 2>/dev/null) - sessionMessage=$(echo "${sessionResponse}"| jq --raw-output .session.message 2>/dev/null) - - # obtain the error message from the session response - sessionError=$(echo "${sessionResponse}"| jq --raw-output .error.message 2>/dev/null) + if [ -z "${sessionResponse}" ]; then + moveXOffset; echo "No response from FTL server. Please check connectivity and use the options to set the API URL" + moveXOffset; echo "Usage: $0 [--server ] or [--api ]" + exit 1 + fi + # obtain validity, session ID and sessionMessage from session response + validSession=$(echo "${sessionResponse}"| jq .session.valid 2>/dev/null) + SID=$(echo "${sessionResponse}"| jq --raw-output .session.sid 2>/dev/null) + sessionMessage=$(echo "${sessionResponse}"| jq --raw-output .session.message 2>/dev/null) + + # obtain the error message from the session response + sessionError=$(echo "${sessionResponse}"| jq --raw-output .error.message 2>/dev/null) } GetFTLData() { - local response - local data - local status - - # get the data from querying the API as well as the http status code - response=$(curl --connect-timeout 2 -sk -w "%{http_code}" -X GET "${API_URL}$1$2" -H "Accept: application/json" -H "sid: ${SID}" ) - - # status are the last 3 characters - # not using ${response#"${response%???}"}" here because it's extremely slow on big responses - status=$(printf "%s" "${response}" | tail -c 3) - # data is everything from response without the last 3 characters - data=$(printf %s "${response%???}") - - if [ "${status}" = 200 ]; then - echo "${data}" - elif [ "${status}" = 000 ]; then - # connection lost - echo "000" - elif [ "${status}" = 401 ]; then - # unauthorized - echo "401" - fi + local response + local data + local status + + # get the data from querying the API as well as the http status code, include delimiter for ease in splitting payload + response=$(curl --connect-timeout 2 -sk -w ">>%{http_code}" -X GET "${API_URL}$1$2" -H "Accept: application/json" -H "sid: ${SID}" ) + + # status is the response http_code, eg. 200, 401. + # Shell parameter expansion, remove everything up to and including the >> delim + status=${response#*>>} + # data is everything from response + # Shell parameter expansion, remove the >> delim and everything after + data=${response%>>*} + + if [ "${status}" = 200 ]; then + echo "${data}" + elif [ "${status}" = 000 ]; then + # connection lost + echo "000" + elif [ "${status}" = 401 ]; then + # unauthorized + echo "401" + fi } ############################################# GETTERS ############################################## GetPADDData() { - local response - response=$(GetFTLData "padd" "$1") - - if [ "${response}" = 000 ]; then - # connection lost - padd_data="000" - elif [ "${response}" = 401 ]; then - # unauthorized - padd_data="401" - else - # Iterate over all the leaf paths in the JSON object and creates key-value - # pairs in the format "key=value". Nested objects are flattened using the dot - # notation, e.g., { "a": { "b": 1 } } becomes "a.b=1". - # We cannot use leaf_paths here as it was deprecated in jq 1.6 and removed in - # current master - # Using "paths(scalars | true)" will return null and false values. - # We also check if the value is exactly `null` and, in this case, return the - # string "null", as jq would return an empty string for nulls. - padd_data=$(echo "$response" | jq -r 'paths(scalars | true) as $p | [$p | join(".")] + [if getpath($p)!=null then getpath($p) else "null" end] | join("=")' 2>/dev/null) - fi + local response + response=$(GetFTLData "padd" "$1") + + if [ "${response}" = 000 ]; then + # connection lost + padd_data="000" + elif [ "${response}" = 401 ]; then + # unauthorized + padd_data="401" + else + # Iterate over all the leaf paths in the JSON object and creates key-value + # pairs in the format "key=value". Nested objects are flattened using the dot + # notation, e.g., { "a": { "b": 1 } } becomes "a.b=1". + # We cannot use leaf_paths here as it was deprecated in jq 1.6 and removed in + # current master + # Using "paths(scalars | true)" will return null and false values. + # We also check if the value is exactly `null` and, in this case, return the + # string "null", as jq would return an empty string for nulls. + padd_data=$(echo "${response}" | jq -r 'paths(scalars | true) as $p | [$p | join(".")] + [if getpath($p)!=null then getpath($p) else "null" end] | join("=")' 2>/dev/null) + fi } GetPADDValue() { - echo "$padd_data" | sed -n "s/^$1=//p" 2>/dev/null + echo "${padd_data}" | sed -n "s/^$1=//p" 2>/dev/null } GetSummaryInformation() { @@ -358,33 +380,46 @@ GetSummaryInformation() { fi - clients=$(GetPADDValue active_clients) + clients=$(GetPADDValue active_clients) + + blocking_enabled=$(GetPADDValue blocking) - blocking_enabled=$(GetPADDValue blocking) + domains_being_blocked_raw=$(GetPADDValue gravity_size) + domains_being_blocked=$(printf "%.f" "${domains_being_blocked_raw}") - domains_being_blocked_raw=$(GetPADDValue gravity_size) - domains_being_blocked=$(printf "%.f" "${domains_being_blocked_raw}") + dns_queries_today_raw=$(GetPADDValue queries.total) + dns_queries_today=$(printf "%.f" "${dns_queries_today_raw}") - dns_queries_today_raw=$(GetPADDValue queries.total) - dns_queries_today=$(printf "%.f" "${dns_queries_today_raw}") + ads_blocked_today_raw=$(GetPADDValue queries.blocked) + ads_blocked_today=$(printf "%.f" "${ads_blocked_today_raw}") - ads_blocked_today_raw=$(GetPADDValue queries.blocked) - ads_blocked_today=$(printf "%.f" "${ads_blocked_today_raw}") + ads_percentage_today_raw=$(GetPADDValue queries.percent_blocked) + ads_percentage_today=$(printf "%.1f" "${ads_percentage_today_raw}") - ads_percentage_today_raw=$(GetPADDValue queries.percent_blocked) - ads_percentage_today=$(printf "%.1f" "${ads_percentage_today_raw}") + cache_size=$(GetPADDValue cache.size) + cache_evictions=$(GetPADDValue cache.evicted) + cache_inserts=$(echo "${padd_data}"| GetPADDValue cache.inserted) - cache_size=$(GetPADDValue cache.size) - cache_evictions=$(GetPADDValue cache.evicted) - cache_inserts=$(echo "${padd_data}"| GetPADDValue cache.inserted) + latest_blocked_raw=$(GetPADDValue recent_blocked) - latest_blocked_raw=$(GetPADDValue recent_blocked) + top_blocked_raw=$(GetPADDValue top_blocked) - top_blocked_raw=$(GetPADDValue top_blocked) + top_domain_raw=$(GetPADDValue top_domain) - top_domain_raw=$(GetPADDValue top_domain) + top_client_raw=$(GetPADDValue top_client) + + privacy_level=$(GetPADDValue config.privacy_level) + + # Substitute 'null' values returned by FTL for privacy level >1 + if [ "${privacy_level}" -ge "1" ]; then + top_domain_raw="hidden by privacy level" + top_blocked_raw="hidden by privacy level" + latest_blocked_raw="hidden by privacy level" + fi + if [ "${privacy_level}" -ge "2" ]; then + top_client_raw="hidden by privacy level" + fi - top_client_raw=$(GetPADDValue top_client) } GetSystemInformation() { @@ -414,27 +449,33 @@ GetSystemInformation() { # CPU temperature and unit cpu_temp_raw=$(GetPADDValue sensors.cpu_temp) - cpu_temp=$(printf "%.1f" "${cpu_temp_raw}") - temp_unit=$(echo "${padd_data}" | GetPADDValue sensors.unit) + if [ "${cpu_temp_raw}" != null ]; then + cpu_temp=$(printf "%.1f" "${cpu_temp_raw}") + temp_unit=$(echo "${padd_data}" | GetPADDValue sensors.unit) + fi # Temp + Unit if [ "${temp_unit}" = "C" ]; then temperature="${cpu_temp}°${temp_unit}" # no conversion needed cpu_temp_celsius="$(echo "${cpu_temp}" | awk -F '.' '{print $1}')" + temp_unicode=true elif [ "${temp_unit}" = "F" ]; then temperature="${cpu_temp}°${temp_unit}" # convert to Celsius for limit checking cpu_temp_celsius="$(echo "${cpu_temp}" | awk '{print ($1-32) * 5 / 9}' | awk -F '.' '{print $1}')" + temp_unicode=true elif [ "${temp_unit}" = "K" ]; then # no ° for Kelvin temperature="${cpu_temp}${temp_unit}" # convert to Celsius for limit checking cpu_temp_celsius="$(echo "${cpu_temp}" | awk '{print $1 - 273.15}' | awk -F '.' '{print $1}')" + temp_unicode=false else # unknown unit - temperature="${cpu_temp}°?" + temperature="N/A" # no conversion needed - cpu_temp_celsius=0 + cpu_temp_celsius=-274 + temp_unicode=false fi # CPU temperature heatmap @@ -448,8 +489,10 @@ GetSystemInformation() { temp_heatmap=${magenta_text} elif [ "${cpu_temp_celsius}" -gt 60 ]; then temp_heatmap=${blue_text} - else + elif [ "${cpu_temp_celsius}" -gt -274 ]; then temp_heatmap=${cyan_text} + else + temp_heatmap=${reset_text} fi # CPU, load, heatmap @@ -471,7 +514,7 @@ GetSystemInformation() { sys_model="$(GetPADDValue host_model)" # DOCKER_VERSION is set during GetVersionInformation, so this needs to run first during startup - if [ ! "${DOCKER_VERSION}" = "null" ]; then + if [ "${DOCKER_VERSION}" != "null" ]; then # Docker image sys_model="Container" fi @@ -480,7 +523,7 @@ GetSystemInformation() { sys_model=$(filterModel "${sys_model}") # FTL returns null if device information is not available - if [ -z "$sys_model" ] || [ "$sys_model" = "null" ]; then + if [ -z "${sys_model}" ] || [ "${sys_model}" = "null" ]; then sys_model="N/A" fi } @@ -500,6 +543,7 @@ GetNetworkInformation() { dhcp_range_heatmap=${reset_text} dhcp_ipv6_status="N/A" dhcp_ipv6_heatmap=${reset_text} + dhcp_check_box=${check_box_question} pi_hostname="N/A" full_hostname="N/A" @@ -649,7 +693,7 @@ GetNetworkInformation() { # name to highlight that there are two different interfaces and the # displayed statistics are only for the IPv4 interface, while the IPv6 # address correctly corresponds to the default IPv6 interface - if [ ! "${gateway_v4_iface}" = "${gateway_v6_iface}" ]; then + if [ "${gateway_v4_iface}" != "${gateway_v6_iface}" ]; then iface_name="${iface_name}*" fi } @@ -685,19 +729,19 @@ GetPiholeInformation() { - # ${ftl_dns_port} == 0 DNS server part of dnsmasq disabled - dns_down_flag=false - if [ "${ftl_dns_port}" = 0 ]; then - dns_status="DNS offline" - dns_heatmap=${red_text} - dns_check_box=${check_box_bad} - # set flag to change the status message in SetStatusMessage() - dns_down_flag=true - else - dns_check_box=${check_box_good} - dns_status="Active" - dns_heatmap=${green_text} -fi + # ${ftl_dns_port} == 0 DNS server part of dnsmasq disabled + dns_down_flag=false + if [ "${ftl_dns_port}" = 0 ]; then + dns_status="DNS offline" + dns_heatmap=${red_text} + dns_check_box=${check_box_bad} + # set flag to change the status message in SetStatusMessage() + dns_down_flag=true + else + dns_check_box=${check_box_good} + dns_status="Active" + dns_heatmap=${green_text} + fi } GetVersionInformation() { @@ -719,7 +763,7 @@ GetVersionInformation() { DOCKER_VERSION="$(GetPADDValue version.docker.local)" # If PADD is running inside docker, immediately return without checking for updated component versions - if [ ! "${DOCKER_VERSION}" = "null" ] ; then + if [ "${DOCKER_VERSION}" != "null" ] ; then GITHUB_DOCKER_VERSION="$(GetPADDValue version.docker.remote)" docker_version_converted="$(VersionConverter "${DOCKER_VERSION}")" docker_version_latest_converted="$(VersionConverter "${GITHUB_DOCKER_VERSION}")" @@ -739,7 +783,7 @@ GetVersionInformation() { # Gather core version information... CORE_BRANCH="$(GetPADDValue version.core.local.branch)" CORE_VERSION="$(GetPADDValue version.core.local.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')" - GITHUB_CORE_VERSION="$(GetPADDValue version.core.remmote.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')" + GITHUB_CORE_VERSION="$(GetPADDValue version.core.remote.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')" CORE_HASH="$(GetPADDValue version.core.local.hash)" GITHUB_CORE_HASH="$(GetPADDValue version.core.remote.hash)" @@ -770,17 +814,17 @@ GetVersionInformation() { fi # shorten common branch names (fix/, tweak/, new/) # use the first 7 characters of the branch name as version - CORE_VERSION="$(printf '%s' "$CORE_BRANCH" | sed 's/fix\//f\//;s/new\//n\//;s/tweak\//t\//' | cut -c 1-7)" + CORE_VERSION="$(printf '%s' "${CORE_BRANCH}" | sed 's/fix\//f\//;s/new\//n\//;s/tweak\//t\//' | cut -c 1-7)" fi fi # Gather web version information... WEB_VERSION="$(GetPADDValue version.web.local.version)" - if [ ! "$WEB_VERSION" = "null" ]; then + if [ "${WEB_VERSION}" != "null" ]; then WEB_BRANCH="$(GetPADDValue version.web.local.branch)" WEB_VERSION="$(GetPADDValue version.web.local.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')" - GITHUB_WEB_VERSION="$(GetPADDValue version.web.remmote.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')" + GITHUB_WEB_VERSION="$(GetPADDValue version.web.remote.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')" WEB_HASH="$(GetPADDValue version.web.local.hash)" GITHUB_WEB_HASH="$(GetPADDValue version.web.remote.hash)" @@ -812,7 +856,7 @@ GetVersionInformation() { fi # shorten common branch names (fix/, tweak/, new/) # use the first 7 characters of the branch name as version - WEB_VERSION="$(printf '%s' "$WEB_BRANCH" | sed 's/fix\//f\//;s/new\//n\//;s/tweak\//t\//' | cut -c 1-7)" + WEB_VERSION="$(printf '%s' "${WEB_BRANCH}" | sed 's/fix\//f\//;s/new\//n\//;s/tweak\//t\//' | cut -c 1-7)" fi fi else @@ -824,7 +868,7 @@ GetVersionInformation() { # Gather FTL version information... FTL_BRANCH="$(GetPADDValue version.ftl.local.branch)" FTL_VERSION="$(GetPADDValue version.ftl.local.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')" - GITHUB_FTL_VERSION="$(GetPADDValue version.ftl.remmote.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')" + GITHUB_FTL_VERSION="$(GetPADDValue version.ftl.remote.version | tr -d '[:alpha:]' | awk -F '-' '{printf $1}')" FTL_HASH="$(GetPADDValue version.ftl.local.hash)" GITHUB_FTL_HASH="$(GetPADDValue version.ftl.remote.hash)" @@ -856,96 +900,104 @@ GetVersionInformation() { fi # shorten common branch names (fix/, tweak/, new/) # use the first 7 characters of the branch name as version - FTL_VERSION="$(printf '%s' "$FTL_BRANCH" | sed 's/fix\//f\//;s/new\//n\//;s/tweak\//t\//' | cut -c 1-7)" + FTL_VERSION="$(printf '%s' "${FTL_BRANCH}" | sed 's/fix\//f\//;s/new\//n\//;s/tweak\//t\//' | cut -c 1-7)" fi fi } GetPADDInformation() { - # If PADD is running inside docker, immediately return without checking for an update - if [ ! "${DOCKER_VERSION}" = "null" ]; then - return - fi - - # PADD version information... - padd_version_latest="$(curl --connect-timeout 5 --silent https://api.github.com/repos/pi-hole/PADD/releases/latest | grep '"tag_name":' | awk -F \" '{print $4}')" - # is PADD up-to-date? - padd_out_of_date_flag=false - if [ -z "${padd_version_latest}" ]; then - padd_version_heatmap=${yellow_text} - else - padd_version_latest_converted="$(VersionConverter "${padd_version_latest}")" - padd_version_converted=$(VersionConverter "${padd_version}") - - if [ "${padd_version_converted}" -lt "${padd_version_latest_converted}" ]; then - padd_out_of_date_flag="true" - padd_version_heatmap=${red_text} + + # PADD version information... + padd_version_latest="$(curl --connect-timeout 5 --silent https://api.github.com/repos/pi-hole/PADD/releases/latest | grep '"tag_name":' | awk -F \" '{print $4}')" + # is PADD up-to-date? + padd_out_of_date_flag=false + if [ -z "${padd_version_latest}" ]; then + padd_version_heatmap=${yellow_text} else - # local and remote PADD version match or local is newer - padd_version_heatmap=${green_text} + padd_version_latest_converted="$(VersionConverter "${padd_version_latest}")" + padd_version_converted=$(VersionConverter "${padd_version}") + + if [ "${padd_version_converted}" -lt "${padd_version_latest_converted}" ]; then + padd_out_of_date_flag="true" + padd_version_heatmap=${red_text} + else + # local and remote PADD version match or local is newer + padd_version_heatmap=${green_text} + fi fi - fi } GenerateSizeDependendOutput() { - if [ "$1" = "pico" ] || [ "$1" = "nano" ]; then - ads_blocked_bar=$(BarGenerator "$ads_percentage_today" 9 "color") - - elif [ "$1" = "micro" ]; then - ads_blocked_bar=$(BarGenerator "$ads_percentage_today" 10 "color") - - elif [ "$1" = "mini" ]; then - ads_blocked_bar=$(BarGenerator "$ads_percentage_today" 20 "color") - - latest_blocked=$(truncateString "$latest_blocked_raw" 29) - top_blocked=$(truncateString "$top_blocked_raw" 29) - - elif [ "$1" = "tiny" ]; then - ads_blocked_bar=$(BarGenerator "$ads_percentage_today" 30 "color") - - latest_blocked=$(truncateString "$latest_blocked_raw" 41) - top_blocked=$(truncateString "$top_blocked_raw" 41) - top_domain=$(truncateString "$top_domain_raw" 41) - top_client=$(truncateString "$top_client_raw" 41) - - elif [ "$1" = "regular" ] || [ "$1" = "slim" ]; then - ads_blocked_bar=$(BarGenerator "$ads_percentage_today" 40 "color") - - latest_blocked=$(truncateString "$latest_blocked_raw" 48) - top_blocked=$(truncateString "$top_blocked_raw" 48) - top_domain=$(truncateString "$top_domain_raw" 48) - top_client=$(truncateString "$top_client_raw" 48) - - - elif [ "$1" = "mega" ]; then - ads_blocked_bar=$(BarGenerator "$ads_percentage_today" 30 "color") - - latest_blocked=$(truncateString "$latest_blocked_raw" 68) - top_blocked=$(truncateString "$top_blocked_raw" 68) - top_domain=$(truncateString "$top_domain_raw" 68) - top_client=$(truncateString "$top_client_raw" 68) - - fi - - # System uptime - if [ "$1" = "pico" ] || [ "$1" = "nano" ] || [ "$1" = "micro" ]; then - system_uptime="$(convertUptime "${system_uptime_raw}" | awk -F ',' '{print $1 "," $2}')" - else - system_uptime="$(convertUptime "${system_uptime_raw}")" - fi - - # Bar generations - if [ "$1" = "mini" ]; then - cpu_bar=$(BarGenerator "${cpu_percent}" 20) - memory_bar=$(BarGenerator "${memory_percent}" 20) - elif [ "$1" = "tiny" ]; then - cpu_bar=$(BarGenerator "${cpu_percent}" 7) - memory_bar=$(BarGenerator "${memory_percent}" 7) - else - cpu_bar=$(BarGenerator "${cpu_percent}" 10) - memory_bar=$(BarGenerator "${memory_percent}" 10) - fi + + if [ "$1" = "pico" ] || [ "$1" = "nano" ]; then + ads_blocked_bar=$(BarGenerator "${ads_percentage_today}" 9 "color") + + elif [ "$1" = "micro" ]; then + ads_blocked_bar=$(BarGenerator "${ads_percentage_today}" 10 "color") + + elif [ "$1" = "mini" ]; then + ads_blocked_bar=$(BarGenerator "${ads_percentage_today}" 20 "color") + + latest_blocked=$(truncateString "${latest_blocked_raw}" 29) + top_blocked=$(truncateString "${top_blocked_raw}" 29) + + elif [ "$1" = "tiny" ]; then + ads_blocked_bar=$(BarGenerator "${ads_percentage_today}" 30 "color") + + latest_blocked=$(truncateString "${latest_blocked_raw}" 41) + top_blocked=$(truncateString "${top_blocked_raw}" 41) + top_domain=$(truncateString "${top_domain_raw}" 41) + top_client=$(truncateString "${top_client_raw}" 41) + + elif [ "$1" = "regular" ] || [ "$1" = "slim" ]; then + ads_blocked_bar=$(BarGenerator "${ads_percentage_today}" 40 "color") + + latest_blocked=$(truncateString "${latest_blocked_raw}" 48) + top_blocked=$(truncateString "${top_blocked_raw}" 48) + top_domain=$(truncateString "${top_domain_raw}" 48) + top_client=$(truncateString "${top_client_raw}" 48) + + if [ "${temp_unicode}" = true ]; then + temp_padding=21 + else + temp_padding=20 + fi + + elif [ "$1" = "mega" ]; then + ads_blocked_bar=$(BarGenerator "${ads_percentage_today}" 30 "color") + + latest_blocked=$(truncateString "${latest_blocked_raw}" 68) + top_blocked=$(truncateString "${top_blocked_raw}" 68) + top_domain=$(truncateString "${top_domain_raw}" 68) + top_client=$(truncateString "${top_client_raw}" 68) + + if [ "${temp_unicode}" = true ]; then + temp_padding=10 + else + temp_padding=9 + fi + + fi + + # System uptime + if [ "$1" = "pico" ] || [ "$1" = "nano" ] || [ "$1" = "micro" ]; then + system_uptime="$(convertUptime "${system_uptime_raw}" | awk -F ',' '{print $1 "," $2}')" + else + system_uptime="$(convertUptime "${system_uptime_raw}")" + fi + + # Bar generations + if [ "$1" = "mini" ]; then + cpu_bar=$(BarGenerator "${cpu_percent}" 20) + memory_bar=$(BarGenerator "${memory_percent}" 20) + elif [ "$1" = "tiny" ]; then + cpu_bar=$(BarGenerator "${cpu_percent}" 7) + memory_bar=$(BarGenerator "${memory_percent}" 7) + else + cpu_bar=$(BarGenerator "${cpu_percent}" 10) + memory_bar=$(BarGenerator "${memory_percent}" 10) + fi } SetStatusMessage() { @@ -1014,43 +1066,28 @@ SetStatusMessage() { ############################################# PRINTERS ############################################# PrintLogo() { - if [ ! "${DOCKER_VERSION}" = "null" ]; then - version_info="Docker ${docker_version_heatmap}${DOCKER_VERSION}${reset_text}" + # Screen size checks + if [ "$1" = "pico" ]; then + moveXOffset; printf "%s${clear_line}\n" "p${padd_text}" + elif [ "$1" = "nano" ]; then + moveXOffset; printf "%s${clear_line}\n" "n${padd_text}" + elif [ "$1" = "micro" ]; then + moveXOffset; printf "%s${clear_line}\n\n" "µ${padd_text}" + elif [ "$1" = "mini" ]; then + moveXOffset; printf "%s${clear_line}\n" "${padd_text}${dim_text}mini${reset_text} Pi-hole® Ad Detection Display" + moveXOffset; printf "%s${clear_line}\n" " A client for Pi-hole®" else - version_info="Pi-hole® ${core_version_heatmap}${CORE_VERSION}${reset_text}, Web ${web_version_heatmap}${WEB_VERSION}${reset_text}, FTL ${ftl_version_heatmap}${FTL_VERSION}${reset_text}" - fi - - # Screen size checks - if [ "$1" = "pico" ]; then - printf "%s${clear_line}\n" "p${padd_text} ${pico_status}" - elif [ "$1" = "nano" ]; then - printf "%s${clear_line}\n" "n${padd_text} ${mini_status}" - elif [ "$1" = "micro" ]; then - printf "%s${clear_line}\n${clear_line}\n" "µ${padd_text} ${mini_status}" - elif [ "$1" = "mini" ]; then - printf "%s${clear_line}\n${clear_line}\n" "${padd_text}${dim_text}mini${reset_text} ${mini_status}" - elif [ "$1" = "tiny" ]; then - printf "%s${clear_line}\n" "${padd_text}${dim_text}tiny${reset_text} ${version_info}${reset_text}" - printf "%s${clear_line}\n" " PADD ${padd_version_heatmap}${padd_version}${reset_text} ${tiny_status}${reset_text}" - elif [ "$1" = "slim" ]; then - printf "%s${clear_line}\n${clear_line}\n" "${padd_text}${dim_text}slim${reset_text} ${full_status}" - elif [ "$1" = "regular" ] || [ "$1" = "slim" ]; then - printf "%s${clear_line}\n" "${padd_logo_1}" - printf "%s${clear_line}\n" "${padd_logo_2}${version_info}${reset_text}" - printf "%s${clear_line}\n${clear_line}\n" "${padd_logo_3}PADD ${padd_version_heatmap}${padd_version}${reset_text} ${full_status}${reset_text}" - # normal or not defined - else - printf "%s${clear_line}\n" "${padd_logo_retro_1}" - printf "%s${clear_line}\n" "${padd_logo_retro_2} ${version_info}, PADD ${padd_version_heatmap}${padd_version}${reset_text}" - printf "%s${clear_line}\n${clear_line}\n" "${padd_logo_retro_3} ${dns_check_box} DNS ${ftl_check_box} FTL ${mega_status}${reset_text}" - fi + moveXOffset; printf "%b" "${padd_logo_1}\n" + moveXOffset; printf "%b" "${padd_logo_2}Pi-hole® Ad Detection Display\n" + moveXOffset; printf "%b" "${padd_logo_3}A client for Pi-hole\n\n" + fi } PrintDashboard() { - if [ ! "${DOCKER_VERSION}" = "null" ]; then - version_info="Docker ${docker_version_heatmap}${DOCKER_VERSION}${reset_text}" + if [ "${DOCKER_VERSION}" != "null" ]; then + version_info="Docker ${docker_version_heatmap}${DOCKER_VERSION}${reset_text}" else - version_info="Pi-hole® ${core_version_heatmap}${CORE_VERSION}${reset_text}, Web ${web_version_heatmap}${WEB_VERSION}${reset_text}, FTL ${ftl_version_heatmap}${FTL_VERSION}${reset_text}" + version_info="Pi-hole® ${core_version_heatmap}${CORE_VERSION}${reset_text}, Web ${web_version_heatmap}${WEB_VERSION}${reset_text}, FTL ${ftl_version_heatmap}${FTL_VERSION}${reset_text}" fi # Move cursor to (0,0). printf '\e[H' @@ -1140,7 +1177,7 @@ PrintDashboard() { moveXOffset; printf " %-10s%-16s %-4s%-7s %-4s%-5s${clear_line}\n" "Interfce:" "${iface_name}" "TX:" "${tx_bytes}" "RX:" "${rx_bytes}" moveXOffset; printf " %-10s%-16s %-8s${dnssec_heatmap}%-16s${reset_text}${clear_line}\n" "DNS:" "${dns_information}" "DNSSEC:" "${dnssec_status}" moveXOffset; printf " %-10s%s${clear_line}\n" "IPv6:" "${pi_ip6_addr}" - moveXOffset; printf " %-10s%-15s%-4s${dhcp_range_heatmap}%-36s${reset_text}${clear_line}\n" "DHCP:" "${dhcp_check_box}" "Rng" "${dhcp_range}" + moveXOffset; printf " %-10s%-5s %-4s${dhcp_range_heatmap}%-33s${reset_text}${clear_line}\n" "DHCP:" "${dhcp_check_box}" "Rng" "${dhcp_range}" moveXOffset; printf "%s${clear_line}\n" "${bold_text}SYSTEM ==============================================${reset_text}" moveXOffset; printf " %-10s%-29s${clear_line}\n" "Uptime:" "${system_uptime}" moveXOffset; printf " %-10s${temp_heatmap}%-17s${reset_text} %-8s${cpu_load_1_heatmap}%-4s${reset_text}, ${cpu_load_5_heatmap}%-4s${reset_text}, ${cpu_load_15_heatmap}%-4s${reset_text}${clear_line}\n" "CPU Temp:" "${temperature}" "Load:" "${cpu_load_1}" "${cpu_load_5}" "${cpu_load_15}" @@ -1174,13 +1211,13 @@ PrintDashboard() { moveXOffset; printf " %-10s%-16s%-6s${dhcp_range_heatmap}%-36s${reset_text}${clear_line}\n" "DHCP:" "${dhcp_check_box}" "Range" "${dhcp_range}" moveXOffset; printf "%s${clear_line}\n" "${bold_text}SYSTEM =====================================================${reset_text}" moveXOffset; printf " %-10s%-39s${clear_line}\n" "Uptime:" "${system_uptime}" - moveXOffset; printf " %-10s${temp_heatmap}%-21s${reset_text}%-10s${cpu_load_1_heatmap}%-4s${reset_text}, ${cpu_load_5_heatmap}%-4s${reset_text}, ${cpu_load_15_heatmap}%-4s${reset_text}${clear_line}\n" "CPU Temp:" "${temperature}" "CPU Load:" "${cpu_load_1}" "${cpu_load_5}" "${cpu_load_15}" + moveXOffset; printf " %-10s${temp_heatmap}%-""${temp_padding}""s${reset_text}%-10s${cpu_load_1_heatmap}%-4s${reset_text}, ${cpu_load_5_heatmap}%-4s${reset_text}, ${cpu_load_15_heatmap}%-4s${reset_text}${clear_line}\n" "CPU Temp:" "${temperature}" "CPU Load:" "${cpu_load_1}" "${cpu_load_5}" "${cpu_load_15}" moveXOffset; printf " %-10s[${memory_heatmap}%-10s${reset_text}] %-6s %-10s[${cpu_load_1_heatmap}%-10s${reset_text}] %-5s${clear_line}" "Memory:" "${memory_bar}" "${memory_percent}%" "CPU Load:" "${cpu_bar}" "${cpu_percent}%" else # ${padd_size} = mega # mega is a screen with at least 80 columns and 26 lines - moveXOffset; printf "%s${clear_line}\n" "${padd_logo_retro_1}" - moveXOffset; printf "%s${clear_line}\n" "${padd_logo_retro_2} ${version_info}, PADD ${padd_version_heatmap}${padd_version}${reset_text}" - moveXOffset; printf "%s${clear_line}\n" "${padd_logo_retro_3} ${dns_check_box} DNS ${ftl_check_box} FTL ${mega_status}${reset_text}" + moveXOffset; printf "%s${clear_line}\n" "${padd_logo_1}" + moveXOffset; printf "%s${clear_line}\n" "${padd_logo_2} ${version_info}, PADD ${padd_version_heatmap}${padd_version}${reset_text}" + moveXOffset; printf "%s${clear_line}\n" "${padd_logo_3} ${dns_check_box} DNS ${ftl_check_box} FTL ${mega_status}${reset_text}" moveXOffset; printf "%s${clear_line}\n" "" moveXOffset; printf "%s${clear_line}\n" "${bold_text}STATS ==========================================================================${reset_text}" moveXOffset; printf " %-10s%-19s %-10s[%-40s] %-5s${clear_line}\n" "Blocking:" "${domains_being_blocked} domains" "Piholed:" "${ads_blocked_bar}" "${ads_percentage_today}%" @@ -1203,7 +1240,7 @@ PrintDashboard() { moveXOffset; printf "%s${clear_line}\n" "${bold_text}SYSTEM =========================================================================${reset_text}" moveXOffset; printf " %-10s%-39s${clear_line}\n" "Device:" "${sys_model}" moveXOffset; printf " %-10s%-39s %-10s[${memory_heatmap}%-10s${reset_text}] %-6s${clear_line}\n" "Uptime:" "${system_uptime}" "Memory:" "${memory_bar}" "${memory_percent}%" - moveXOffset; printf " %-10s${temp_heatmap}%-10s${reset_text} %-10s${cpu_load_1_heatmap}%-4s${reset_text}, ${cpu_load_5_heatmap}%-4s${reset_text}, ${cpu_load_15_heatmap}%-7s${reset_text} %-10s[${memory_heatmap}%-10s${reset_text}] %-6s${clear_line}" "CPU Temp:" "${temperature}" "CPU Load:" "${cpu_load_1}" "${cpu_load_5}" "${cpu_load_15}" "CPU Load:" "${cpu_bar}" "${cpu_percent}%" + moveXOffset; printf " %-10s${temp_heatmap}%-""${temp_padding}""s${reset_text} %-10s${cpu_load_1_heatmap}%-4s${reset_text}, ${cpu_load_5_heatmap}%-4s${reset_text}, ${cpu_load_15_heatmap}%-7s${reset_text} %-10s[${memory_heatmap}%-10s${reset_text}] %-6s${clear_line}" "CPU Temp:" "${temperature}" "CPU Load:" "${cpu_load_1}" "${cpu_load_5}" "${cpu_load_15}" "CPU Load:" "${cpu_bar}" "${cpu_percent}%" fi # Clear to end of screen (below the drawn dashboard) @@ -1216,26 +1253,26 @@ PrintDashboard() { # Provides a color based on a provided percentage # takes in one or two parameters HeatmapGenerator () { - # if one number is provided, just use that percentage to figure out the colors - if [ -z "$2" ]; then - load=$(printf "%.0f" "$1") - # if two numbers are provided, do some math to make a percentage to figure out the colors - else - load=$(printf "%.0f" "$(echo "$1 $2" | awk '{print ($1 / $2) * 100}')") - fi - - # Color logic - # |<- green ->| yellow | red -> - # 0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 - if [ "${load}" -lt 75 ]; then - out=${green_text} - elif [ "${load}" -lt 90 ]; then - out=${yellow_text} - else - out=${red_text} - fi - - echo "$out" + # if one number is provided, just use that percentage to figure out the colors + if [ -z "${2}" ]; then + load=$(printf "%.0f" "${1}") + # if two numbers are provided, do some math to make a percentage to figure out the colors + else + load=$(printf "%.0f" "$(echo "$1 $2" | awk '{print ($1 / $2) * 100}')") + fi + + # Color logic + # |<- green ->| yellow | red -> + # 0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 + if [ "${load}" -lt 75 ]; then + out=${green_text} + elif [ "${load}" -lt 90 ]; then + out=${yellow_text} + else + out=${red_text} + fi + + echo "${out}" } # Provides a "bar graph" @@ -1244,32 +1281,32 @@ HeatmapGenerator () { # $2: max length of the bar # $3: colored flag, if "color" backfill with color BarGenerator() { - # number of filled in cells in the bar - barNumber=$(printf %.f "$(echo "$1 $2" | awk '{print ($1 / 100) * $2}')") - frontFill=$(for i in $(seq "$barNumber"); do printf "%b" "■"; done) - - # remaining "unfilled" cells in the bar - backfillNumber=$(($2-barNumber)) - - # if the filled in cells is less than the max length of the bar, fill it - if [ "$barNumber" -lt "$2" ]; then - # if the bar should be colored - if [ "$3" = "color" ]; then - # fill the rest in color - backFill=$(for i in $(seq $backfillNumber); do printf "%b" "■"; done) - out="${red_text}${frontFill}${green_text}${backFill}${reset_text}" - # else, it shouldn't be colored in + # number of filled in cells in the bar + barNumber=$(printf %.f "$(echo "$1 $2" | awk '{print ($1 / 100) * $2}')") + frontFill=$(for i in $(seq "${barNumber}"); do printf "%b" "■"; done) + + # remaining "unfilled" cells in the bar + backfillNumber=$(($2-barNumber)) + + # if the filled in cells is less than the max length of the bar, fill it + if [ "${barNumber}" -lt "${2}" ]; then + # if the bar should be colored + if [ "$3" = "color" ]; then + # fill the rest in color + backFill=$(for i in $(seq ${backfillNumber}); do printf "%b" "■"; done) + out="${red_text}${frontFill}${green_text}${backFill}${reset_text}" + # else, it shouldn't be colored in + else + # fill the rest with "space" + backFill=$(for i in $(seq ${backfillNumber}); do printf "%b" "·"; done) + out="${frontFill}${reset_text}${backFill}" + fi + # else, fill it all the way else - # fill the rest with "space" - backFill=$(for i in $(seq $backfillNumber); do printf "%b" "·"; done) - out="${frontFill}${reset_text}${backFill}" + out=$(for i in $(seq "${2}"); do printf "%b" "■"; done) fi - # else, fill it all the way - else - out=$(for i in $(seq "$2"); do printf "%b" "■"; done) - fi - echo "$out" + echo "${out}" } # Checks the size of the screen and sets the value of ${padd_data}_size @@ -1283,42 +1320,42 @@ SizeChecker(){ console_height=$(tput lines) # Mega - if [ "$console_width" -ge "80" ] && [ "$console_height" -ge "26" ]; then + if [ "${console_width}" -ge "80" ] && [ "${console_height}" -ge "26" ]; then padd_size="mega" width=80 height=26 # Below Mega. Gives you Regular. - elif [ "$console_width" -ge "60" ] && [ "$console_height" -ge "22" ]; then + elif [ "${console_width}" -ge "60" ] && [ "${console_height}" -ge "22" ]; then padd_size="regular" width=60 height=22 # Below Regular. Gives you Slim. - elif [ "$console_width" -ge "60" ] && [ "$console_height" -ge "21" ]; then + elif [ "${console_width}" -ge "60" ] && [ "${console_height}" -ge "21" ]; then padd_size="slim" width=60 height=21 # Below Slim. Gives you Tiny. - elif [ "$console_width" -ge "53" ] && [ "$console_height" -ge "20" ]; then + elif [ "${console_width}" -ge "53" ] && [ "${console_height}" -ge "20" ]; then padd_size="tiny" width=53 height=20 # Below Tiny. Gives you Mini. - elif [ "$console_width" -ge "40" ] && [ "$console_height" -ge "18" ]; then + elif [ "${console_width}" -ge "40" ] && [ "${console_height}" -ge "18" ]; then padd_size="mini" width=40 height=18 # Below Mini. Gives you Micro. - elif [ "$console_width" -ge "30" ] && [ "$console_height" -ge "16" ]; then + elif [ "${console_width}" -ge "30" ] && [ "${console_height}" -ge "16" ]; then padd_size="micro" width=30 height=16 # Below Micro, Gives you Nano. - elif [ "$console_width" -ge "24" ] && [ "$console_height" -ge "12" ]; then + elif [ "${console_width}" -ge "24" ] && [ "${console_height}" -ge "12" ]; then padd_size="nano" width=24 height=12 # Below Nano. Gives you Pico. - elif [ "$console_width" -ge "20" ] && [ "$console_height" -ge "10" ]; then + elif [ "${console_width}" -ge "20" ] && [ "${console_height}" -ge "10" ]; then padd_size="pico" width=20 height=10 @@ -1332,22 +1369,22 @@ SizeChecker(){ yOffset="$(( (console_height - height) / 2 ))" # If the user sets an offset option, use it. - if [ -n "$xOffOrig" ]; then - xOffset=$xOffOrig + if [ -n "${xOffOrig}" ]; then + xOffset=${xOffOrig} # Limit the offset to avoid breaks xMaxOffset=$((console_width - width)) - if [ "$xOffset" -gt "$xMaxOffset" ]; then - xOffset="$xMaxOffset" + if [ "${xOffset}" -gt "${xMaxOffset}" ]; then + xOffset="${xMaxOffset}" fi fi - if [ -n "$yOffOrig" ]; then - yOffset=$yOffOrig + if [ -n "${yOffOrig}" ]; then + yOffset=${yOffOrig} # Limit the offset to avoid breaks yMaxOffset=$((console_height - height)) - if [ "$yOffset" -gt "$yMaxOffset" ]; then - yOffset="$yMaxOffset" + if [ "${yOffset}" -gt "${yMaxOffset}" ]; then + yOffset="${yMaxOffset}" fi fi } @@ -1355,7 +1392,7 @@ SizeChecker(){ # converts a given version string e.g. v3.7.1 to 3007001000 to allow for easier comparison of multi digit version numbers # credits https://apple.stackexchange.com/a/123408 VersionConverter() { - echo "$@" | tr -d '[:alpha:]' | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; + echo "$@" | tr -d '[:alpha:]' | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; } moveYOffset(){ @@ -1388,7 +1425,7 @@ filterModel() { # `-v` : set $FILTERLIST into a variable called `list` # `gsub()` : replace all list items (ignoring case) with an empty string, deleting them # `{$1=$1}1`: remove all extra spaces. The last "1" evaluates as true, printing the result - echo "$1" | awk -v list="$FILTERLIST" '{IGNORECASE=1; gsub(list,"")}; {$1=$1}1' + echo "$1" | awk -v list="${FILTERLIST}" '{IGNORECASE=1; gsub(list,"")}; {$1=$1}1' } # Truncates a given string and appends three '...' @@ -1400,10 +1437,10 @@ truncateString() { length=${#1} shorted=$(($2-3)) # shorten max allowed length by 3 to make room for the dots - if [ "${length}" -gt "$2" ]; then + if [ "${length}" -gt "${2}" ]; then # if length of the string is larger then the specified max length # cut every char from the string exceeding length $shorted and add three dots - truncatedString=$(echo "$1" | cut -c1-$shorted)"..." + truncatedString=$(echo "$1" | cut -c1-${shorted})"..." echo "${truncatedString}" else echo "$1" @@ -1413,8 +1450,12 @@ truncateString() { # Converts seconds to days, hours, minutes # https://unix.stackexchange.com/a/338844 convertUptime() { - # shellcheck disable=SC2016 - eval "echo $(date -ud "@$1" +'$((%s/3600/24)) days, %H hours, %M minutes')" + + local D=$(($1/60/60/24)) + local H=$(($1/60/60%24)) + local M=$(($1/60%60)) + + printf "%d days, %02d hours, %02d minutes" "${D}" "${H}" "${M}" } secretRead() { @@ -1447,7 +1488,7 @@ secretRead() { fi if [ "${key}" = "$(printf '\177')" ] ; then # Backspace - if [ $charcount -gt 0 ] ; then + if [ ${charcount} -gt 0 ] ; then charcount=$((charcount-1)) printf '\b \b' password="${password%?}" @@ -1456,7 +1497,7 @@ secretRead() { # any other character charcount=$((charcount+1)) printf '*' - password="$password$key" + password="${password}${key}" fi done @@ -1465,210 +1506,191 @@ secretRead() { } check_dependencies() { + local hasDeps=true # Check for required dependencies if ! command -v curl >/dev/null 2>&1; then printf "%b" "${check_box_bad} Error!\n 'curl' is missing but required.\n" - exit 1 + hasDeps=false fi if ! command -v jq >/dev/null 2>&1; then - printf "%b" "${check_box_bad} Error!\n 'jq' is missing but required.\n" - exit 1 + printf "%b" "${check_box_bad} Error!\n 'jq' is missing but required.\n" + hasDeps=false fi -} - -########################################## MAIN FUNCTIONS ########################################## -OutputJSON() { - # Hiding the cursor. - # https://vt100.net/docs/vt510-rm/DECTCEM.html - printf '\e[?25l' - # Traps for graceful shutdown - # https://unix.stackexchange.com/a/681201 - trap CleanExit EXIT - trap sig_cleanup INT QUIT TERM - # Save current terminal settings (needed for later restore after password prompt) - stty_orig=$(stty -g) + if ! command -v dig >/dev/null 2>&1; then + printf "%b" "${check_box_bad} Error!\n 'dig' is missing but required.\n" + hasDeps=false + fi - # Test if the authentication endpoint is available - TestAPIAvailability - # Authenticate with the FTL server - printf "%b" "Establishing connection with FTL...\n" - LoginAPI + if ! command -v tput >/dev/null 2>&1; then + printf "%b" "${check_box_bad} Error!\n 'tput' is missing but required.\n" + hasDeps=false + fi - GetPADDData - GetSummaryInformation - printf "%b" "{\"domains_being_blocked\":${domains_being_blocked_raw},\"dns_queries_today\":${dns_queries_today_raw},\"ads_blocked_today\":${ads_blocked_today_raw},\"ads_percentage_today\":${ads_percentage_today},\"clients\": ${clients}}" + if [ "${hasDeps}" != true ]; then + printf "%b" "\n Please install the missing dependencies noted above.\n" + exit 1 + fi } -ShowVersion() { - # Hiding the cursor. - # https://vt100.net/docs/vt510-rm/DECTCEM.html - printf '\e[?25l' - # Traps for graceful shutdown - # https://unix.stackexchange.com/a/681201 - trap CleanExit EXIT - trap sig_cleanup INT QUIT TERM - - # Save current terminal settings (needed for later restore after password prompt) - stty_orig=$(stty -g) +########################################## MAIN FUNCTIONS ########################################## - # Test if the authentication endpoint is available - TestAPIAvailability - # Authenticate with the FTL server - printf "%b" "Establishing connection with FTL...\n" - LoginAPI +ShowVersion() { - GetPADDData - GetVersionInformation GetPADDInformation + if [ -z "${padd_version_latest}" ]; then padd_version_latest="N/A" fi - if [ ! "${DOCKER_VERSION}" = "null" ]; then - # Check for latest Docker version - printf "%s${clear_line}\n" "PADD version is ${padd_version} as part of Docker ${docker_version_heatmap}${DOCKER_VERSION}${reset_text} (Latest Docker: ${GITHUB_DOCKER_VERSION})" - version_info="Docker ${docker_version_heatmap}${DOCKER_VERSION}${reset_text}" - else - printf "%s${clear_line}\n" "PADD version is ${padd_version_heatmap}${padd_version}${reset_text} (Latest: ${padd_version_latest})" - fi + + printf "\n%s${clear_line}\n" "PADD version is ${padd_version_heatmap}${padd_version}${reset_text} (Latest: ${padd_version_latest})" + + exit 0 } StartupRoutine(){ - if [ "$1" = "ants" ]; then - # If the screen is too small from the beginning, exit - printf "%b" "${check_box_bad} Error!\n PADD isn't\n for ants!\n" - exit 1 - fi - - # Clear the screen and move cursor to (0,0). - # This mimics the 'clear' command. - # https://vt100.net/docs/vt510-rm/ED.html - # https://vt100.net/docs/vt510-rm/CUP.html - # E3 extension `\e[3J` to clear the scrollback buffer see 'man clear' - printf '\e[H\e[2J\e[3J' - - # adds the y-offset - moveYOffset - - if [ "$1" = "pico" ] || [ "$1" = "nano" ] || [ "$1" = "micro" ]; then - moveXOffset; PrintLogo "$1" - moveXOffset; printf "%b" "START-UP ===========\n" - - # Test if the authentication endpoint is available - TestAPIAvailability - - # Authenticate with the FTL server - moveXOffset; printf "%b" "Establishing connection with FTL...\n" - LoginAPI - - moveXOffset; printf "%b" "Starting PADD...\n" - - moveXOffset; printf "%b" " [■·········] 10%\r" - - # Request PADD data - GetPADDData - - # Check for updates - moveXOffset; printf "%b" " [■■········] 20%\r" - moveXOffset; printf "%b" " [■■■·······] 30%\r" - - # Get our information for the first time - moveXOffset; printf "%b" " [■■■■······] 40%\r" - GetVersionInformation - moveXOffset; printf "%b" " [■■■■■·····] 50%\r" - GetSummaryInformation - moveXOffset; printf "%b" " [■■■■■■····] 60%\r" - GetPiholeInformation - moveXOffset; printf "%b" " [■■■■■■■···] 70%\r" - GetNetworkInformation - moveXOffset; printf "%b" " [■■■■■■■■··] 80%\r" - GetSystemInformation - moveXOffset; printf "%b" " [■■■■■■■■■·] 90%\r" - GetPADDInformation - moveXOffset; printf "%b" " [■■■■■■■■■■] 100%\n" - - elif [ "$1" = "mini" ]; then - moveXOffset; PrintLogo "$1" - moveXOffset; echo "START UP =====================" - # Test if the authentication endpoint is available - TestAPIAvailability - # Authenticate with the FTL server - moveXOffset; printf "%b" "Establishing connection with FTL...\n" - LoginAPI - - # Request PADD data - moveXOffset; echo "- Requesting PADD information..." - GetPADDData - - # Get our information for the first time - moveXOffset; echo "- Gathering version info." - GetVersionInformation - moveXOffset; echo "- Gathering system info." - GetSystemInformation - moveXOffset; echo "- Gathering CPU/DNS info." - GetPiholeInformation - GetSummaryInformation - moveXOffset; echo "- Gathering network info." - GetNetworkInformation - GetPADDInformation - if [ ! "${DOCKER_VERSION}" = "null" ]; then - moveXOffset; echo " - Docker Tag ${DOCKER_VERSION}" - else - moveXOffset; echo " - Core $CORE_VERSION, Web $WEB_VERSION" - moveXOffset; echo " - FTL $FTL_VERSION, PADD ${padd_version}" + if [ "$1" = "ants" ]; then + # If the screen is too small from the beginning, exit + printf "%b" "${check_box_bad} Error!\n PADD isn't\n for ants!\n" + exit 1 fi - else - moveXOffset; printf "%b" "${padd_logo_retro_1}\n" - moveXOffset; printf "%b" "${padd_logo_retro_2}Pi-hole® Ad Detection Display\n" - moveXOffset; printf "%b" "${padd_logo_retro_3}A client for Pi-hole\n\n" - if [ "$1" = "tiny" ]; then - moveXOffset; echo "START UP ============================================" - else - moveXOffset; echo "START UP ===================================================" - fi + # Clear the screen and move cursor to (0,0). + # This mimics the 'clear' command. + # https://vt100.net/docs/vt510-rm/ED.html + # https://vt100.net/docs/vt510-rm/CUP.html + # E3 extension `\e[3J` to clear the scrollback buffer see 'man clear' + printf '\e[H\e[2J\e[3J' - # Test if the authentication endpoint is available - TestAPIAvailability + # adds the y-offset + moveYOffset - # Authenticate with the FTL server - moveXOffset; printf "%b" "Establishing connection with FTL...\n" - LoginAPI + if [ "$1" = "pico" ] || [ "$1" = "nano" ] || [ "$1" = "micro" ]; then + PrintLogo "$1" + if [ "$1" = "pico" ]; then + moveXOffset; printf "%b" "START-UP ===========\n" + elif [ "$1" = "nano" ]; then + moveXOffset; printf "%b" "START-UP ===============\n" + else + moveXOffset; printf "%b" "START-UP =====================\n" + fi - # Request PADD data - moveXOffset; echo "- Requesting PADD information..." - GetPADDData + # Test if the authentication endpoint is available + TestAPIAvailability - # Get our information for the first time - moveXOffset; echo "- Gathering version information..." - GetVersionInformation - moveXOffset; echo "- Gathering system information..." - GetSystemInformation - moveXOffset; echo "- Gathering CPU/DNS information..." - GetSummaryInformation - GetPiholeInformation - moveXOffset; echo "- Gathering network information..." - GetNetworkInformation + # Authenticate with the FTL server + moveXOffset; printf "%b" "Establishing connection with FTL...\n" + LoginAPI + + moveXOffset; printf "%b" "Starting PADD...\n" + + moveXOffset; printf "%b" " [■·········] 10%\r" + + # Request PADD data + GetPADDData + + # Check for updates + moveXOffset; printf "%b" " [■■········] 20%\r" + moveXOffset; printf "%b" " [■■■·······] 30%\r" + + # Get our information for the first time + moveXOffset; printf "%b" " [■■■■······] 40%\r" + GetVersionInformation + moveXOffset; printf "%b" " [■■■■■·····] 50%\r" + GetSummaryInformation + moveXOffset; printf "%b" " [■■■■■■····] 60%\r" + GetPiholeInformation + moveXOffset; printf "%b" " [■■■■■■■···] 70%\r" + GetNetworkInformation + moveXOffset; printf "%b" " [■■■■■■■■··] 80%\r" + GetSystemInformation + moveXOffset; printf "%b" " [■■■■■■■■■·] 90%\r" + GetPADDInformation + moveXOffset; printf "%b" " [■■■■■■■■■■] 100%\n" + + elif [ "$1" = "mini" ]; then + PrintLogo "$1" + moveXOffset; echo "START UP ===============================" + # Test if the authentication endpoint is available + TestAPIAvailability + # Authenticate with the FTL server + moveXOffset; printf "%b" "Establishing connection with FTL...\n" + LoginAPI + + # Request PADD data + moveXOffset; echo "- Requesting PADD information..." + GetPADDData + + # Get our information for the first time + moveXOffset; echo "- Gathering version info." + GetVersionInformation + moveXOffset; echo "- Gathering system info." + GetSystemInformation + moveXOffset; echo "- Gathering CPU/DNS info." + GetPiholeInformation + GetSummaryInformation + moveXOffset; echo "- Gathering network info." + GetNetworkInformation + GetPADDInformation + if [ "${DOCKER_VERSION}" != "null" ]; then + moveXOffset; echo " - Docker Tag ${DOCKER_VERSION}" + else + moveXOffset; echo " - Core ${CORE_VERSION}, Web ${WEB_VERSION}" + moveXOffset; echo " - FTL ${FTL_VERSION}, PADD ${padd_version}" + fi - GetPADDInformation - if [ ! "${DOCKER_VERSION}" = "null" ]; then - moveXOffset; echo " - Docker Tag ${DOCKER_VERSION}" else - moveXOffset; echo " - Pi-hole Core $CORE_VERSION" - moveXOffset; echo " - Web Admin $WEB_VERSION" - moveXOffset; echo " - FTL $FTL_VERSION" - moveXOffset; echo " - PADD ${padd_version}" - fi - fi - - moveXOffset; printf "%s" "- Starting in " - for i in 3 2 1 - do - printf "%s..." "$i" - sleep 1 - done + PrintLogo "$1" + if [ "$1" = "tiny" ]; then + moveXOffset; echo "START UP ============================================" + else + moveXOffset; echo "START UP ===================================================" + fi + + # Test if the authentication endpoint is available + TestAPIAvailability + + # Authenticate with the FTL server + moveXOffset; printf "%b" "Establishing connection with FTL...\n" + LoginAPI + + # Request PADD data + moveXOffset; echo "- Requesting PADD information..." + GetPADDData + + # Get our information for the first time + moveXOffset; echo "- Gathering version information..." + GetVersionInformation + moveXOffset; echo "- Gathering system information..." + GetSystemInformation + moveXOffset; echo "- Gathering CPU/DNS information..." + GetSummaryInformation + GetPiholeInformation + moveXOffset; echo "- Gathering network information..." + GetNetworkInformation + + GetPADDInformation + if [ "${DOCKER_VERSION}" != "null" ]; then + moveXOffset; echo " - Docker Tag ${DOCKER_VERSION}" + else + moveXOffset; echo " - Pi-hole Core ${CORE_VERSION}" + moveXOffset; echo " - Web Admin ${WEB_VERSION}" + moveXOffset; echo " - FTL ${FTL_VERSION}" + moveXOffset; echo " - PADD ${padd_version}" + fi + fi + + if [ "${runOnce}" = "false" ]; then + moveXOffset; printf "%s" "- Starting in " + for i in 3 2 1 + do + printf "%s..." "${i}" + sleep 1 + done + fi + } NormalPADD() { @@ -1692,6 +1714,11 @@ NormalPADD() { # Output everything to the screen PrintDashboard ${padd_size} + # Should we only run once? + if [ "${runOnce}" = "true" ]; then + break + fi + # Sleep for 5 seconds # sending sleep in the background and wait for it # this way the TerminalResize trap can kill the sleep @@ -1758,13 +1785,6 @@ NormalPADD() { } Update() { - # source version file to check if $DOCKER_VERSION is set - . /etc/pihole/versions - - if [ -n "${DOCKER_VERSION}" ]; then - echo "${check_box_info} Update is not supported for Docker" - exit 1 - fi GetPADDInformation @@ -1800,9 +1820,10 @@ DisplayHelp() { ::: --yoff [num] set the y-offset, reference is the upper left corner, disables auto-centering ::: ::: --server domain or IP of your Pi-hole (default: localhost) +::: --api API URL location of your Pi-hole (example: https://pi.hole/api/) ::: --secret your Pi-hole's password, required to access the API ::: --2fa <2fa> your Pi-hole's 2FA code, if 2FA is enabled -::: -j, --json output stats as JSON formatted string and exit +::: --runonce display output once and exit ::: -u, --update update to the latest version ::: -v, --version show PADD version info ::: -h, --help display this help text @@ -1819,7 +1840,7 @@ sig_cleanup() { # causing EXIT trap to be executed, so we trap EXIT after INT trap '' EXIT - (exit $err) # execute in a subshell just to pass $? to CleanExit() + (exit ${err}) # execute in a subshell just to pass $? to CleanExit() CleanExit } @@ -1842,11 +1863,11 @@ CleanExit() { # if background sleep is running, kill it # http://mywiki.wooledge.org/SignalTrap#When_is_the_signal_handled.3F - kill "{$sleepPID}" > /dev/null 2>&1 + kill "${sleepPID}" > /dev/null 2>&1 # Delete session from FTL server DeleteSession - exit $err # exit the script with saved $? + exit ${err} # exit the script with saved $? } TerminalResize(){ @@ -1863,7 +1884,7 @@ TerminalResize(){ printf '\e[H\e[2J\e[3J' - kill "{$sleepPID}" > /dev/null 2>&1 + kill "${sleepPID}" > /dev/null 2>&1 } main(){ @@ -1887,25 +1908,43 @@ main(){ StartupRoutine ${padd_size} + # Run SizeChecker again to account for resizing during startup + SizeChecker + # Run PADD NormalPADD } # Process all options (if present) while [ "$#" -gt 0 ]; do - case "$1" in - "-j" | "--json" ) xOffset=0; OutputJSON; exit 0;; - "-u" | "--update" ) Update;; - "-h" | "--help" ) DisplayHelp; exit 0;; - "-v" | "--version" ) xOffset=0; ShowVersion; exit 0;; - "--xoff" ) xOffset="$2"; xOffOrig="$2"; shift;; - "--yoff" ) yOffset="$2"; yOffOrig="$2"; shift;; - "--server" ) SERVER="$2"; shift;; - "--secret" ) password="$2"; shift;; - "--2fa" ) totp="$2"; shift;; - * ) DisplayHelp; exit 1;; - esac - shift + case "$1" in + "-u" | "--update" ) xOffset=0; doUpdate=true;; + "-h" | "--help" ) DisplayHelp; exit 0;; + "-v" | "--version" ) xOffset=0; versionOnly=true ;; + "--runonce" ) runOnce=true;; + "--xoff" ) xOffset="${2}"; xOffOrig="${2}"; shift;; + "--yoff" ) yOffset="${2}"; yOffOrig="${2}"; shift;; + "--server" ) SERVER="${2}"; shift;; + "--api" ) API_LOCATION="${2}"; shift;; + "--secret" ) password="${2}"; shift;; + "--2fa" ) totp="${2}"; shift;; + * ) DisplayHelp; exit 1;; + esac + shift done +if [ -n "${API_LOCATION}" ] && [ -n "${SERVER}" ]; then + moveXOffset; echo "Do not set --server and --api simultaneously." + moveXOffset; echo "Exiting." + exit 1 +fi + +if [ "${versionOnly}" ]; then + ShowVersion +fi + +if [ "${doUpdate}" ]; then + Update +fi + main