The smallest watchdog on earth. Tiny, monitoring-plugins compatible monitoring with a status page. https://cloud.docker.com/repository/docker/momar/chihuahua/general
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

178 lines
4.5 KiB

package chihuahua
//go:generate pkger
import (
"github.com/rs/zerolog/log"
"io/ioutil"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/markbates/pkger"
)
var keyList string
var keyListAge time.Time
func UpdateKeys() {
if time.Now().Sub(keyListAge) < 500*time.Millisecond {
return
}
keyFiles, _ := filepath.Glob(filepath.Join(os.Getenv("HOME"), ".ssh/id_*"))
keyList = ""
if keyFiles != nil {
for _, file := range keyFiles {
if !strings.HasSuffix(file, ".pub") {
continue // it's a private key, and we don't want those to be reachable unauthenticated via HTTP :)
}
key, _ := ioutil.ReadFile(file)
keyList += strings.TrimSpace(string(key)) + "\n"
}
}
keyListAge = time.Now()
}
func GenerateKeys() {
// Get SSH keys for the setup script
// chown if someone already created a .ssh directory; mostly relevant for Docker
filepath.Walk(filepath.Join(os.Getenv("HOME"), ".ssh/"), func(path string, info os.FileInfo, err error) error {
os.Chown(path, os.Getuid(), os.Getgid())
return nil
})
UpdateKeys()
if keyList == "" {
if info, err := os.Stat(filepath.Join(os.Getenv("HOME"), ".ssh")); err != nil || !info.IsDir() {
err := os.MkdirAll(filepath.Join(os.Getenv("HOME"), ".ssh"), 755)
if err != nil {
log.Warn().Str("caller", "api").Err(err).Msg("couldn't generate an SSH key")
}
}
cmd := exec.Command("ssh-keygen", "-t", "ed25519", "-N", "", "-C", "Chihuahua Monitoring", "-f", filepath.Join(os.Getenv("HOME"), ".ssh/id_ed25519"))
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
log.Info().Str("caller", "api").Msgf("no SSH key found, generating one at %s", cmd.Args[len(cmd.Args)-1])
err := cmd.Run()
if err != nil {
log.Warn().Str("caller", "api").Err(err).Msg("couldn't generate an SSH key")
}
}
}
// GetCheck returns the check results
func GetCheck(cfg *Config) func(c *gin.Context) {
return func(c *gin.Context) {
path := c.Param("id")
if path == "" {
// All servers
c.JSON(200, cfg.Servers)
return
}
id := strings.Split(strings.Trim(path, "/"), "/")
parent := &ServerOrGroup{Children: cfg.Servers}
for i := 0; i < len(id); i++ {
idPart, err := url.QueryUnescape(id[i])
if err != nil {
idPart = id[i]
}
found := false
if parent.Children != nil {
for _, child := range parent.Children {
if child.ID == idPart {
parent = child
found = true
break
}
}
}
if !found {
// allow single checks
if parent.Checks != nil {
for _, check := range parent.Checks {
if check.ID == idPart {
if i+1 >= len(id) {
c.JSON(200, check)
return
}
}
}
}
c.JSON(404, map[string]struct{}{})
return
}
}
c.JSON(200, parent)
return
}
}
func Api(cfg *Config) {
if os.Getenv("PORT") == "none" {
log.Warn().Msg("disabling API and UI completely (PORT=none)")
select{}
}
// putMessage adds a message to a check
//putMessage := func(req *air.Request, res *air.Response) error {
// // TODO
// return nil
//}
// deleteMessage deletes the message from a check
//deleteMessage := func(req *air.Request, res *air.Response) error {
// // TODO:
// return nil
//}
// Get the setup script
setupFile, err := pkger.Open("codeberg.org/momar/chihuahua:/resources/setup.sh")
if err != nil {
log.Error().Str("caller", "api").Err(err).Msg("couldn't open setup.sh from web resources")
os.Exit(1)
}
setupScript, err := ioutil.ReadAll(setupFile)
if err != nil {
log.Error().Str("caller", "api").Err(err).Msg("couldn't read setup.sh from web resources")
os.Exit(1)
}
getSetupScript := func(c *gin.Context) {
UpdateKeys()
c.String(200, strings.ReplaceAll(string(setupScript), "[CHIHUAHUA_PUBLIC_KEYS]", keyList))
}
app := gin.New()
app.Use(gin.Recovery())
app.GET("/setup.sh", getSetupScript)
app.GET("/checks", GetCheck(cfg))
app.GET("/checks/*id", GetCheck(cfg))
//app.PUT("/checks/*", putMessage)
//app.DELETE("/checks/:server/:check", deleteMessage)
app.NoRoute(gin.WrapH(http.FileServer(pkger.Dir("/web"))))
addr := ":"
if host := os.Getenv("HOST"); host != "" {
addr = host + addr
}
if port := os.Getenv("PORT"); port != "" {
addr += port
} else {
addr += "8080"
}
log.Info().Str("caller", "api").Str("address", addr).Msg("starting API web server")
err = app.Run(addr)
if err != nil {
log.Error().Str("caller", "api").Err(err).Msg("web server threw an error")
os.Exit(1)
}
}
func SendUpdate(cfg *Config) {
// TODO: send current check state to browsers via SSE
}