{"action":"created","comment":{"url":"https://api.github.com/repos/pi-hole/pi-hole/pulls/comments/136720348","pull_request_review_id":60299519,"id":136720348,"diff_hunk":"@@ -1,476 +1,626 @@\n #!/usr/bin/env bash\n+# shellcheck disable=SC1090\n+\n # Pi-hole: A black hole for Internet advertisements\n # (c) 2017 Pi-hole, LLC (https://pi-hole.net)\n # Network-wide ad blocking via your own hardware.\n #\n+# Usage: \"pihole -g\"\n # Compiles a list of ad-serving domains by downloading them from multiple sources\n #\n # This file is copyright under the latest version of the EUPL.\n # Please see LICENSE file for your rights under this license.\n \n-# Run this script as root or under sudo\n-\n coltable=\"/opt/pihole/COL_TABLE\"\n-source ${coltable}\n+source \"${coltable}\"\n \n-helpFunc() {\n-  echo \"Usage: pihole -g\n-Update domains from blocklists specified in adlists.list\n+basename=\"pihole\"\n+PIHOLE_COMMAND=\"/usr/local/bin/${basename}\"\n+WHITELIST_COMMAND=\"${PIHOLE_COMMAND} -w\"\n \n-Options:\n-  -f, --force          Force the download of all specified blocklists\n-  -h, --help           Show this help dialog\"\n-  exit 0\n-}\n+piholeDir=\"/etc/${basename}\"\n+piholeRepo=\"/etc/.${basename}\"\n+\n+adListFile=\"${piholeDir}/adlists.list\"\n+adListDefault=\"${piholeDir}/adlists.default\"\n+adListRepoDefault=\"${piholeRepo}/adlists.default\"\n \n-PIHOLE_COMMAND=\"/usr/local/bin/pihole\"\n+whitelistFile=\"${piholeDir}/whitelist.txt\"\n+blacklistFile=\"${piholeDir}/blacklist.txt\"\n+wildcardFile=\"/etc/dnsmasq.d/03-pihole-wildcard.conf\"\n \n-adListFile=/etc/pihole/adlists.list\n-adListDefault=/etc/pihole/adlists.default # Deprecated\n-adListRepoDefault=/etc/.pihole/adlists.default\n-whitelistScript=\"${PIHOLE_COMMAND} -w\"\n-whitelistFile=/etc/pihole/whitelist.txt\n-blacklistFile=/etc/pihole/blacklist.txt\n-readonly wildcardlist=\"/etc/dnsmasq.d/03-pihole-wildcard.conf\"\n+adList=\"${piholeDir}/gravity.list\"\n+blackList=\"${piholeDir}/black.list\"\n+localList=\"${piholeDir}/local.list\"\n \n-# Source the setupVars from install script for the IP\n-setupVars=/etc/pihole/setupVars.conf\n+domainsExtension=\"domains\"\n+matterAndLight=\"${basename}.0.matterandlight.txt\"\n+parsedMatter=\"${basename}.1.parsedmatter.txt\"\n+whitelistMatter=\"${basename}.2.whitelistmatter.txt\"\n+accretionDisc=\"${basename}.3.accretionDisc.txt\"\n+preEventHorizon=\"list.preEventHorizon\"\n+\n+skipDownload=\"false\"\n+\n+# Use \"force-reload\" when restarting dnsmasq for everything but Wildcards\n+dnsRestart=\"force-reload\"\n+\n+# Source setupVars from install script\n+setupVars=\"${piholeDir}/setupVars.conf\"\n if [[ -f \"${setupVars}\" ]];then\n-  . /etc/pihole/setupVars.conf\n+  source \"${setupVars}\"\n+\n+  # Remove CIDR mask from IPv4/6 addresses\n+  IPV4_ADDRESS=\"${IPV4_ADDRESS%/*}\"\n+  IPV6_ADDRESS=\"${IPV6_ADDRESS%/*}\"\n else\n-  echo -e \"  ${COL_LIGHT_RED}Error: /etc/pihole/setupVars.conf missing. Possible installation failure.${COL_NC}\n-  Please run 'pihole -r', and choose the 'reconfigure' option to reconfigure.\"\n+  echo -e \"  ${COL_LIGHT_RED}Installation Failure: ${setupVars} does not exist! ${COL_NC}\n+  Please run 'pihole -r', and choose the 'reconfigure' option to fix.\"\n   exit 1\n fi\n \n-# Remove the /* from the end of the IP addresses\n-IPV4_ADDRESS=${IPV4_ADDRESS%/*}\n-IPV6_ADDRESS=${IPV6_ADDRESS%/*}\n-\n-# Variables for various stages of downloading and formatting the list\n-basename=pihole\n-piholeDir=/etc/${basename}\n-adList=${piholeDir}/gravity.list\n-blackList=${piholeDir}/black.list\n-localList=${piholeDir}/local.list\n-justDomainsExtension=domains\n-matterAndLight=${basename}.0.matterandlight.txt\n-supernova=${basename}.1.supernova.txt\n-preEventHorizon=list.preEventHorizon\n-eventHorizon=${basename}.2.supernova.txt\n-accretionDisc=${basename}.3.accretionDisc.txt\n-\n-skipDownload=false\n-\n # Warn users still using pihole.conf that it no longer has any effect\n-if [[ -r ${piholeDir}/pihole.conf ]]; then\n-echo -e \"  ${COL_LIGHT_RED}pihole.conf file no longer supported. Overrides in this file are ignored.${COL_NC}\"\n+if [[ -r \"${piholeDir}/pihole.conf\" ]]; then\n+  echo -e \"  ${COL_LIGHT_RED}Ignoring overrides specified within pihole.conf! ${COL_NC}\"\n fi\n \n-###########################\n-# Collapse - begin formation of pihole\n-gravity_collapse() {\n-\n-  #New Logic:\n-  # Does /etc/pihole/adlists.list exist? If so leave it alone\n-  #                                      If not, cp /etc/.pihole/adlists.default /etc/pihole/adlists.list\n-  # Read from adlists.list\n-\n-  # The following two blocks will sort out any missing adlists in the /etc/pihole directory, and remove legacy adlists.default\n-  if [[ -f \"${adListDefault}\" ]] && [[ -f \"${adListFile}\" ]]; then\n-    rm \"${adListDefault}\"\n-  fi\n-\n-  if [ ! -f \"${adListFile}\" ]; then\n-    cp \"${adListRepoDefault}\" \"${adListFile}\"\n-  fi\n-\n-  echo -e \"  ${INFO} Neutrino emissions detected...\"\n-  echo \"\"\n-  local str=\"Pulling source lists into range\"\n-  echo -ne \"  ${INFO} ${str}...\"\n+# Determine if DNS resolution is available before proceeding with retrieving blocklists\n+gravity_DNSLookup() {\n+  local plu\n \n-  sources=()\n-  while IFS= read -r line || [[ -n \"$line\" ]]; do\n-    # Do not read commented out or blank lines\n-    if [[ ${line} = \\#* ]] || [[ ! ${line} ]]; then\n-      echo \"\" > /dev/null\n-    else\n-      sources+=(${line})\n+  # Determine if pi.hole can be resolved\n+  if ! timeout 10 nslookup pi.hole > /dev/null; then\n+    if [[ -n \"${secs}\" ]]; then\n+      echo -e \"${OVER}  ${CROSS} DNS resolution is still unavailable, cancelling\"\n+      exit 1\n     fi\n-  done < ${adListFile}\n \n-  echo -e \"${OVER}  ${TICK} ${str}\"\n-}\n-\n-# patternCheck - check to see if curl downloaded any new files.\n-gravity_patternCheck() {\n-  patternBuffer=$1\n-  success=$2\n-  error=$3\n-  if [[ \"${success}\" = true ]]; then\n-    # Check if download was successful but list has not been modified\n-    if [[ \"${error}\" == \"304\" ]]; then\n-      echo -e \"  ${TICK} No changes detected, transport skipped!\"\n-    # Check if the patternbuffer is a non-zero length file\n-    elif [[ -s \"${patternBuffer}\" ]]; then\n-      # Some blocklists are copyright, they need to be downloaded and stored\n-      # as is. They can be processed for content after they have been saved.\n-      mv \"${patternBuffer}\" \"${saveLocation}\"\n-      echo -e \"  ${TICK} List updated, transport successful!\"\n+    # Determine error output message\n+    if pidof dnsmasq > /dev/null; then\n+      echo -e \"  ${CROSS} DNS resolution is temporarily unavailable\"\n     else\n-      # Empty file -> use previously downloaded list\n-      echo -e \"  ${INFO} Received empty file, ${COL_LIGHT_GREEN}using cached one${COL_NC} (list not updated!)\"\n-    fi\n-  else\n-    # Check if cached list exists\n-    if [[ -r \"${saveLocation}\" ]]; then\n-      echo -e \"  ${CROSS} List download failed, using cached list (list not updated!)\"\n-    else\n-      echo -e \"  ${CROSS} Download failed and no cached list available (list will not be considered)\"\n+      echo -e \"  ${CROSS} DNS service is not running\"\n+      \"${PIHOLE_COMMAND}\" restartdns\n     fi\n+\n+    # Give time for dnsmasq to be resolvable\n+    secs=\"30\"\n+    while [[ \"${secs}\" -ne 0 ]]; do\n+      [[ \"${secs}\" -ne 1 ]] && plu=\"s\" || plu=\"\"\n+      echo -ne \"${OVER}  ${INFO} Waiting $secs second${plu} before continuing...\"\n+      sleep 1\n+      : $((secs--))\n+    done\n+\n+    # Try again\n+    gravity_DNSLookup\n+  elif [[ -n \"${secs}\" ]]; then\n+    # Print confirmation of resolvability if it had previously failed\n+    echo -e \"${OVER}  ${TICK} DNS resolution is now available\\\\n\"\n   fi\n }\n \n-# transport - curl the specified url with any needed command extentions\n-gravity_transport() {\n-  url=$1\n-  cmd_ext=$2\n-  agent=$3\n+# Retrieve blocklist URLs and parse domains from adlists.list\n+gravity_Collapse() {\n+  echo -e \"  ${INFO} Neutrino emissions detected...\"\n \n-  # tmp file, so we don't have to store the (long!) lists in RAM\n-  patternBuffer=$(mktemp)\n-  heisenbergCompensator=\"\"\n-  if [[ -r ${saveLocation} ]]; then\n-    # If domain has been saved, add file for date check to only download newer\n-    heisenbergCompensator=\"-z ${saveLocation}\"\n+  # Handle \"adlists.list\" and \"adlists.default\" files\n+  if [[ -f \"${adListDefault}\" ]] && [[ -f \"${adListFile}\" ]]; then\n+    # Remove superceded $adListDefault file\n+    rm \"${adListDefault}\" 2> /dev/null || \\\n+      echo -e \"  ${CROSS} Unable to remove ${adListDefault}\"\n+  elif [[ ! -f \"${adListFile}\" ]]; then\n+    # Create \"adlists.list\" by copying \"adlists.default\" from internal Pi-hole repo\n+    cp \"${adListRepoDefault}\" \"${adListFile}\" 2> /dev/null || \\\n+      echo -e \"  ${CROSS} Unable to copy ${adListFile##*/} from ${piholeRepo}\"\n   fi\n \n-  # Silently curl url\n-  echo -e \"${OVER}  ${TICK} ${str}\"\n-  local str=\"Status:\"\n-  echo -ne \"  ${INFO} ${str} Pending\"\n-  err=$(curl -s -L ${cmd_ext} ${heisenbergCompensator} -w %{http_code} -A \"${agent}\" ${url} -o ${patternBuffer})\n-\n-  # Analyze http response\n-  case \"$err\" in\n-    \"200\"  ) echo -e \"${OVER}  ${TICK} ${str} Success (OK)\"; success=true;;\n-    \"304\"  ) echo -e \"${OVER}  ${TICK} ${str} Not modified\"; success=true;;\n-    \"403\"  ) echo -e \"${OVER}  ${CROSS} ${str} Forbidden\"; success=false;;\n-    \"404\"  ) echo -e \"${OVER}  ${CROSS} ${str} Not found\"; success=false;;\n-    \"408\"  ) echo -e \"${OVER}  ${CROSS} ${str} Time-out\"; success=false;;\n-    \"451\"  ) echo -e \"${OVER}  ${CROSS} ${str} Unavailable For Legal Reasons\"; success=false;;\n-    \"521\"  ) echo -e \"${OVER}  ${CROSS} ${str} Web Server Is Down (Cloudflare)\"; success=false;;\n-    \"522\"  ) echo -e \"${OVER}  ${CROSS} ${str} Connection Timed Out (Cloudflare)\"; success=false;;\n-    \"500\"  ) echo -e \"${OVER}  ${CROSS} ${str} Internal Server Error\"; success=false;;\n-    *      ) echo -e \"${OVER}  ${CROSS} ${str} Status $err\"; success=false;;\n-  esac\n+  local str=\"Pulling blocklist source list into range\"\n+  echo -ne \"  ${INFO} ${str}...\"\n \n-  # Process result\n-  gravity_patternCheck \"${patternBuffer}\" \"${success}\" \"${err}\"\n+  # Retrieve source URLs from $adListFile\n+  # Awk Logic: Remove comments, CR line endings and empty lines\n+  mapfile -t sources < <(awk '!/^[#@;!\\[]/ {gsub(/\\r$/, \"\", $0); if ($1) { print $1 } }' \"${adListFile}\" 2> /dev/null)\n \n-  # Delete temp file if it hasn't been moved\n-  if [[ -f \"${patternBuffer}\" ]]; then\n-    rm \"${patternBuffer}\"\n+  # Parse source domains from $sources\n+  # Awk Logic: Split by folder/port, remove URL protocol & optional username:password@\n+  mapfile -t sourceDomains < <(\n+    awk -F '[/:]' '{\n+      gsub(/(.*:\\/\\/|.*:.*@)/, \"\", $0)\n+      print $1\n+    }' <<< \"$(printf '%s\\n' \"${sources[@]}\")\" 2> /dev/null\n+  )\n+\n+  if [[ -n \"${sources[*]}\" ]] && [[ -n \"${sourceDomains[*]}\" ]]; then\n+    echo -e \"${OVER}  ${TICK} ${str}\"\n+  else\n+    echo -e \"${OVER}  ${CROSS} ${str}\"\n+    gravity_Cleanup \"error\"\n   fi\n }\n \n-# spinup - main gravity function\n-gravity_spinup() {\n+# Define options for when retrieving blocklists\n+gravity_Supernova() {\n+  local url domain agent cmd_ext str\n+\n   echo \"\"\n-  # Loop through domain list. Download each one and remove commented lines (lines beginning with '# 'or '/') and # blank lines\n+\n+  # Loop through $sources to download each one\n   for ((i = 0; i < \"${#sources[@]}\"; i++)); do\n-    url=${sources[$i]}\n-    # Get just the domain from the URL\n-    domain=$(cut -d'/' -f3 <<< \"${url}\")\n+    url=\"${sources[$i]}\"\n+    domain=\"${sourceDomains[$i]}\"\n \n     # Save the file as list.#.domain\n-    saveLocation=${piholeDir}/list.${i}.${domain}.${justDomainsExtension}\n-    activeDomains[$i]=${saveLocation}\n+    saveLocation=\"${piholeDir}/list.${i}.${domain}.${domainsExtension}\"\n+    activeDomains[$i]=\"${saveLocation}\"\n \n+    # Default user-agent (for Cloudflare's Browser Integrity Check: https://support.cloudflare.com/hc/en-us/articles/200170086-What-does-the-Browser-Integrity-Check-do-)\n     agent=\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36\"\n \n-    # Use a case statement to download lists that need special cURL commands\n-    # to complete properly and reset the user agent when required\n+    # Provide special commands for blocklists which may need them\n     case \"${domain}\" in\n-      \"pgl.yoyo.org\")\n-        cmd_ext=\"-d mimetype=plaintext -d hostformat=hosts\"\n-      ;;\n-\n-      # Default is a simple request\n-      *) cmd_ext=\"\"\n+      \"pgl.yoyo.org\") cmd_ext=\"-d mimetype=plaintext -d hostformat=hosts\";;\n+      *) cmd_ext=\"\";;\n     esac\n \n     if [[ \"${skipDownload}\" == false ]]; then\n-      local str=\"Aiming tractor beam at $domain\"\n-      echo -ne \"  ${INFO} ${str}...\"\n+      str=\"Target: ${domain} (${url##*/})\"\n+      echo -e \"  ${INFO} ${str}\"\n+\n+      gravity_Pull \"${url}\" \"${cmd_ext}\" \"${agent}\" \"${str}\"\n \n-      gravity_transport \"$url\" \"$cmd_ext\" \"$agent\" \"$str\"\n       echo \"\"\n     fi\n   done\n }\n \n-# Schwarzchild - aggregate domains to one list and add blacklisted domains\n-gravity_Schwarzchild() {\n-  echo \"\"\n-  # Find all active domains and compile them into one file and remove CRs\n-  local str=\"Aggregating list of domains\"\n+# Download specified URL and perform QA\n+gravity_Pull() {\n+  local url cmd_ext agent heisenbergCompensator patternBuffer str httpCode success\n+\n+  url=\"${1}\"\n+  cmd_ext=\"${2}\"\n+  agent=\"${3}\"\n+\n+  # Store downloaded content to temp file instead of RAM\n+  patternBuffer=$(mktemp)\n+\n+  heisenbergCompensator=\"\"\n+  if [[ -r \"${saveLocation}\" ]]; then\n+    # Allow curl to determine if a remote file has been modified since last retrieval\n+    heisenbergCompensator=\"-z ${saveLocation}\"\n+  fi\n+\n+  str=\"Status:\"\n+  echo -ne \"  ${INFO} ${str} Pending...\"\n+  # shellcheck disable=SC2086\n+  httpCode=$(curl -s -L ${cmd_ext} ${heisenbergCompensator} -w \"%{http_code}\" -A \"${agent}\" \"${url}\" -o \"${patternBuffer}\" 2> /dev/null)\n+\n+  # Determine \"Status:\" output based on HTTP response\n+  case \"${httpCode}\" in\n+    \"200\" ) echo -e \"${OVER}  ${TICK} ${str} Retrieval successful\"; success=\"true\";;\n+    \"304\" ) echo -e \"${OVER}  ${TICK} ${str} No changes detected\"; success=\"true\";;\n+    \"403\" ) echo -e \"${OVER}  ${CROSS} ${str} Forbidden\"; success=\"false\";;\n+    \"404\" ) echo -e \"${OVER}  ${CROSS} ${str} Not found\"; success=\"false\";;\n+    \"408\" ) echo -e \"${OVER}  ${CROSS} ${str} Time-out\"; success=\"false\";;\n+    \"451\" ) echo -e \"${OVER}  ${CROSS} ${str} Unavailable For Legal Reasons\"; success=\"false\";;\n+    \"521\" ) echo -e \"${OVER}  ${CROSS} ${str} Web Server Is Down (Cloudflare)\"; success=\"false\";;\n+    \"522\" ) echo -e \"${OVER}  ${CROSS} ${str} Connection Timed Out (Cloudflare)\"; success=\"false\";;\n+    \"500\" ) echo -e \"${OVER}  ${CROSS} ${str} Internal Server Error\"; success=\"false\";;\n+    *     ) echo -e \"${OVER}  ${CROSS} ${str} Status ${httpCode}\"; success=\"false\";;\n+  esac\n+\n+  # Determine if the blocklist was downloaded and saved correctly\n+  if [[ \"${success}\" == \"true\" ]]; then\n+    if [[ \"${httpCode}\" == \"304\" ]]; then\n+      : # Do nothing\n+    # Check if patternbuffer is a non-zero length file\n+    elif [[ -s \"${patternBuffer}\" ]]; then\n+      # Determine if blocklist is non-standard and parse as appropriate\n+      gravity_ParseFileIntoDomains \"${patternBuffer}\" \"${saveLocation}\"\n+    else\n+      # Fall back to previously cached list if patternBuffer is empty\n+      echo -e \"  ${INFO} Received empty file: ${COL_LIGHT_GREEN}using previously cached list${COL_NC}\"\n+    fi\n+  else\n+    # Determine if cached list exists\n+    if [[ -r \"${saveLocation}\" ]]; then\n+      echo -e \"  ${CROSS} List download failed: ${COL_LIGHT_GREEN}using previously cached list${COL_NC}\"\n+    else\n+      echo -e \"  ${CROSS} List download failed: ${COL_LIGHT_RED}no cached list available${COL_NC}\"\n+    fi\n+  fi\n+\n+  # Delete temp file if it has not been moved\n+  if [[ -f \"${patternBuffer}\" ]]; then\n+    rm \"${patternBuffer}\" 2> /dev/null || \\\n+      echo -e \"  ${CROSS} Unable to remove ${patternBuffer}\"\n+  fi\n+}\n+\n+# Parse non-standard source files into domains-only format\n+gravity_ParseFileIntoDomains() {\n+  local source destination commentPattern firstLine abpFilter\n+  source=\"${1}\"\n+  destination=\"${2}\"\n+\n+  # Determine how to parse source file\n+  if [[ \"${source}\" == \"${piholeDir}/${matterAndLight}\" ]]; then\n+    # Consolidated list parsing: Remove comments and hosts IP's\n+\n+    # Define symbols used as comments: #;@![/\n+    commentPattern=\"[#;@![\\\\/]\"\n+\n+    # Awk Logic: Process lines which do not begin with comments\n+    awk '!/^'\"${commentPattern}\"'/ {\n+      # If there are multiple words seperated by space\n+      if (NF>1) {\n+        # Remove comments (including prefixed spaces/tabs)\n+        if ($0 ~ /'\"${commentPattern}\"'/) { gsub(\"( |\\t)'\"${commentPattern}\"'.*\", \"\", $0) }\n+        # Print consecutive domains\n+        if ($3) {\n+          $1=\"\"\n+          # Remove space which is left in $0 when removing $1\n+          gsub(\"^ \", \"\", $0)\n+          print $0\n+        # Print single domain\n+        } else if ($2) {\n+          print $2\n+        }\n+      # If there are no words seperated by space\n+      } else if($1) {\n+        print $1\n+      }\n+    }' \"${source}\" 2> /dev/null > \"${destination}\"\n+  else\n+    # Individual file parsing: Keep comments, while parsing domains from each line\n+    read -r firstLine < \"${source}\"\n+\n+    # Determine how to parse individual source file formats\n+    # Lists may not capitalise the first line correctly, so compare strings against lower case\n+    if [[ \"${firstLine,,}\" =~ \"adblock\" ]] || [[ \"${firstLine,,}\" =~ \"ublock\" ]] || [[ \"${firstLine,,}\" =~ \"! checksum\" ]]; then\n+      # Awk Logic: Parse Adblock domains & comments: https://adblockplus.org/filter-cheatsheet \n+      abpFilter=\"/^(\\\\[|!)|^(\\\\|\\\\|.*\\\\^)/\"\n+      awk ''\"${abpFilter}\"' {\n+        # Remove valid adblock type options\n+        gsub(/~?(important|third-party|popup|subdocument|websocket),?/, \"\", $0)\n+        # Remove starting domain name anchor \"||\" and ending seperator \"^$\" ($ optional)\n+        gsub(/(\\|\\||\\^\\$?$)/, \"\", $0)\n+        # Remove lines which are only IPv4 addresses or contain \"^/*\"\n+        if ($0 ~ /(^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$|[\\\\^\\/\\*])/) { $0=\"\" }\n+        if ($0) { print $0 }\n+      }' \"${source}\" 2> /dev/null > \"${destination}\"\n+    elif grep -q -E \"^(https?://|([0-9]{1,3}\\\\.){3}[0-9]{1,3}$)\" \"${source}\" &> /dev/null; then\n+      # Parse URLs if source file contains http:// or IPv4\n+      awk '{\n+        # Remove URL protocol, optional \"username:password@\", and \":?/;\"\n+        if ($0 ~ /[:?\\/;]/) { gsub(/(^.*:\\/\\/(.*:.*@)?|[:?\\/;].*)/, \"\", $0) }\n+        # Remove lines which are only IPv4 addresses\n+        if ($0 ~ /^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$/) { $0=\"\" }\n+        if ($0) { print $0 }\n+      }' \"${source}\" 2> /dev/null > \"${destination}\"\n+    else\n+      # Keep hosts/domains file in same format as it was downloaded\n+      output=$( { mv \"${source}\" \"${destination}\"; } 2>&1 )\n+      status=\"$?\"\n+\n+      if [[ \"${status}\" -ne 0 ]]; then\n+        echo -e \"  ${CROSS} Unable to move tmp file to ${piholeDir}\n+      ${output}\"\n+        gravity_Cleanup \"error\"\n+      fi\n+    fi\n+  fi\n+}\n+\n+# Create unfiltered \"Matter and Light\" consolidated list\n+gravity_Schwarzschild() {\n+  local str lastLine\n+\n+  str=\"Consolidating blocklists\"\n   echo -ne \"  ${INFO} ${str}...\"\n-  \n-  truncate -s 0 ${piholeDir}/${matterAndLight}\n+\n+  # Empty $matterAndLight if it already exists, otherwise, create it\n+  : > \"${piholeDir}/${matterAndLight}\"\n+\n   for i in \"${activeDomains[@]}\"; do\n     # Only assimilate list if it is available (download might have failed permanently)\n     if [[ -r \"${i}\" ]]; then\n-      cat \"${i}\" | tr -d '\\r' >> ${piholeDir}/${matterAndLight}\n+      # Compile all blacklisted domains into one file and remove CRs\n+      tr -d '\\r' < \"${i}\" >> \"${piholeDir}/${matterAndLight}\"\n+\n+      # Ensure each source blocklist has a final newline\n+      lastLine=$(tail -1 \"${piholeDir}/${matterAndLight}\")\n+      [[ \"${#lastLine}\" -gt 0 ]] && echo \"\" >> \"${piholeDir}/${matterAndLight}\"\n     fi\n   done\n \n   echo -e \"${OVER}  ${TICK} ${str}\"\n }\n \n-gravity_Blacklist() {\n-  # Append blacklist entries to eventHorizon if they exist\n-  if [[ -f \"${blacklistFile}\" ]]; then\n-    numBlacklisted=$(wc -l < \"${blacklistFile}\")\n-    plural=; [[ \"$numBlacklisted\" != \"1\" ]] && plural=s\n-    local str=\"Exact blocked domain${plural}: $numBlacklisted\"\n-    echo -e \"  ${INFO} ${str}\"\n-  else\n-    echo -e \"  ${INFO} Nothing to blacklist!\"\n-  fi\n+# Parse unfiltered consolidated blocklist into filtered domains-only format\n+gravity_Filter() {\n+  local str num\n+\n+  str=\"Extracting domains from blocklists\"\n+  echo -ne \"  ${INFO} ${str}...\"\n+\n+  # Parse into hosts file\n+  gravity_ParseFileIntoDomains \"${piholeDir}/${matterAndLight}\" \"${piholeDir}/${parsedMatter}\"\n+\n+  # Format file line count as currency\n+  num=$(printf \"%'.0f\" \"$(wc -l < \"${piholeDir}/${parsedMatter}\")\")\n+  echo -e \"${OVER}  ${TICK} ${str}\n+  ${INFO} ${COL_LIGHT_BLUE}${num}${COL_NC} domains being pulled in by gravity\"\n+\n+  gravity_Unique\n }\n \n-gravity_Wildcard() {\n-  # Return number of wildcards in output - don't actually handle wildcards\n-  if [[ -f \"${wildcardlist}\" ]]; then\n-    numWildcards=$(grep -c ^ \"${wildcardlist}\")\n-    if [[ -n \"${IPV4_ADDRESS}\" ]] && [[ -n \"${IPV6_ADDRESS}\" ]];then\n-      let numWildcards/=2\n-    fi\n-    plural=; [[ \"$numWildcards\" != \"1\" ]] && plural=s\n-    echo -e \"  ${INFO} Wildcard blocked domain${plural}: $numWildcards\"\n-  else\n-    echo -e \"  ${INFO} No wildcards used!\"\n-  fi\n+# Sort and remove duplicate blacklisted domains\n+gravity_Unique() {\n+  local str num\n+\n+  str=\"Removing duplicate domains\"\n+  echo -ne \"  ${INFO} ${str}...\"\n+  sort -u \"${piholeDir}/${parsedMatter}\" > \"${piholeDir}/${preEventHorizon}\"\n+  echo -e \"${OVER}  ${TICK} ${str}\"\n \n+  # Format file line count as currency\n+  num=$(printf \"%'.0f\" \"$(wc -l < \"${piholeDir}/${preEventHorizon}\")\")\n+  echo -e \"  ${INFO} ${COL_LIGHT_BLUE}${num}${COL_NC} unique domains trapped in the Event Horizon\"\n }\n \n-gravity_Whitelist() {\n+# Whitelist blocklist domain sources\n+gravity_WhitelistBLD() {\n+  local plural str uniqDomains\n+\n   echo \"\"\n-  # Prevent our sources from being pulled into the hole\n-  plural=; [[ \"${sources[@]}\" != \"1\" ]] && plural=s\n-  local str=\"Adding adlist source${plural} to the whitelist\"\n+  plural=; [[ \"${#sources[*]}\" != \"1\" ]] && plural=s\n+  str=\"Adding blocklist source${plural} to the whitelist\"\n   echo -ne \"  ${INFO} ${str}...\"\n \n-  urls=()\n-  for url in \"${sources[@]}\"; do\n-    tmp=$(awk -F '/' '{print $3}' <<< \"${url}\")\n-    urls=(\"${urls[@]}\" ${tmp})\n-  done\n+  # Create array of unique $sourceDomains\n+  # Disable SC2046 as quoting will only return first domain\n+  # shellcheck disable=SC2046\n+  read -r -a uniqDomains <<< $(awk '{ if(!a[$1]++) { print $1 } }' <<< \"$(printf '%s\\n' \"${sourceDomains[@]}\")\")\n+\n+  ${WHITELIST_COMMAND} -nr -q \"${uniqDomains[*]}\" > /dev/null\n \n   echo -e \"${OVER}  ${TICK} ${str}\"\n+}\n \n-  # Ensure adlist domains are in whitelist.txt\n-  ${whitelistScript} -nr -q \"${urls[@]}\" > /dev/null\n+# Whitelist user-defined domains\n+gravity_Whitelist() {\n+  local plural str num\n \n-  # Check whitelist.txt exists.\n+  # Test existence of whitelist.txt\n   if [[ -f \"${whitelistFile}\" ]]; then\n     # Remove anything in whitelist.txt from the Event Horizon\n-    numWhitelisted=$(wc -l < \"${whitelistFile}\")\n-    plural=; [[ \"$numWhitelisted\" != \"1\" ]] && plural=s\n-    local str=\"Whitelisting $numWhitelisted domain${plural}\"\n+    num=$(wc -l < \"${whitelistFile}\")\n+    plural=; [[ \"${num}\" != \"1\" ]] && plural=s\n+    str=\"Whitelisting ${num} domain${plural}\"\n     echo -ne \"  ${INFO} ${str}...\"\n \n-    # Print everything from preEventHorizon into eventHorizon EXCEPT domains in whitelist.txt\n-    grep -F -x -v -f ${whitelistFile} ${piholeDir}/${preEventHorizon} > ${piholeDir}/${eventHorizon}\n+    # Print everything from preEventHorizon into whitelistMatter EXCEPT domains in whitelist.txt\n+    grep -F -x -v -f \"${whitelistFile}\" \"${piholeDir}/${preEventHorizon}\" > \"${piholeDir}/${whitelistMatter}\"\n \n     echo -e \"${OVER}  ${TICK} ${str}\"\n   else\n     echo -e \"  ${INFO} Nothing to whitelist!\"\n   fi\n }\n \n-gravity_unique() {\n-  # Sort and remove duplicates\n-  local str=\"Removing duplicate domains\"\n-  echo -ne \"  ${INFO} ${str}...\"\n+# Output count of blacklisted domains and wildcards\n+gravity_ShowBlockCount() {\n+  local num plural str\n \n-  sort -u  ${piholeDir}/${supernova} > ${piholeDir}/${preEventHorizon}\n+  if [[ -f \"${blacklistFile}\" ]]; then\n+    num=$(printf \"%'.0f\" \"$(wc -l < \"${blacklistFile}\")\")\n+    plural=; [[ \"${num}\" != \"1\" ]] && plural=s\n+    str=\"Exact blocked domain${plural}: ${num}\"\n+    echo -e \"  ${INFO} ${str}\"\n+  else\n+    echo -e \"  ${INFO} Nothing to blacklist!\"\n+  fi\n \n-  echo -e \"${OVER}  ${TICK} ${str}\"\n-  numberOf=$(wc -l < ${piholeDir}/${preEventHorizon})\n-  echo -e \"  ${INFO} ${COL_LIGHT_BLUE}${numberOf}${COL_NC} unique domains trapped in the event horizon.\"\n+  if [[ -f \"${wildcardFile}\" ]]; then\n+    num=$(grep -c \"^\" \"${wildcardFile}\")\n+    # If IPv4 and IPv6 is used, divide total wildcard count by 2\n+    if [[ -n \"${IPV4_ADDRESS}\" ]] && [[ -n \"${IPV6_ADDRESS}\" ]];then\n+      num=$(( num/2 ))\n+    fi\n+    plural=; [[ \"${num}\" != \"1\" ]] && plural=s\n+    echo -e \"  ${INFO} Wildcard blocked domain${plural}: ${num}\"\n+  else\n+    echo -e \"  ${INFO} No wildcards used!\"\n+  fi\n }\n \n-gravity_doHostFormat() {\n-  # Check vars from setupVars.conf to see if we're using IPv4, IPv6, Or both.\n-  if [[ -n \"${IPV4_ADDRESS}\" ]] && [[ -n \"${IPV6_ADDRESS}\" ]];then\n-    # Both IPv4 and IPv6\n-    awk -v ipv4addr=\"$IPV4_ADDRESS\" -v ipv6addr=\"$IPV6_ADDRESS\" '{sub(/\\r$/,\"\"); print ipv4addr\" \"$0\"\\n\"ipv6addr\" \"$0}' >> \"${2}\" < \"${1}\"\n-  elif [[ -n \"${IPV4_ADDRESS}\" ]] && [[ -z \"${IPV6_ADDRESS}\" ]];then\n-    # Only IPv4\n-    awk -v ipv4addr=\"$IPV4_ADDRESS\" '{sub(/\\r$/,\"\"); print ipv4addr\" \"$0}' >> \"${2}\" < \"${1}\"\n-  elif [[ -z \"${IPV4_ADDRESS}\" ]] && [[ -n \"${IPV6_ADDRESS}\" ]];then\n-    # Only IPv6\n-    awk -v ipv6addr=\"$IPV6_ADDRESS\" '{sub(/\\r$/,\"\"); print ipv6addr\" \"$0}' >> \"${2}\" < \"${1}\"\n-  elif [[ -z \"${IPV4_ADDRESS}\" ]] &&[[  -z \"${IPV6_ADDRESS}\" ]];then\n+# Parse list of domains into hosts format\n+gravity_ParseDomainsIntoHosts() {\n+  if [[ -n \"${IPV4_ADDRESS}\" ]] || [[ -n \"${IPV6_ADDRESS}\" ]]; then\n+    # Awk Logic: Remove CR line endings and print IP before domain if IPv4/6 is used\n+    awk -v ipv4addr=\"$IPV4_ADDRESS\" -v ipv6addr=\"$IPV6_ADDRESS\" '{\n+      sub(/\\r$/, \"\")\n+      if(ipv4addr) { print ipv4addr\" \"$0; }\n+      if(ipv6addr) { print ipv6addr\" \"$0; }\n+    }' >> \"${2}\" < \"${1}\"\n+  else\n     echo -e \"${OVER}  ${CROSS} ${str}\"\n-    echo -e \"      ${COL_LIGHT_RED}No IP Values found! Please run 'pihole -r' and choose reconfigure to restore values${COL_NC}\"\n-    exit 1\n+    echo -e \"  ${COL_LIGHT_RED}No IP addresses found! Please run 'pihole -r' to reconfigure${COL_NC}\\\\n\"\n+    gravity_Cleanup \"error\"\n   fi\n }\n \n-gravity_hostFormatLocal() {\n-  # Format domain list as \"192.168.x.x domain.com\"\n+# Create \"localhost\" entries\n+gravity_ParseLocalDomains() {\n+  local hostname\n \n   if [[ -f \"/etc/hostname\" ]]; then\n-    hostname=$(< /etc/hostname)\n-  elif [ -x \"$(command -v hostname)\" ]; then\n+    hostname=$(< \"/etc/hostname\")\n+  elif command -v hostname > /dev/null; then\n     hostname=$(hostname -f)\n   else\n     echo -e \"  ${CROSS} Unable to determine fully qualified domain name of host\"\n   fi\n \n-  echo -e \"${hostname}\\npi.hole\" > \"${localList}.tmp\"\n-  # Copy the file over as /etc/pihole/local.list so dnsmasq can use it\n-  rm \"${localList}\"\n-  gravity_doHostFormat \"${localList}.tmp\" \"${localList}\"\n-  rm \"${localList}.tmp\"\n+  echo -e \"${hostname}\\\\npi.hole\" > \"${localList}.tmp\"\n+  gravity_ParseDomainsIntoHosts \"${localList}.tmp\" \"${localList}\"\n }\n \n-gravity_hostFormatGravity() {\n-  # Format domain list as \"192.168.x.x domain.com\"\n-  echo \"\" > \"${piholeDir}/${accretionDisc}\"\n-  gravity_doHostFormat \"${piholeDir}/${eventHorizon}\" \"${piholeDir}/${accretionDisc}\"\n-  # Copy the file over as /etc/pihole/gravity.list so dnsmasq can use it\n-  mv \"${piholeDir}/${accretionDisc}\" \"${adList}\"\n+# Create primary blacklist entries\n+gravity_ParseBlacklistDomains() {\n+  local output status\n+\n+  # Empty $accretionDisc if it already exists, otherwise, create it\n+  : > \"${piholeDir}/${accretionDisc}\"\n+\n+  gravity_ParseDomainsIntoHosts \"${piholeDir}/${whitelistMatter}\" \"${piholeDir}/${accretionDisc}\"\n \n+  # Move the file over as /etc/pihole/gravity.list so dnsmasq can use it\n+  output=$( { mv \"${piholeDir}/${accretionDisc}\" \"${adList}\"; } 2>&1 )\n+  status=\"$?\"\n+\n+  if [[ \"${status}\" -ne 0 ]]; then\n+    echo -e \"  ${CROSS} Unable to move ${accretionDisc} from ${piholeDir}\\\\n  ${output}\"\n+    gravity_Cleanup \"error\"\n+  fi\n }\n \n-gravity_hostFormatBlack() {\n+# Create user-added blacklist entries\n+gravity_ParseUserDomains() {\n   if [[ -f \"${blacklistFile}\" ]]; then\n-    numBlacklisted=$(wc -l < \"${blacklistFile}\")\n-    # Format domain list as \"192.168.x.x domain.com\"\n-    gravity_doHostFormat \"${blacklistFile}\" \"${blackList}.tmp\"\n+    gravity_ParseDomainsIntoHosts \"${blacklistFile}\" \"${blackList}.tmp\"\n     # Copy the file over as /etc/pihole/black.list so dnsmasq can use it\n-    mv \"${blackList}.tmp\" \"${blackList}\"\n+    mv \"${blackList}.tmp\" \"${blackList}\" 2> /dev/null || \\\n+      echo -e \"  ${CROSS} Unable to move ${blackList##*/}.tmp to ${piholeDir}\"\n   else\n     echo -e \"  ${INFO} Nothing to blacklist!\"\n   fi\n }\n \n-# blackbody - remove any remnant files from script processes\n-gravity_blackbody() {\n-  # Loop through list files\n-  for file in ${piholeDir}/*.${justDomainsExtension}; do\n-    # If list is in active array then leave it (noop) else rm the list\n-    if [[ \" ${activeDomains[@]} \" =~ ${file} ]]; then\n-      :\n-    else\n-      rm -f \"${file}\"\n-    fi\n-  done\n+# Trap Ctrl-C\n+gravity_Trap() {\n+  trap '{ echo -e \"\\\\n\\\\n  ${INFO} ${COL_LIGHT_RED}User-abort detected${COL_NC}\"; gravity_Cleanup \"error\"; }' INT\n }\n \n-gravity_advanced() {\n-  # Remove comments and print only the domain name\n-  # Most of the lists downloaded are already in hosts file format but the spacing/formating is not contigious\n-  # This helps with that and makes it easier to read\n-  # It also helps with debugging so each stage of the script can be researched more in depth\n-  local str=\"Formatting list of domains to remove comments\"\n+# Clean up after Gravity\n+gravity_Cleanup() {\n+  local error=\"${1:-}\"\n+\n+  str=\"Cleaning up stray matter\"\n   echo -ne \"  ${INFO} ${str}...\"\n \n-  #awk '($1 !~ /^#/) { if (NF>1) {print $2} else {print $1}}' ${piholeDir}/${matterAndLight} | sed -nr -e 's/\\.{2,}/./g' -e '/\\./p' >  ${piholeDir}/${supernova}\n-  #Above line does not correctly grab domains where comment is on the same line (e.g 'addomain.com #comment')\n-  #Awk -F splits on given IFS, we grab the right hand side (chops trailing #coments and /'s to grab the domain only.\n-  #Last awk command takes non-commented lines and if they have 2 fields, take the left field (the domain) and leave\n-  #+ the right (IP address), otherwise grab the single field.\n-  cat ${piholeDir}/${matterAndLight} | \\\n-    awk -F '#' '{print $1}' | \\\n-    awk -F '/' '{print $1}' | \\\n-    awk '($1 !~ /^#/) { if (NF>1) {print $2} else {print $1}}' | \\\n-    sed -nr -e 's/\\.{2,}/./g' -e '/\\./p' >  ${piholeDir}/${supernova}\n+  rm ${piholeDir}/pihole.*.txt 2> /dev/null\n+  rm ${piholeDir}/*.tmp 2> /dev/null\n+\n+  # Remove any unused .domains files\n+  for file in ${piholeDir}/*.${domainsExtension}; do\n+    # If list is not in active array, then remove it\n+    if [[ ! \"${activeDomains[*]}\" =~ \"${file}\" ]]; then\n+      rm -f \"${file}\" 2> /dev/null || \\\n+        echo -e \"  ${CROSS} Failed to remove ${file##*/}\"\n+    fi\n+  done\n \n   echo -e \"${OVER}  ${TICK} ${str}\"\n+  \n+  [[ -n \"${error}\" ]] && echo \"\"\n \n-  numberOf=$(wc -l < ${piholeDir}/${supernova})\n-  echo -e \"  ${INFO} ${COL_LIGHT_BLUE}${numberOf}${COL_NC} domains being pulled in by gravity\"\n+  # Only restart DNS service if offline\n+  if ! pidof dnsmasq &> /dev/null; then\n+    \"${PIHOLE_COMMAND}\" restartdns\n+  fi\n \n-  gravity_unique\n+  # Print Pi-hole status if an error occured\n+  if [[ -n \"${error}\" ]]; then\n+    \"${PIHOLE_COMMAND}\" status\n+    exit 1\n+  fi\n }\n \n-gravity_reload() {\n-  # Reload hosts file\n-  echo \"\"\n-  local str=\"Refreshing lists in dnsmasq\"\n-  echo -e \"  ${INFO} ${str}...\"\n-\n-  # Ensure /etc/dnsmasq.d/01-pihole.conf is pointing at the correct list!\n-  # First escape forward slashes in the path:\n-  adList=${adList//\\//\\\\\\/}\n-  # Now replace the line in dnsmasq file\n-  #  sed -i \"s/^addn-hosts.*/addn-hosts=$adList/\" /etc/dnsmasq.d/01-pihole.conf\n+helpFunc() {\n+  echo \"Usage: pihole -g\n+Update domains from blocklists specified in adlists.list\n \n-  \"${PIHOLE_COMMAND}\" restartdns\n+Options:\n+  -f, --force          Force the download of all specified blocklists\n+  -h, --help           Show this help dialog\"","path":"gravity.sh","position":835,"original_position":835,"commit_id":"a5c4ae955acdc1c8b13f06acf719e4ddaa8ea40d","original_commit_id":"56b8c4bfdbff2e290220baaee3e27bb369d35def","user":{"login":"WaLLy3K","id":3049142,"avatar_url":"https://avatars3.githubusercontent.com/u/3049142?v=4","gravatar_id":"","url":"https://api.github.com/users/WaLLy3K","html_url":"https://github.com/WaLLy3K","followers_url":"https://api.github.com/users/WaLLy3K/followers","following_url":"https://api.github.com/users/WaLLy3K/following{/other_user}","gists_url":"https://api.github.com/users/WaLLy3K/gists{/gist_id}","starred_url":"https://api.github.com/users/WaLLy3K/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/WaLLy3K/subscriptions","organizations_url":"https://api.github.com/users/WaLLy3K/orgs","repos_url":"https://api.github.com/users/WaLLy3K/repos","events_url":"https://api.github.com/users/WaLLy3K/events{/privacy}","received_events_url":"https://api.github.com/users/WaLLy3K/received_events","type":"User","site_admin":false},"body":"Ah! I've definitely considered adding those flags, but opted not to because I couldn't think of any use case where they'd be needed.","created_at":"2017-09-03T16:15:15Z","updated_at":"2017-09-03T16:15:15Z","html_url":"https://github.com/pi-hole/pi-hole/pull/1631#discussion_r136720348","pull_request_url":"https://api.github.com/repos/pi-hole/pi-hole/pulls/1631","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/pi-hole/pi-hole/pulls/comments/136720348"},"html":{"href":"https://github.com/pi-hole/pi-hole/pull/1631#discussion_r136720348"},"pull_request":{"href":"https://api.github.com/repos/pi-hole/pi-hole/pulls/1631"}},"in_reply_to_id":136704487},"pull_request":{"url":"https://api.github.com/repos/pi-hole/pi-hole/pulls/1631","id":132028603,"html_url":"https://github.com/pi-hole/pi-hole/pull/1631","diff_url":"https://github.com/pi-hole/pi-hole/pull/1631.diff","patch_url":"https://github.com/pi-hole/pi-hole/pull/1631.patch","issue_url":"https://api.github.com/repos/pi-hole/pi-hole/issues/1631","number":1631,"state":"open","locked":false,"title":"Clean up and optimise Gravity","user":{"login":"WaLLy3K","id":3049142,"avatar_url":"https://avatars3.githubusercontent.com/u/3049142?v=4","gravatar_id":"","url":"https://api.github.com/users/WaLLy3K","html_url":"https://github.com/WaLLy3K","followers_url":"https://api.github.com/users/WaLLy3K/followers","following_url":"https://api.github.com/users/WaLLy3K/following{/other_user}","gists_url":"https://api.github.com/users/WaLLy3K/gists{/gist_id}","starred_url":"https://api.github.com/users/WaLLy3K/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/WaLLy3K/subscriptions","organizations_url":"https://api.github.com/users/WaLLy3K/orgs","repos_url":"https://api.github.com/users/WaLLy3K/repos","events_url":"https://api.github.com/users/WaLLy3K/events{/privacy}","received_events_url":"https://api.github.com/users/WaLLy3K/received_events","type":"User","site_admin":false},"body":"**By submitting this pull request, I confirm the following:**\r\n\r\n- [x] I have read and understood the [contributors guide](https://github.com/pi-hole/pi-hole/blob/master/CONTRIBUTING.md).\r\n- [x] I have checked that [another pull request](https://github.com/pi-hole/pi-hole/pulls) for this purpose does not exist.\r\n- [x] I have considered, and confirmed that this submission will be valuable to others.\r\n- [x] I accept that this submission may not be used, and the pull request closed at the will of the maintainer.\r\n- [x] I give this submission freely, and claim no ownership to its content.\r\n\r\n**How familiar are you with the codebase?:**\r\n\r\n9\r\n\r\n---\r\nSome pictures of Gravity running can be [found here](https://imgur.com/a/kf8iT).\r\n\r\nChanges for `gravity.sh`\r\n* Shellcheck validation\r\n* Made variable names, function names, comments and output clearer to understand\r\n* Quoted and braced variables and conditionals\r\n* Fix adlists.list handling logic, and remove any CR line endings\r\n* Make CP/MV/RM provide user-friendly output upon failure\r\n* Change adlists.list retrieval logic\r\n* Moved and fixed adlists.list domain parsing logic\r\n* Create gravity_ParseFileAsDomains() function to handle parsing of source files\r\n* If no changes to a list is detected, print no output\r\n* Ensure each source blocklist has a final newline\r\n* Format number output as currency\r\n* Make array of adlists domain sources unique to prevent redundant whitelisting\r\n* Merged bash IPv4/IPv6 hosts formatting IF statement into an awk one-liner\r\n* Trap Ctrl-C cancellations and run gravity_Cleanup()\r\n* Use new gravity_Cleanup() function on errors and script completion\r\n* Ensure that dnsmasq uses force-reload when gravity is invoked\r\n* Add --wildcard option to ensure dnsmasq is restarted upon b/wlisting of a wildcard\r\n\r\nChanges for `list.sh`\r\n* Add \"--blacklist-only\" to only run essential gravity functions\r\n* Pass \"--wildcard\" option to `gravity.sh` to ensure dnsmasq is restarted\r\n\r\nChanges for `pihole`\r\n* Made use of $PI_HOLE_SCRIPT_DIR\r\n* Fix #1610 & #1624\r\n* Add \"$2\" to restartDNS options\r\n\r\nChanges for `webpage.sh`\r\n* Make RestartDNS() use `pihole restartdns`\r\n","created_at":"2017-07-24T11:31:07Z","updated_at":"2017-09-03T16:15:15Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e5ba96d01f25437f6055087508cff13017962177","assignee":null,"assignees":[],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/pi-hole/pi-hole/pulls/1631/commits","review_comments_url":"https://api.github.com/repos/pi-hole/pi-hole/pulls/1631/comments","review_comment_url":"https://api.github.com/repos/pi-hole/pi-hole/pulls/comments{/number}","comments_url":"https://api.github.com/repos/pi-hole/pi-hole/issues/1631/comments","statuses_url":"https://api.github.com/repos/pi-hole/pi-hole/statuses/a5c4ae955acdc1c8b13f06acf719e4ddaa8ea40d","head":{"label":"pi-hole:tweak/gravity","ref":"tweak/gravity","sha":"a5c4ae955acdc1c8b13f06acf719e4ddaa8ea40d","user":{"login":"pi-hole","id":16827203,"avatar_url":"https://avatars2.githubusercontent.com/u/16827203?v=4","gravatar_id":"","url":"https://api.github.com/users/pi-hole","html_url":"https://github.com/pi-hole","followers_url":"https://api.github.com/users/pi-hole/followers","following_url":"https://api.github.com/users/pi-hole/following{/other_user}","gists_url":"https://api.github.com/users/pi-hole/gists{/gist_id}","starred_url":"https://api.github.com/users/pi-hole/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pi-hole/subscriptions","organizations_url":"https://api.github.com/users/pi-hole/orgs","repos_url":"https://api.github.com/users/pi-hole/repos","events_url":"https://api.github.com/users/pi-hole/events{/privacy}","received_events_url":"https://api.github.com/users/pi-hole/received_events","type":"Organization","site_admin":false},"repo":{"id":20619036,"name":"pi-hole","full_name":"pi-hole/pi-hole","owner":{"login":"pi-hole","id":16827203,"avatar_url":"https://avatars2.githubusercontent.com/u/16827203?v=4","gravatar_id":"","url":"https://api.github.com/users/pi-hole","html_url":"https://github.com/pi-hole","followers_url":"https://api.github.com/users/pi-hole/followers","following_url":"https://api.github.com/users/pi-hole/following{/other_user}","gists_url":"https://api.github.com/users/pi-hole/gists{/gist_id}","starred_url":"https://api.github.com/users/pi-hole/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pi-hole/subscriptions","organizations_url":"https://api.github.com/users/pi-hole/orgs","repos_url":"https://api.github.com/users/pi-hole/repos","events_url":"https://api.github.com/users/pi-hole/events{/privacy}","received_events_url":"https://api.github.com/users/pi-hole/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/pi-hole/pi-hole","description":"A black hole for Internet advertisements","fork":false,"url":"https://api.github.com/repos/pi-hole/pi-hole","forks_url":"https://api.github.com/repos/pi-hole/pi-hole/forks","keys_url":"https://api.github.com/repos/pi-hole/pi-hole/keys{/key_id}","collaborators_url":"https://api.github.com/repos/pi-hole/pi-hole/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/pi-hole/pi-hole/teams","hooks_url":"https://api.github.com/repos/pi-hole/pi-hole/hooks","issue_events_url":"https://api.github.com/repos/pi-hole/pi-hole/issues/events{/number}","events_url":"https://api.github.com/repos/pi-hole/pi-hole/events","assignees_url":"https://api.github.com/repos/pi-hole/pi-hole/assignees{/user}","branches_url":"https://api.github.com/repos/pi-hole/pi-hole/branches{/branch}","tags_url":"https://api.github.com/repos/pi-hole/pi-hole/tags","blobs_url":"https://api.github.com/repos/pi-hole/pi-hole/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/pi-hole/pi-hole/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/pi-hole/pi-hole/git/refs{/sha}","trees_url":"https://api.github.com/repos/pi-hole/pi-hole/git/trees{/sha}","statuses_url":"https://api.github.com/repos/pi-hole/pi-hole/statuses/{sha}","languages_url":"https://api.github.com/repos/pi-hole/pi-hole/languages","stargazers_url":"https://api.github.com/repos/pi-hole/pi-hole/stargazers","contributors_url":"https://api.github.com/repos/pi-hole/pi-hole/contributors","subscribers_url":"https://api.github.com/repos/pi-hole/pi-hole/subscribers","subscription_url":"https://api.github.com/repos/pi-hole/pi-hole/subscription","commits_url":"https://api.github.com/repos/pi-hole/pi-hole/commits{/sha}","git_commits_url":"https://api.github.com/repos/pi-hole/pi-hole/git/commits{/sha}","comments_url":"https://api.github.com/repos/pi-hole/pi-hole/comments{/number}","issue_comment_url":"https://api.github.com/repos/pi-hole/pi-hole/issues/comments{/number}","contents_url":"https://api.github.com/repos/pi-hole/pi-hole/contents/{+path}","compare_url":"https://api.github.com/repos/pi-hole/pi-hole/compare/{base}...{head}","merges_url":"https://api.github.com/repos/pi-hole/pi-hole/merges","archive_url":"https://api.github.com/repos/pi-hole/pi-hole/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/pi-hole/pi-hole/downloads","issues_url":"https://api.github.com/repos/pi-hole/pi-hole/issues{/number}","pulls_url":"https://api.github.com/repos/pi-hole/pi-hole/pulls{/number}","milestones_url":"https://api.github.com/repos/pi-hole/pi-hole/milestones{/number}","notifications_url":"https://api.github.com/repos/pi-hole/pi-hole/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/pi-hole/pi-hole/labels{/name}","releases_url":"https://api.github.com/repos/pi-hole/pi-hole/releases{/id}","deployments_url":"https://api.github.com/repos/pi-hole/pi-hole/deployments","created_at":"2014-06-08T15:02:55Z","updated_at":"2017-09-03T12:05:07Z","pushed_at":"2017-09-03T05:28:43Z","git_url":"git://github.com/pi-hole/pi-hole.git","ssh_url":"git@github.com:pi-hole/pi-hole.git","clone_url":"https://github.com/pi-hole/pi-hole.git","svn_url":"https://github.com/pi-hole/pi-hole","homepage":"https://pi-hole.net","size":3043,"stargazers_count":5409,"watchers_count":5409,"language":"Shell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":470,"mirror_url":null,"open_issues_count":72,"forks":470,"open_issues":72,"watchers":5409,"default_branch":"master"}},"base":{"label":"pi-hole:development","ref":"development","sha":"d7d53849a31fc461c2156372ddbd8ece107a5b55","user":{"login":"pi-hole","id":16827203,"avatar_url":"https://avatars2.githubusercontent.com/u/16827203?v=4","gravatar_id":"","url":"https://api.github.com/users/pi-hole","html_url":"https://github.com/pi-hole","followers_url":"https://api.github.com/users/pi-hole/followers","following_url":"https://api.github.com/users/pi-hole/following{/other_user}","gists_url":"https://api.github.com/users/pi-hole/gists{/gist_id}","starred_url":"https://api.github.com/users/pi-hole/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pi-hole/subscriptions","organizations_url":"https://api.github.com/users/pi-hole/orgs","repos_url":"https://api.github.com/users/pi-hole/repos","events_url":"https://api.github.com/users/pi-hole/events{/privacy}","received_events_url":"https://api.github.com/users/pi-hole/received_events","type":"Organization","site_admin":false},"repo":{"id":20619036,"name":"pi-hole","full_name":"pi-hole/pi-hole","owner":{"login":"pi-hole","id":16827203,"avatar_url":"https://avatars2.githubusercontent.com/u/16827203?v=4","gravatar_id":"","url":"https://api.github.com/users/pi-hole","html_url":"https://github.com/pi-hole","followers_url":"https://api.github.com/users/pi-hole/followers","following_url":"https://api.github.com/users/pi-hole/following{/other_user}","gists_url":"https://api.github.com/users/pi-hole/gists{/gist_id}","starred_url":"https://api.github.com/users/pi-hole/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pi-hole/subscriptions","organizations_url":"https://api.github.com/users/pi-hole/orgs","repos_url":"https://api.github.com/users/pi-hole/repos","events_url":"https://api.github.com/users/pi-hole/events{/privacy}","received_events_url":"https://api.github.com/users/pi-hole/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/pi-hole/pi-hole","description":"A black hole for Internet advertisements","fork":false,"url":"https://api.github.com/repos/pi-hole/pi-hole","forks_url":"https://api.github.com/repos/pi-hole/pi-hole/forks","keys_url":"https://api.github.com/repos/pi-hole/pi-hole/keys{/key_id}","collaborators_url":"https://api.github.com/repos/pi-hole/pi-hole/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/pi-hole/pi-hole/teams","hooks_url":"https://api.github.com/repos/pi-hole/pi-hole/hooks","issue_events_url":"https://api.github.com/repos/pi-hole/pi-hole/issues/events{/number}","events_url":"https://api.github.com/repos/pi-hole/pi-hole/events","assignees_url":"https://api.github.com/repos/pi-hole/pi-hole/assignees{/user}","branches_url":"https://api.github.com/repos/pi-hole/pi-hole/branches{/branch}","tags_url":"https://api.github.com/repos/pi-hole/pi-hole/tags","blobs_url":"https://api.github.com/repos/pi-hole/pi-hole/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/pi-hole/pi-hole/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/pi-hole/pi-hole/git/refs{/sha}","trees_url":"https://api.github.com/repos/pi-hole/pi-hole/git/trees{/sha}","statuses_url":"https://api.github.com/repos/pi-hole/pi-hole/statuses/{sha}","languages_url":"https://api.github.com/repos/pi-hole/pi-hole/languages","stargazers_url":"https://api.github.com/repos/pi-hole/pi-hole/stargazers","contributors_url":"https://api.github.com/repos/pi-hole/pi-hole/contributors","subscribers_url":"https://api.github.com/repos/pi-hole/pi-hole/subscribers","subscription_url":"https://api.github.com/repos/pi-hole/pi-hole/subscription","commits_url":"https://api.github.com/repos/pi-hole/pi-hole/commits{/sha}","git_commits_url":"https://api.github.com/repos/pi-hole/pi-hole/git/commits{/sha}","comments_url":"https://api.github.com/repos/pi-hole/pi-hole/comments{/number}","issue_comment_url":"https://api.github.com/repos/pi-hole/pi-hole/issues/comments{/number}","contents_url":"https://api.github.com/repos/pi-hole/pi-hole/contents/{+path}","compare_url":"https://api.github.com/repos/pi-hole/pi-hole/compare/{base}...{head}","merges_url":"https://api.github.com/repos/pi-hole/pi-hole/merges","archive_url":"https://api.github.com/repos/pi-hole/pi-hole/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/pi-hole/pi-hole/downloads","issues_url":"https://api.github.com/repos/pi-hole/pi-hole/issues{/number}","pulls_url":"https://api.github.com/repos/pi-hole/pi-hole/pulls{/number}","milestones_url":"https://api.github.com/repos/pi-hole/pi-hole/milestones{/number}","notifications_url":"https://api.github.com/repos/pi-hole/pi-hole/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/pi-hole/pi-hole/labels{/name}","releases_url":"https://api.github.com/repos/pi-hole/pi-hole/releases{/id}","deployments_url":"https://api.github.com/repos/pi-hole/pi-hole/deployments","created_at":"2014-06-08T15:02:55Z","updated_at":"2017-09-03T12:05:07Z","pushed_at":"2017-09-03T05:28:43Z","git_url":"git://github.com/pi-hole/pi-hole.git","ssh_url":"git@github.com:pi-hole/pi-hole.git","clone_url":"https://github.com/pi-hole/pi-hole.git","svn_url":"https://github.com/pi-hole/pi-hole","homepage":"https://pi-hole.net","size":3043,"stargazers_count":5409,"watchers_count":5409,"language":"Shell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":470,"mirror_url":null,"open_issues_count":72,"forks":470,"open_issues":72,"watchers":5409,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/pi-hole/pi-hole/pulls/1631"},"html":{"href":"https://github.com/pi-hole/pi-hole/pull/1631"},"issue":{"href":"https://api.github.com/repos/pi-hole/pi-hole/issues/1631"},"comments":{"href":"https://api.github.com/repos/pi-hole/pi-hole/issues/1631/comments"},"review_comments":{"href":"https://api.github.com/repos/pi-hole/pi-hole/pulls/1631/comments"},"review_comment":{"href":"https://api.github.com/repos/pi-hole/pi-hole/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/pi-hole/pi-hole/pulls/1631/commits"},"statuses":{"href":"https://api.github.com/repos/pi-hole/pi-hole/statuses/a5c4ae955acdc1c8b13f06acf719e4ddaa8ea40d"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"WaLLy3K"}}