From 5ca55f2b7a5ebde71827921f54af30cbe041e7ad Mon Sep 17 00:00:00 2001 From: Akutra Date: Tue, 30 Jan 2024 20:58:57 -0800 Subject: [PATCH 1/4] Add script to update a secondary dns server --- php/SyncDNS.sh | 6 + php/update_secondary_dnsserver.php | 185 +++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 php/SyncDNS.sh create mode 100644 php/update_secondary_dnsserver.php diff --git a/php/SyncDNS.sh b/php/SyncDNS.sh new file mode 100644 index 0000000..317bfcb --- /dev/null +++ b/php/SyncDNS.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +sudo /usr/bin/php update_secondary_dnsserver.php +sudo service bind9 restart diff --git a/php/update_secondary_dnsserver.php b/php/update_secondary_dnsserver.php new file mode 100644 index 0000000..bc28345 --- /dev/null +++ b/php/update_secondary_dnsserver.php @@ -0,0 +1,185 @@ + $domain) + { + if(NSValidator($domain)) { + array_push($filecontents, SecondaryStringTemplate($key, $domain['IP'])); + $totalUpdated++; + } + } + + if( $totalUpdated > 0) { + echo "$totalUpdated of $total defined as secondary authoritative domains." . PHP_EOL; + WriteBindConfig($file, $filecontents); + } else { + // In case of network or API error, don't change the file + echo "No zone records, $file not changed."; + } +} + +function SecondaryStringTemplate($domainname, $primary) +{ + // template for secondary domain on bind9, update this if syntax needs a change. + $templateValue = "zone \"$domainname\" { type slave; file \"db.$domainname\"; masters { $primary; }; };"; + return $templateValue; +} + +function NSValidator($domainvalues) +{ + global $config; + // only values that have the primary server as the SOA + if(isset($domainvalues['IP']) && isset($domainvalues['SOA']) && $config['primary'] == $domainvalues['SOA']) + { + return true; + } + return false; +} + +function GetHSUserDomans($user) +{ + // Get all domains for a Hestia user + $reqvars = array( + 'cmd' => 'v-list-dns-domains', + 'arg1' => $user, + 'arg2' => 'json' // use config + ); + + $domains = sanitizeBadJson(APIReq($reqvars)); + + $data = ""; + if(!empty($domains)) { + // Parse JSON output + $data = json_decode($domains, true); + } + + return $data; +} + +function GetHSDomains() +{ + // For each Hestia user GetUserDomains and merge + $users = GetHSUsers(); + + $alldomains = array(); + foreach($users as $key => $user) + { + $domains = GetHSUserDomans($user); + if( isset($domains) && !empty($domains) ) { // skip domainless users like possibly admin + $alldomains = array_merge($alldomains, $domains); } + } + return $alldomains; +} + +function GetHSUsers() +{ + // get all the users + $reqvars = array( + 'cmd' => 'v-list-sys-users', + 'arg1' => 'json' // use config + ); + + // request the user list + $users = sanitizeBadJson(APIReq($reqvars)); + + $data = ""; + if(!empty($users)) { + // Parse JSON output + $data = json_decode($users, true); + } + + return $data; +} + +function sanitizeBadJson($json) +{ + $minified = preg_replace('/\s+/', '',$json); + + // The ending comma causes json decode to fail so fix that. + if(str_ends_with($minified,",]")) { + return substr($minified, 0, -2)."]"; + } + return $minified; +} + +function APIReq($callreqvars) +{ + global $config; + + // Prepare POST query + $std_reqvars = array( + 'user' => $config['server']['username'], + 'password' => $config['server']['pwd'], + 'returncode' => $config['server']['returncode'] + ); + $postreqvars = array_merge( $callreqvars, $std_reqvars ); + + // Send POST query via cURL + $postreqdata = http_build_query($postreqvars); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, 'https://' . $config['server']['host'] . ':' . $config['server']['port'] . '/api/'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postreqdata); + $response = curl_exec($ch); + + if( str_starts_with($response, "Error: ")) + { + echo "WARNING Web Request ".$response.PHP_EOL; + //print_r($callreqvars); + $response = ""; + } + + return $response; +} + +// Server credentials +$config = array( + 'server' => array( + 'host' => 'server.hestiacp.com', + 'port' => '8083', + 'username' => 'admin', + 'pwd' => 'p4ssw0rd', + 'returncode' => 'no' + ), + 'primary' => 'ns1.hestiacp.com' // this is the primary server to use for this secondary server +); + +// for a basic server setup with bind9 the secondary records will be in the below file +$cfg_file = '/etc/bind/named.conf.local'; +// Update the config file, FYI, Bind may need to be restarted after +UpdateBindConfigFile($cfg_file); + From dc88de92c587b0020e25d85c4ed6410166b753e3 Mon Sep 17 00:00:00 2001 From: Akutra Date: Tue, 30 Jan 2024 21:07:45 -0800 Subject: [PATCH 2/4] Add comments --- php/SyncDNS.sh | 3 +++ php/update_secondary_dnsserver.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/php/SyncDNS.sh b/php/SyncDNS.sh index 317bfcb..f3f3099 100644 --- a/php/SyncDNS.sh +++ b/php/SyncDNS.sh @@ -4,3 +4,6 @@ set -Eeuo pipefail sudo /usr/bin/php update_secondary_dnsserver.php sudo service bind9 restart + +# Tested on a 2 doller per month IONOS VM running Ubuntu with bind9, php-cli, php-curl installed +# akutra.tm@leapmaker.com for more information \ No newline at end of file diff --git a/php/update_secondary_dnsserver.php b/php/update_secondary_dnsserver.php index bc28345..53028e8 100644 --- a/php/update_secondary_dnsserver.php +++ b/php/update_secondary_dnsserver.php @@ -183,3 +183,6 @@ function APIReq($callreqvars) // Update the config file, FYI, Bind may need to be restarted after UpdateBindConfigFile($cfg_file); +// Tested on a 2 doller per month IONOS VM running Ubuntu with bind9, php-cli, php-curl installed +// akutra.tm@leapmaker.com for more information + From 6205bfa51fdb18c7b3066c8b553c02acf5ce244a Mon Sep 17 00:00:00 2001 From: Akutra Date: Wed, 31 Jan 2024 13:14:02 -0800 Subject: [PATCH 3/4] Change to plain API for users - plain text simplicity - avoid json errors in old versions of Hestia --- php/update_secondary_dnsserver.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/php/update_secondary_dnsserver.php b/php/update_secondary_dnsserver.php index 53028e8..581d60f 100644 --- a/php/update_secondary_dnsserver.php +++ b/php/update_secondary_dnsserver.php @@ -107,16 +107,17 @@ function GetHSUsers() // get all the users $reqvars = array( 'cmd' => 'v-list-sys-users', - 'arg1' => 'json' // use config + 'arg1' => 'plain' // use plain for simplicity ); // request the user list - $users = sanitizeBadJson(APIReq($reqvars)); + $users = APIReq($reqvars); $data = ""; if(!empty($users)) { - // Parse JSON output - $data = json_decode($users, true); + // Parse output + // New line delimited list, use array filter to purge empty values. + $data = array_filter(explode("\x0A", $users)); } return $data; @@ -127,6 +128,7 @@ function sanitizeBadJson($json) $minified = preg_replace('/\s+/', '',$json); // The ending comma causes json decode to fail so fix that. + // Hestia versions prior to commit 77e69939c ("Replace bin/v-list-sys-users by bin/v-list-users (#3930)", 2023-08-16) if(str_ends_with($minified,",]")) { return substr($minified, 0, -2)."]"; } From 0ec12cb1e6078a6ea73fac5a47a65d668c582f16 Mon Sep 17 00:00:00 2001 From: Akutra Date: Wed, 31 Jan 2024 21:56:30 -0800 Subject: [PATCH 4/4] Move return code setting out of config since it is required for the script to run properly --- php/update_secondary_dnsserver.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/php/update_secondary_dnsserver.php b/php/update_secondary_dnsserver.php index 581d60f..c7615a1 100644 --- a/php/update_secondary_dnsserver.php +++ b/php/update_secondary_dnsserver.php @@ -143,7 +143,7 @@ function APIReq($callreqvars) $std_reqvars = array( 'user' => $config['server']['username'], 'password' => $config['server']['pwd'], - 'returncode' => $config['server']['returncode'] + 'returncode' => 'no' ); $postreqvars = array_merge( $callreqvars, $std_reqvars ); @@ -174,8 +174,7 @@ function APIReq($callreqvars) 'host' => 'server.hestiacp.com', 'port' => '8083', 'username' => 'admin', - 'pwd' => 'p4ssw0rd', - 'returncode' => 'no' + 'pwd' => 'p4ssw0rd' ), 'primary' => 'ns1.hestiacp.com' // this is the primary server to use for this secondary server );