mirror of
https://codeberg.org/hyperreal/go-pihole-stats
synced 2024-11-01 16:53:09 +01:00
219 lines
5.6 KiB
Go
219 lines
5.6 KiB
Go
|
/* pihole-stats
|
||
|
Copyright (C) 2020 Jeffrey Serio
|
||
|
|
||
|
This program is free software: you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation, either version 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||
|
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"log"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
|
||
|
"github.com/fatih/color"
|
||
|
"github.com/pkg/errors"
|
||
|
)
|
||
|
|
||
|
/*
|
||
|
Pi-hole stats for cli by Jeffrey Serio @hyperreal42 on Github/GitLab
|
||
|
WIP
|
||
|
TODO:
|
||
|
*/
|
||
|
|
||
|
// Basic variables for Pihole instance ---
|
||
|
var (
|
||
|
baseURL = os.Getenv("PIHOLE_URL")
|
||
|
urlSummary = baseURL + "/api.php?summary"
|
||
|
urlStatus = baseURL + "/api.php?status"
|
||
|
urlEnable = baseURL + "/api.php?enable"
|
||
|
urlDisable = baseURL + "/api.php?disable"
|
||
|
authorization = "&auth=" + os.Getenv("PIHOLE_AUTH")
|
||
|
)
|
||
|
|
||
|
// Colors ---
|
||
|
var (
|
||
|
blue = color.New(color.FgBlue).SprintFunc()
|
||
|
green = color.New(color.FgGreen).SprintFunc()
|
||
|
red = color.New(color.FgRed).SprintFunc()
|
||
|
bold = color.New(color.Bold).SprintFunc()
|
||
|
underline = color.New(color.Underline).SprintFunc()
|
||
|
)
|
||
|
|
||
|
const usage = `
|
||
|
Pi-hole stats
|
||
|
-------------
|
||
|
This program prints Pi-hole stats to standard output
|
||
|
|
||
|
Usage:
|
||
|
pihole-stats : Print stats from Pi-hole instance
|
||
|
|
||
|
pihole-stats [e | d] : Enable (e) or disable (d) Pi-hole`
|
||
|
|
||
|
/*
|
||
|
Data structures ---
|
||
|
*/
|
||
|
|
||
|
// PiholeStats ---
|
||
|
type PiholeStats struct {
|
||
|
Status string `json:"status"`
|
||
|
UniqueClients string `json:"unique_clients"`
|
||
|
ClientsEverSeen string `json:"clients_ever_seen"`
|
||
|
DomainsBeingBlocked string `json:"domains_being_blocked"`
|
||
|
AdsBlockedToday string `json:"ads_blocked_today"`
|
||
|
AdsPercentageToday string `json:"ads_percentage_today"`
|
||
|
DNSQueriesToday string `json:"dns_queries_today"`
|
||
|
QueriesCachedToday string `json:"queries_cached"`
|
||
|
QueriesForwardedToday string `json:"queries_forwarded"`
|
||
|
UniqueDomainsToday string `json:"unique_domains"`
|
||
|
|
||
|
GravityLastUpdated struct {
|
||
|
GravFileExists bool `json:"file_exists"`
|
||
|
|
||
|
GravRelUp struct {
|
||
|
Days int `json:"days"`
|
||
|
Hours int `json:"hours"`
|
||
|
Minutes int `json:"minutes"`
|
||
|
} `json:"relative"`
|
||
|
} `json:"gravity_last_updated"`
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Helper functions ---
|
||
|
*/
|
||
|
|
||
|
// doRequest --- HTTP GET request to the API
|
||
|
// Returns byte array to be plugged into other functions
|
||
|
func doRequest(url string, auth string) ([]byte, error) {
|
||
|
newURL := url + auth
|
||
|
if newURL == "/api.php?summary&auth=" {
|
||
|
err := errors.New("Pi-hole url or auth token not supplied")
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
req, err := http.NewRequest("GET", newURL, nil)
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrap(err, "GET request failed")
|
||
|
}
|
||
|
|
||
|
res, err := http.DefaultClient.Do(req)
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrap(err, "Could not get HTTP response")
|
||
|
}
|
||
|
|
||
|
defer res.Body.Close()
|
||
|
body, err := ioutil.ReadAll(res.Body)
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrap(err, "Failed to read HTTP response body")
|
||
|
}
|
||
|
return body, nil
|
||
|
}
|
||
|
|
||
|
// getSummary --- returns *PiholeStats instance
|
||
|
func getSummary(jsonKey []byte) (*PiholeStats, error) {
|
||
|
|
||
|
data := &PiholeStats{}
|
||
|
if err := json.Unmarshal(jsonKey, data); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return data, nil
|
||
|
}
|
||
|
|
||
|
func enablePihole() error {
|
||
|
res, err := doRequest(urlEnable, authorization)
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "Failed to enable Pi-hole")
|
||
|
}
|
||
|
fmt.Println(string(res))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func disablePihole() error {
|
||
|
res, err := doRequest(urlDisable, authorization)
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "Failed to disable Pi-hole")
|
||
|
}
|
||
|
fmt.Println(string(res))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func printContent() error {
|
||
|
content, err := doRequest(urlSummary, authorization)
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "Failed to run HTTP request for Pi-hole stats")
|
||
|
}
|
||
|
data, err := getSummary(content)
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "Failed to get Pi-hole stats summary")
|
||
|
}
|
||
|
|
||
|
fmt.Printf("%s\n\n", bold(underline(red("Pi-hole Statistics"))))
|
||
|
fmt.Printf("Pi-hole admin console: %s\n", baseURL)
|
||
|
if data.Status == "enabled" {
|
||
|
fmt.Printf("Status: %s\n", green("Enabled"))
|
||
|
} else {
|
||
|
fmt.Printf("Status: %s\n", red("Disabled"))
|
||
|
}
|
||
|
|
||
|
g := data.GravityLastUpdated
|
||
|
if g.GravFileExists == true {
|
||
|
fmt.Printf("Gravity last updated: %d days, %d hours, %d minutes\n", g.GravRelUp.Days, g.GravRelUp.Hours, g.GravRelUp.Minutes)
|
||
|
} else {
|
||
|
fmt.Println("Gravity has not been updated yet")
|
||
|
}
|
||
|
fmt.Printf("%s\n", blue("---"))
|
||
|
|
||
|
dataMap := map[string]string{
|
||
|
"Current unique clients": data.UniqueClients,
|
||
|
"Total clients ever seen": data.ClientsEverSeen,
|
||
|
"Domains being blocked": data.DomainsBeingBlocked,
|
||
|
"Ads blocked today": data.AdsBlockedToday,
|
||
|
"Ads percentage today": data.AdsPercentageToday,
|
||
|
"DNS queries today": data.DNSQueriesToday,
|
||
|
"Queries cached today": data.QueriesCachedToday,
|
||
|
"Queries forwarded today": data.QueriesForwardedToday,
|
||
|
"Unique domains today": data.UniqueDomainsToday,
|
||
|
}
|
||
|
|
||
|
for i, j := range dataMap {
|
||
|
fmt.Printf("%s: %s\n", i, j)
|
||
|
}
|
||
|
fmt.Printf("%s\n", blue("---"))
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
if len(os.Args) < 2 {
|
||
|
if err := printContent(); err != nil {
|
||
|
log.Fatalln(err)
|
||
|
}
|
||
|
} else {
|
||
|
switch os.Args[1] {
|
||
|
case "e":
|
||
|
if err := enablePihole(); err != nil {
|
||
|
log.Fatalln(err)
|
||
|
}
|
||
|
case "d":
|
||
|
if err := disablePihole(); err != nil {
|
||
|
log.Fatalln(err)
|
||
|
}
|
||
|
default:
|
||
|
fmt.Println(usage)
|
||
|
}
|
||
|
}
|
||
|
}
|