From 15867c03787fe21c26a249e805fa987923c1037e Mon Sep 17 00:00:00 2001 From: Michael Stanclift Date: Tue, 3 May 2022 10:50:05 -0500 Subject: [PATCH] 4.0.4 (#343) * Start 4.0.4 * SDHCP replication * Fix typo of SDHCP * another SDHCP reference * Update readme --- README.md | 17 ++-- VERSION | 2 +- gravity-sync | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 227 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5884bdc..38fc789 100644 --- a/README.md +++ b/README.md @@ -18,22 +18,25 @@ But if you have redundant Pi-hole in your network you'll want a simple way to ke ## Features -Gravity Sync replicates the core of Pi-hole's resolver settings, which includes: +Gravity Sync replicates the core of Pi-hole's ad/telemetry blocking settings, which includes: - Adlist settings with status and comments. -- Domain whitelist and blacklist along with status with comments. -- Custom RegEx whitelist and blacklists. -- Clients and groups, along with any list assignments. +- Domain/RegEx whitelists and blacklist along with status and comments. +- Clients and group assignments, along with status and descriptions. + +Gravity Sync also replicates local network DNS/DHCP settings, which includes: + - Local DNS Records. - Local CNAME Records. +- Static DHCP Assignments. ### Limitations Gravity Sync will **not**: -- Modify the individual Pi-hole's upstream DNS resolvers. -- Sync DHCP settings or monitor device leases. -- Merge long term data, query logs, or statistics. +- Modify or sync the individual Pi-hole's upstream DNS resolvers. +- Merge query logs, statistics, long-term data, caches, or other resolution information. +- Sync individual Pi-hole DHCP scoping information or leases. ## Setup Steps diff --git a/VERSION b/VERSION index aa31e71..7d666cb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.0.3 \ No newline at end of file +4.0.4 \ No newline at end of file diff --git a/gravity-sync b/gravity-sync index 20f7e06..d42dd8c 100755 --- a/gravity-sync +++ b/gravity-sync @@ -4,7 +4,7 @@ GS_RUN_START=$SECONDS # GRAVITY SYNC BY VMSTAN ##################### PROGRAM='Gravity Sync' -GS_VERSION='4.0.3' +GS_VERSION='4.0.4' # For documentation or the changelog/updates visit https://github.com/vmstan/gravity-sync # Requires Pi-Hole 5.x or higher already be installed, for help visit https://pi-hole.net @@ -51,6 +51,7 @@ PIHOLE_CONTAINER_IMAGE='pihole/pihole' # Official Pi-hole container ima PH_GRAVITY_FI='gravity.db' # Pi-hole database file name PH_CUSTOM_DNS='custom.list' # Pi-hole DNS lookup filename PH_CNAME_CONF='05-pihole-custom-cname.conf' # DNSMASQ CNAME alias file +PH_SDHCP_CONF='04-pihole-static-dhcp.conf' # DNSMASQ Static DHCP file # Backup Customization GS_BACKUP_TIMEOUT='240' # replace in gravity-sync.conf to overwrite @@ -65,6 +66,7 @@ GS_SYNCING_LOG='gs-sync.log' # replace in grav GS_GRAVITY_FI_MD5_LOG='gs-gravity.md5' # replace in gravity-sync.conf to overwrite GS_CUSTOM_DNS_MD5_LOG='gs-clist.md5' # replace in gravity-sync.conf to overwrite GS_CNAME_CONF_MD5_LOG='05-pihole-custom-cname.conf.md5' # replace in gravity-sync.conf to overwrite +GS_SDHCP_CONF_MD5_LOG='04-pihole-static-dhcp.conf.md5' # replace in gravity-sync.conf to overwrite # SSH Customization GS_SSH_PORT='22' # replace in gravity-sync.conf to overwrite @@ -82,6 +84,7 @@ OS_SSH_CMD='ssh' UI_GRAVITY_NAME='Gravity Database' UI_CUSTOM_NAME='DNS Records' UI_CNAME_NAME='DNS CNAMEs' +UI_SDHCP_NAME='Static DHCP Addresses' # Reused UI Text UI_CORE_LOADING='Loading' UI_CORE_EVALUATING='Evaluating arguments' @@ -384,6 +387,28 @@ function pull_gs_cname { fi } +## Pull DHCP +function pull_gs_sdhcp { + if [ "$REMOTE_SDHCP_DNS" == "1" ]; then + backup_local_sdhcp + backup_remote_sdhcp + + MESSAGE="${UI_PULL_REMOTE} ${UI_SDHCP_NAME}" + echo_stat + RSYNC_REPATH="sudo rsync" + RSYNC_SOURCE="${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PIHOLE_DIRECTORY}/${PH_SDHCP_CONF}.${GS_BACKUP_EXT}" + RSYNC_TARGET="${OS_TMP}/${PH_SDHCP_CONF}.${GS_BACKUP_EXT}" + create_rsync_cmd + + MESSAGE="${UI_REPLACE_LOCAL} ${UI_SDHCP_NAME}" + echo_stat + sudo mv ${OS_TMP}/${PH_SDHCP_CONF}.${GS_BACKUP_EXT} ${LOCAL_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF} >/dev/null 2>&1 + error_validate + + validate_sdhcp_permissions + fi +} + ## Pull Reload function pull_gs_reload { sleep 1 @@ -394,7 +419,7 @@ function pull_gs_reload { error_validate if [ "${GS_TASK_TYPE}" == SMART ]; then - if [ "${REMOTE_DNS_CHANGE}" == "1" ] || [ "${LOCAL_DNS_CHANGE}" == "1" ] || [ "${REMOTE_CNAME_CHANGE}" == "1" ] || [ "${LOCAL_CNAME_CHANGE}" == "1" ]; then + if [ "${REMOTE_DNS_CHANGE}" == "1" ] || [ "${LOCAL_DNS_CHANGE}" == "1" ] || [ "${REMOTE_CNAME_CHANGE}" == "1" ] || [ "${LOCAL_CNAME_CHANGE}" == "1" ] || [ "${REMOTE_SDHCP_CHANGE}" == "1" ] || [ "${LOCAL_SDHCP_CHANGE}" == "1" ]; then MESSAGE="${UI_FTLDNS_CONFIG_PULL_RELOAD}" echo_stat ${PH_EXEC} restartdns >/dev/null 2>&1 @@ -415,6 +440,7 @@ function pull_gs { pull_gs_grav pull_gs_custom pull_gs_cname + pull_gs_sdhcp pull_gs_reload md5_recheck logs_export @@ -517,6 +543,34 @@ function push_gs_cname { fi } +## Push Custom +function push_gs_sdhcp { + if [ "$REMOTE_SDHCP_DNS" == "1" ]; then + backup_remote_sdhcp + backup_local_sdhcp + + MESSAGE="${UI_PUSH_LOCAL} ${UI_SDHCP_NAME}" + echo_stat + RSYNC_REPATH="sudo rsync" + RSYNC_SOURCE="${LOCAL_PIHOLE_DIRECTORY}/${PH_SDHCP_CONF}.${GS_BACKUP_EXT}" + RSYNC_TARGET="${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF}" + create_rsync_cmd + + MESSAGE="${UI_SET_LOCAL_FILE_OWNERSHIP} ${UI_SDHCP_NAME}" + echo_stat + CMD_TIMEOUT=$GS_BACKUP_TIMEOUT + CMD_REQUESTED="sudo chown root:root ${REMOTE_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF}" + create_ssh_cmd + + + MESSAGE="${UI_SET_FILE_PERMISSION} ${UI_SDHCP_NAME}" + echo_stat + CMD_TIMEOUT=$GS_BACKUP_TIMEOUT + CMD_REQUESTED="sudo chmod 644 ${REMOTE_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF}" + create_ssh_cmd + fi +} + ## Push Reload function push_gs_reload { sleep 1 @@ -528,7 +582,7 @@ function push_gs_reload { create_ssh_cmd if [ "${GS_TASK_TYPE}" == SMART ]; then - if [ "${REMOTE_DNS_CHANGE}" == "1" ] || [ "${LOCAL_DNS_CHANGE}" == "1" ] || [ "${REMOTE_CNAME_CHANGE}" == "1" ] || [ "${LOCAL_CNAME_CHANGE}" == "1" ]; then + if [ "${REMOTE_DNS_CHANGE}" == "1" ] || [ "${LOCAL_DNS_CHANGE}" == "1" ] || [ "${REMOTE_CNAME_CHANGE}" == "1" ] || [ "${LOCAL_CNAME_CHANGE}" == "1" ] || [ "${REMOTE_SDHCP_CHANGE}" == "1" ] || [ "${LOCAL_SDHCP_CHANGE}" == "1" ]; then MESSAGE="${UI_FTLDNS_CONFIG_PUSH_RELOAD}" echo_stat CMD_TIMEOUT=$GS_BACKUP_TIMEOUT @@ -551,6 +605,7 @@ function push_gs { push_gs_grav push_gs_custom push_gs_cname + push_gs_sdhcp push_gs_reload md5_recheck logs_export @@ -586,6 +641,8 @@ function smart_gs { LOCAL_DNS_CHANGE="0" REMOTE_CNAME_CHANGE="0" LOCAL_CNAME_CHANGE="0" + REMOTE_SDHCP_CHANGE="0" + LOCAL_SDHCP_CHANGE="0" if [ "${REMOTE_DB_MD5}" != "${LAST_REMOTE_DB_MD5}" ]; then REMOTE_GRAVITY_CHANGE="1" @@ -716,6 +773,51 @@ function smart_gs { pull_gs_cname GS_PULL_RESTART="1" fi + + if [ "${REMOTE_SDHCP_MD5}" != "${LAST_REMOTE_SDHCP_MD5}" ]; then + REMOTE_SDHCP_CHANGE="1" + fi + + if [ "${LOCAL_SDHCP_MD5}" != "${LAST_LOCAL_SDHCP_MD5}" ]; then + LOCAL_SDHCP_CHANGE="1" + fi + + if [ -f "${LOCAL_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF}" ]; then + if [ "${REMOTE_SDHCP_CHANGE}" == "${LOCAL_SDHCP_CHANGE}" ]; then + if [ "${REMOTE_SDHCP_CHANGE}" != "0" ]; then + MESSAGE="Both ${UI_SDHCP_NAME} have Changed" + echo_warn + + REMOTE_SDHCP_DATE=$(${OS_SSH_CMD} -p ${GS_SSH_PORT} -i "${GS_SSH_PKIF}" ${REMOTE_USER}@${REMOTE_HOST} "stat -c %Y ${REMOTE_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF}") + LOCAL_SDHCP_DATE=$(stat -c %Y ${LOCAL_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF}) + + if (( "$REMOTE_SDHCP_DATE" >= "$LOCAL_SDHCP_DATE" )); then + MESSAGE="Remote ${UI_SDHCP_NAME} was last changed" + echo_warn + + pull_gs_sdhcp + GS_PULL_RESTART="1" + else + MESSAGE="Local ${UI_SDHCP_NAME} was last changed" + echo_warn + + push_gs_sdhcp + GS_PUSH_RESTART="1" + fi + fi + else + if [ "${REMOTE_SDHCP_CHANGE}" != "0" ]; then + pull_gs_sdhcp + GS_PULL_RESTART="1" + elif [ "${LOCAL_SDHCP_CHANGE}" != "0" ]; then + push_gs_sdhcp + GS_PUSH_RESTART="1" + fi + fi + else + pull_gs_sdhcp + GS_PULL_RESTART="1" + fi if [ "$GS_PULL_RESTART" == "1" ]; then pull_gs_reload @@ -804,6 +906,19 @@ function backup_local_cname { fi } +function backup_local_sdhcp { + if [ -f ${LOCAL_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF} ]; then + MESSAGE="${UI_BACKUP_LOCAL} ${UI_SDHCP_NAME}" + echo_stat + + sudo cp ${LOCAL_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF} ${LOCAL_PIHOLE_DIRECTORY}/${PH_SDHCP_CONF}.${GS_BACKUP_EXT} + error_validate + else + MESSAGE="No local ${PH_SDHCP_CONF} detected" + echo_warn + fi +} + function backup_remote_gravity { MESSAGE="${UI_BACKUP_REMOTE} ${UI_GRAVITY_NAME}" echo_stat @@ -873,6 +988,15 @@ function backup_remote_cname { create_ssh_cmd } +function backup_remote_sdhcp { + MESSAGE="${UI_BACKUP_REMOTE} ${UI_SDHCP_NAME}" + echo_stat + + CMD_TIMEOUT=$GS_BACKUP_TIMEOUT + CMD_REQUESTED="sudo cp ${REMOTE_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF} ${REMOTE_PIHOLE_DIRECTORY}/${PH_SDHCP_CONF}.${GS_BACKUP_EXT}" + create_ssh_cmd +} + function backup_cleanup { MESSAGE="Purging backups" echo_stat @@ -980,6 +1104,43 @@ function md5_compare { MESSAGE="${UI_CNAME_NAME} ${UI_HASHING_NOT_DETECTED} ${UI_HASHING_LOCAL}" echo_warn fi + + if [ -f ${LOCAL_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF} ]; then + if ${OS_SSH_CMD} -p ${GS_SSH_PORT} -i "${GS_SSH_PKIF}" ${REMOTE_USER}@${REMOTE_HOST} test -e ${REMOTE_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF}; then + REMOTE_SDHCP_DNS="1" + MESSAGE="${UI_HASHING_HASHING} ${UI_SDHCP_NAME}" + echo_stat + + REMOTE_SDHCP_MD5=$(${OS_SSH_CMD} -p ${GS_SSH_PORT} -i "${GS_SSH_PKIF}" ${REMOTE_USER}@${REMOTE_HOST} "md5sum ${REMOTE_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF} | sed 's/\s.*$//'") + error_validate + + MESSAGE="${UI_HASHING_COMPARING} ${UI_SDHCP_NAME}" + echo_stat + LOCAL_SDHCP_MD5=$(md5sum ${LOCAL_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF} | sed 's/\s.*$//') + error_validate + + if [ "$REMOTE_SDHCP_MD5" == "$LAST_REMOTE_SDHCP_MD5" ] && [ "$LOCAL_SDHCP_MD5" == "$LAST_LOCAL_SDHCP_MD5" ]; then + GS_HASH_MARK=$((GS_HASH_MARK+0)) + else + MESSAGE="${UI_HASHING_DIFFERENCE} ${UI_SDHCP_NAME}" + echo_warn + GS_HASH_MARK=$((GS_HASH_MARK+1)) + fi + else + MESSAGE="${UI_SDHCP_NAME} ${UI_HASHING_NOT_DETECTED} ${UI_HASHING_REMOTE}" + echo_warn + fi + else + if ${OS_SSH_CMD} -p ${GS_SSH_PORT} -i "${GS_SSH_PKIF}" ${REMOTE_USER}@${REMOTE_HOST} test -e ${REMOTE_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF}; then + REMOTE_SDHCP_DNS="1" + MESSAGE="${UI_SDHCP_NAME} ${UI_HASHING_DETECTED} ${UI_HASHING_REMOTE}" + GS_HASH_MARK=$((GS_HASH_MARK+1)) + echo_warn + fi + + MESSAGE="${UI_SDHCP_NAME} ${UI_HASHING_NOT_DETECTED} ${UI_HASHING_LOCAL}" + echo_warn + fi if [ "$GS_HASH_MARK" != "0" ]; then MESSAGE="Replication of ${UI_CORE_APP} settings is required" @@ -1016,6 +1177,14 @@ function previous_md5 { LAST_REMOTE_CN_MD5="0" LAST_LOCAL_CN_MD5="0" fi + + if [ -f "${GS_ETC_PATH}/${GS_SDHCP_CONF_MD5_LOG}" ]; then + LAST_REMOTE_SDHCP_MD5=$(sed "1q;d" ${GS_ETC_PATH}/${GS_SDHCP_CONF_MD5_LOG}) + LAST_LOCAL_SDHCP_MD5=$(sed "2q;d" ${GS_ETC_PATH}/${GS_SDHCP_CONF_MD5_LOG}) + else + LAST_REMOTE_SDHCP_MD5="0" + LAST_LOCAL_SDHCP_MD5="0" + fi } function md5_recheck { @@ -1088,6 +1257,34 @@ function md5_recheck { MESSAGE="${UI_CNAME_NAME} ${UI_HASHING_NOT_DETECTED} ${UI_HASHING_LOCAL}" echo_warn fi + + if [ -f ${LOCAL_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF} ]; then + if ${OS_SSH_CMD} -p ${GS_SSH_PORT} -i "${GS_SSH_PKIF}" ${REMOTE_USER}@${REMOTE_HOST} test -e ${REMOTE_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF}; then + REMOTE_SDHCP_DNS="1" + MESSAGE="${UI_HASHING_REHASHING} ${UI_SDHCP_NAME}" + echo_stat + + REMOTE_SDHCP_MD5=$(${OS_SSH_CMD} -p ${GS_SSH_PORT} -i "${GS_SSH_PKIF}" ${REMOTE_USER}@${REMOTE_HOST} "md5sum ${REMOTE_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF} | sed 's/\s.*$//'") + error_validate + + MESSAGE="${UI_HASHING_RECOMPARING} ${UI_SDHCP_NAME}" + echo_stat + LOCAL_SDHCP_MD5=$(md5sum ${LOCAL_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF} | sed 's/\s.*$//') + error_validate + else + MESSAGE="${UI_SDHCP_NAME} ${UI_HASHING_NOT_DETECTED} ${UI_HASHING_REMOTE}" + echo_warn + fi + else + if ${OS_SSH_CMD} -p ${GS_SSH_PORT} -i "${GS_SSH_PKIF}" ${REMOTE_USER}@${REMOTE_HOST} test -e ${REMOTE_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF}; then + REMOTE_SDHCP_DNS="1" + MESSAGE="${UI_SDHCP_NAME} ${UI_HASHING_NOT_DETECTED} ${UI_HASHING_REMOTE}" + echo_warn + fi + + MESSAGE="${UI_SDHCP_NAME} ${UI_HASHING_NOT_DETECTED} ${UI_HASHING_LOCAL}" + echo_warn + fi } ## Determine SSH Pathways @@ -1177,6 +1374,8 @@ function logs_export { echo -e ${LOCAL_CL_MD5} | sudo tee -a ${GS_ETC_PATH}/${GS_CUSTOM_DNS_MD5_LOG} 1> /dev/null echo -e ${REMOTE_CN_MD5} | sudo tee -a ${GS_ETC_PATH}/${GS_CNAME_CONF_MD5_LOG} 1> /dev/null echo -e ${LOCAL_CN_MD5} | sudo tee -a ${GS_ETC_PATH}/${GS_CNAME_CONF_MD5_LOG} 1> /dev/null + echo -e ${REMOTE_SDHCP_MD5} | sudo tee -a ${GS_ETC_PATH}/${GS_SDHCP_CONF_MD5_LOG} 1> /dev/null + echo -e ${LOCAL_SDHCP_MD5} | sudo tee -a ${GS_ETC_PATH}/${GS_SDHCP_CONF_MD5_LOG} 1> /dev/null if [ "${GS_PEERLESS_MODE}" != "1" ]; then sudo rm -f ${OS_TMP}/*.md5 @@ -1186,6 +1385,8 @@ function logs_export { echo -e ${REMOTE_CL_MD5} | sudo tee -a ${OS_TMP}/${GS_CUSTOM_DNS_MD5_LOG} 1> /dev/null echo -e ${LOCAL_CN_MD5} | sudo tee -a ${OS_TMP}/${GS_CNAME_CONF_MD5_LOG} 1> /dev/null echo -e ${REMOTE_CN_MD5} | sudo tee -a ${OS_TMP}/${GS_CNAME_CONF_MD5_LOG} 1> /dev/null + echo -e ${LOCAL_SDHCP_MD5} | sudo tee -a ${OS_TMP}/${GS_SDHCP_CONF_MD5_LOG} 1> /dev/null + echo -e ${REMOTE_SDHCP_MD5} | sudo tee -a ${OS_TMP}/${GS_SDHCP_CONF_MD5_LOG} 1> /dev/null error_validate MESSAGE="Sending hashes to ${PROGRAM} peer" @@ -1386,6 +1587,18 @@ function validate_cname_permissions { error_validate } +function validate_sdhcp_permissions { + MESSAGE="${UI_SET_LOCAL_FILE_OWNERSHIP} ${UI_SDHCP_NAME}" + echo_stat + sudo chown root:root ${LOCAL_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF} >/dev/null 2>&1 + error_validate + + MESSAGE="${UI_SET_FILE_PERMISSION} ${UI_SDHCP_NAME}" + echo_stat + sudo chmod 644 ${LOCAL_DNSMASQ_DIRECTORY}/${PH_SDHCP_CONF} >/dev/null 2>&1 + error_validate +} + ## Validate Intent function intent_validate { PHASER=$((( RANDOM % 4 ) + 1 ))