420 lines
16 KiB
Go
420 lines
16 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/matrix-org/gomatrix"
|
|
)
|
|
|
|
type Status struct {
|
|
FuzIsOpen bool `json:"fuzIsOpen"`
|
|
LastSeenAsOpen bool `json:"lastSeenAsOpen"`
|
|
LastSeen time.Time `json:"lastSeen"`
|
|
LastOpened time.Time `json:"lastOpened"`
|
|
LastClosed time.Time `json:"lastClosed"`
|
|
ProcessUptime string `json:"processUptime"`
|
|
}
|
|
|
|
type Config struct {
|
|
PORT string
|
|
MATRIXROOM string
|
|
MATRIXOPENINGMESSAGE string
|
|
MATRIXCLOSINGMESSAGE string
|
|
MATRIXACCESSTOKEN string
|
|
MATRIXUSERNAME string
|
|
ESPUSERNAME string
|
|
ESPPASSWORD string
|
|
}
|
|
|
|
const (
|
|
dbPath = "./.data/data.json"
|
|
defaultClosingTimeout = 1 * time.Minute
|
|
espMaxBlinkDuration = 5 * time.Minute
|
|
)
|
|
|
|
var (
|
|
status Status
|
|
config = Config{
|
|
PORT: "8080",
|
|
}
|
|
startTime = time.Now()
|
|
imgs = map[bool]string{
|
|
// https://www.iconfinder.com/icons/1871431/online_open_shop_shopping_sign_icon
|
|
// formerly https://www.flaticon.com/free-icon/open_1234189, maybe try https://flaticons.net/customize.php?dir=Miscellaneous&icon=Open.png without attribution
|
|
true: `<?xml version="1.0" ?><svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#5bc9e1;}.cls-2{fill:#fd0;}.cls-3{fill:#314967;}</style></defs><title/><g data-name="15 Open Sign" id="_15_Open_Sign"><rect class="cls-1" height="36" rx="6" ry="6" width="96" x="16" y="64"/><circle class="cls-2" cx="64" cy="28" r="6"/><path class="cls-3" d="M92.73,98H22a4,4,0,0,1-4-4V70a4,4,0,0,1,4-4H42.54a2,2,0,0,0,0-4H32.83L59.93,34.89a8,8,0,0,0,8.13,0L81.71,48.54a2,2,0,0,0,2.83-2.83L70.88,32.06A8,8,0,0,0,64,20a2,2,0,0,0,0,4,4,4,0,1,1-3.49,2,2,2,0,0,0-3.49-2,8,8,0,0,0,.09,8L27.17,62H22a8,8,0,0,0-8,8V94a8,8,0,0,0,8,8H92.73A2,2,0,0,0,92.73,98Z"/><path class="cls-3" d="M106,62h-5.17L88.09,49.26a2,2,0,0,0-2.83,2.83L95.17,62H76a2,2,0,0,0,0,4h30a4,4,0,0,1,4,4V94a4,4,0,0,1-4,4H98.51a2,2,0,0,0,0,4H106a8,8,0,0,0,8-8V70A8,8,0,0,0,106,62Z"/><path class="cls-3" d="M70,62H49.22a2,2,0,0,0,0,4H70A2,2,0,0,0,70,62Z"/><path class="cls-3" d="M54.86,73.62a2.2,2.2,0,0,0-2.19,2.19v12.8a2.2,2.2,0,0,0,4.41,0V84.91h1.67a5.64,5.64,0,1,0,0-11.29ZM60,79.27c0,1.49-1.64,1.23-2.93,1.23V78C58.42,78,60,77.78,60,79.27Z"/><path class="cls-3" d="M77,78c2.86,0,2.93-4.41,0-4.41H69.92a2.17,2.17,0,0,0-2.19,2.19v12.8a2.2,2.2,0,0,0,1.25,2v.21h8c2.91,0,2.87-4.41,0-4.41H72.13v-2h4.06a2.2,2.2,0,0,0,0-4.41H72.13V78Z"/><path class="cls-3" d="M96.63,76c0-2.83-4.37-2.9-4.37,0v5.85l-5.19-7.19A2.18,2.18,0,0,0,83.13,76V88.62a2.18,2.18,0,0,0,4.37,0V82.71l5.1,7.08a2.19,2.19,0,0,0,4-1.17Z"/><path class="cls-3" d="M39.84,73.19a8.82,8.82,0,0,0,0,17.62A8.69,8.69,0,0,0,48.22,82C48.22,77.31,44.49,73.19,39.84,73.19Zm0,13.15c-5.3,0-5.26-8.68,0-8.68a4.36,4.36,0,0,1,0,8.68Z"/></g></svg>`,
|
|
// https://www.iconfinder.com/icons/1871435/closed_online_shop_shopping_sign_icon
|
|
// formerly https://www.flaticon.com/free-icon/closed_1234190, maybe try https://flaticons.net/customize.php?dir=Miscellaneous&icon=Closed.png without attribution
|
|
false: `<?xml version="1.0" ?><svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#f8991d;}.cls-2{fill:#fd0;}.cls-3{fill:#314967;}</style></defs><title/><g data-name="18 Closed Sign" id="_18_Closed_Sign"><rect class="cls-1" height="36" rx="6" ry="6" width="96" x="16" y="64"/><circle class="cls-2" cx="64" cy="28" r="6"/><path class="cls-3" d="M71.85,77.83a4.37,4.37,0,0,1,2.34,1,1.93,1.93,0,1,0,2.08-3.25A7.94,7.94,0,0,0,71.85,74c-3.13,0-5.55,2.06-5.55,4.58,0,2.74,2.64,4.21,5.35,4.58.55.11,2,.45,2.26,1,0,.57-1.38,1-2,1a5.25,5.25,0,0,1-2.79-1.16,1.93,1.93,0,1,0-2.42,3,8.73,8.73,0,0,0,5.23,2c3.12,0,5.88-2,5.88-4.82s-2.88-4.4-5.66-4.78c-1.6-.31-2-.77-2-.77C70.15,78.39,70.8,77.83,71.85,77.83Z"/><path class="cls-3" d="M40,76.34V87a2,2,0,0,0,2,2h5.83a2,2,0,0,0,0-4H44V76.34A2,2,0,0,0,40,76.34Z"/><path class="cls-3" d="M50,81.48A7.16,7.16,0,1,0,57.23,74,7.32,7.32,0,0,0,50,81.48Zm10.26,0c0,2.89-3.21,4.64-5.27,2.43-1.94-2-.71-5.86,2.2-5.86A3.29,3.29,0,0,1,60.3,81.48Z"/><path class="cls-3" d="M34.44,78.82a2,2,0,0,0,2.49-3.21A7.7,7.7,0,0,0,32.15,74a7.6,7.6,0,0,0-7.6,7.48h0c0,6.3,7.44,9.7,12.39,5.86a2,2,0,0,0-2.51-3.2,3.52,3.52,0,1,1,0-5.31Z"/><path class="cls-3" d="M87.69,78.35a2,2,0,0,0,0-4H81.8a2,2,0,0,0-2,2V87a2,2,0,0,0,1,1.75V89h6.83a2,2,0,0,0,0-4H83.81V83.65H87a2,2,0,0,0,0-4h-3.2V78.35Z"/><path class="cls-3" d="M103.09,81.64a7.29,7.29,0,0,0-7.28-7.28H93.69a2,2,0,0,0-2,2V87a2,2,0,0,0,2,2h2.13A7.31,7.31,0,0,0,103.09,81.64ZM95.81,85h-.12V78.35h.12C100.14,78.35,100.18,84.93,95.81,85Z"/><path class="cls-3" d="M92.73,98H22a4,4,0,0,1-4-4V70a4,4,0,0,1,4-4H42.55a2,2,0,0,0,0-4H32.83L59.93,34.89a8,8,0,0,0,8.13,0L81.71,48.54a2,2,0,0,0,2.83-2.83L70.88,32.06A8,8,0,0,0,64,20a2,2,0,0,0,0,4,4,4,0,0,1,2.79,6.86C63.58,34,58.24,30.08,60.51,26a2,2,0,0,0-3.49-2,8,8,0,0,0,.09,8L27.17,62H22a8,8,0,0,0-8,8V94a8,8,0,0,0,8,8H92.73A2,2,0,0,0,92.73,98Z"/><path class="cls-3" d="M106,62h-5.17L88.09,49.26a2,2,0,0,0-2.83,2.83L95.17,62H76a2,2,0,0,0,0,4h30a4,4,0,0,1,4,4V94a4,4,0,0,1-4,4H98.51a2,2,0,0,0,0,4H106a8,8,0,0,0,8-8V70A8,8,0,0,0,106,62Z"/><path class="cls-3" d="M70,62H49.22a2,2,0,0,0,0,4H70A2,2,0,0,0,70,62Z"/></g></svg>`,
|
|
}
|
|
db *os.File
|
|
matrix *gomatrix.Client
|
|
espIP string
|
|
espBlinkDeadline = time.Now().Add(-espMaxBlinkDuration)
|
|
)
|
|
|
|
func init() {
|
|
port := os.Getenv("PORT")
|
|
if val, _ := strconv.Atoi(port); val > 0 {
|
|
config.PORT = port
|
|
}
|
|
|
|
config.MATRIXUSERNAME = os.Getenv("MATRIXUSERNAME")
|
|
config.MATRIXACCESSTOKEN = os.Getenv("MATRIXACCESSTOKEN")
|
|
|
|
config.MATRIXROOM = os.Getenv("MATRIXROOM")
|
|
config.MATRIXOPENINGMESSAGE = os.Getenv("MATRIXOPENINGMESSAGE")
|
|
config.MATRIXCLOSINGMESSAGE = os.Getenv("MATRIXCLOSINGMESSAGE")
|
|
|
|
config.ESPUSERNAME = os.Getenv("ESPUSERNAME")
|
|
config.ESPPASSWORD = os.Getenv("ESPPASSWORD")
|
|
|
|
if config.MATRIXUSERNAME == "" {
|
|
panic("MATRIXUSERNAME is empty")
|
|
}
|
|
if config.MATRIXACCESSTOKEN == "" {
|
|
panic("MATRIXACCESSTOKEN is empty")
|
|
}
|
|
var err error
|
|
matrix, err = gomatrix.NewClient(fmt.Sprintf("https://%s", config.MATRIXUSERNAME[strings.Index(config.MATRIXUSERNAME, ":")+1:]), config.MATRIXUSERNAME, config.MATRIXACCESSTOKEN)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("error creating matrix client: %s", err))
|
|
}
|
|
if _, err := matrix.GetOwnStatus(); err != nil { // a way to quickly check if access token is valid
|
|
panic(fmt.Sprintf("error getting matrix status: %s", err))
|
|
}
|
|
|
|
if config.MATRIXROOM == "" {
|
|
panic("MATRIXROOM is empty")
|
|
}
|
|
if config.MATRIXOPENINGMESSAGE == "" {
|
|
panic("MATRIXOPENINGMESSAGE is empty")
|
|
}
|
|
if config.MATRIXCLOSINGMESSAGE == "" {
|
|
panic("MATRIXCLOSINGMESSAGE is empty")
|
|
}
|
|
|
|
if config.ESPUSERNAME == "" {
|
|
panic("ESPUSERNAME is empty")
|
|
}
|
|
if config.ESPPASSWORD == "" {
|
|
panic("ESPPASSWORD is empty")
|
|
}
|
|
|
|
err = os.MkdirAll(filepath.Dir(dbPath), 0755)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
db, err = os.OpenFile(dbPath, os.O_RDWR|os.O_CREATE, 0600)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
panic(err)
|
|
}
|
|
err = json.NewDecoder(db).Decode(&status)
|
|
if err != nil {
|
|
fmt.Println("error unmarshalling db:", err)
|
|
}
|
|
}
|
|
|
|
func updateUptime() {
|
|
for range time.Tick(time.Second) {
|
|
status.ProcessUptime = time.Since(startTime).Truncate(time.Second).String()
|
|
}
|
|
}
|
|
|
|
func checkClosure() {
|
|
time.Sleep(time.Minute) // give some time for presence button to show up
|
|
for {
|
|
if status.LastSeen.Add(defaultClosingTimeout).Before(time.Now()) && status.LastClosed.Before(status.LastSeen) {
|
|
// the Fuz is newly closed, notify on matrix and write file to survive reboot
|
|
fmt.Println("the Fuz is newly closed, notify on matrix and write file to survive reboot")
|
|
msg := config.MATRIXCLOSINGMESSAGE
|
|
if !status.FuzIsOpen {
|
|
msg = fmt.Sprintf("%s : passage bref", msg)
|
|
}
|
|
_, err := matrix.SendText(config.MATRIXROOM, msg)
|
|
if err != nil {
|
|
fmt.Println("err:", err)
|
|
time.Sleep(10 * time.Second)
|
|
continue
|
|
}
|
|
|
|
status.LastClosed = time.Now()
|
|
status.FuzIsOpen = false
|
|
db.Truncate(0)
|
|
db.Seek(0, 0)
|
|
e := json.NewEncoder(db)
|
|
e.SetIndent("", " ")
|
|
e.Encode(status)
|
|
}
|
|
time.Sleep(10 * time.Second)
|
|
}
|
|
}
|
|
|
|
func rootHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
fmt.Fprintf(w, `Fuz presence button public API
|
|
|
|
This API provides the current opening status of the hackerspace. This server also posts messages on Matrix to notify when the space opens and closes.
|
|
|
|
Usage:
|
|
|
|
/ Shows help
|
|
/api Serves some JSON with lax CORS headers to get the current opening status programatically. The properties are the following:
|
|
* fuzIsOpen: (boolean) reflects if the space is currently open
|
|
* lastSeenAsOpen: (boolean) reflects if the last ping by the ESP was after being pushed (space officially opened)
|
|
* lastSeen: (date) last ESP ping timestamp
|
|
* lastOpened: (date) last space opening timestamp
|
|
* lastClosed: (date) last space closing timestamp
|
|
* processUptime: (duration) API process uptime
|
|
/img Serves an svg image showing if the space is open or closed.
|
|
/status Private endpoint used by the ESP (physical button) to regularly ping/update the opening status.
|
|
|
|
|
|
Source code: https://github.com/Lomanic/presence-button-web
|
|
Source code mirror: https://git.interhacker.space/Lomanic/presence-button-web
|
|
Documentation: https://wiki.fuz.re/doku.php?id=projets:fuz:presence_button`)
|
|
}
|
|
|
|
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
|
|
e := json.NewEncoder(w)
|
|
e.SetIndent("", " ")
|
|
e.Encode(status)
|
|
}
|
|
|
|
func imgHandler(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "image/svg+xml")
|
|
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate")
|
|
w.Header().Set("Pragma", "no-cache")
|
|
w.Header().Set("Expires", "0")
|
|
fmt.Fprintf(w, imgs[status.FuzIsOpen])
|
|
}
|
|
|
|
func statusHandler(w http.ResponseWriter, r *http.Request) {
|
|
user, pass, ok := r.BasicAuth()
|
|
fmt.Printf("status notification by button... ")
|
|
if !ok || user != config.ESPUSERNAME || pass != config.ESPPASSWORD {
|
|
fmt.Printf("bad authentication: user:%v pass:%v\n", user, pass)
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="Authentication required"`)
|
|
http.Error(w, "Authentication required", 401)
|
|
return
|
|
}
|
|
|
|
// handle being behind proxy https://stackoverflow.com/a/33301173
|
|
espIP, _, _ = net.SplitHostPort(r.RemoteAddr)
|
|
for _, ifaceIP := range ifacesIPs() {
|
|
if espIP == ifaceIP {
|
|
for _, header := range []string{"X-Real-Ip", "X-Forwarded-For"} {
|
|
if r.Header.Get(header) != "" {
|
|
espIP = strings.Split(r.Header.Get(header), ", ")[0]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fmt.Printf("espIP %s ", espIP)
|
|
|
|
|
|
q := r.URL.Query()
|
|
fuzIsOpen := q.Get("fuzisopen") == "1"
|
|
fmt.Printf("button pushed: %v\n", fuzIsOpen)
|
|
status.FuzIsOpen = fuzIsOpen
|
|
status.LastSeenAsOpen = fuzIsOpen
|
|
status.LastSeen = time.Now()
|
|
fmt.Fprintf(w, "OK")
|
|
|
|
db.Truncate(0)
|
|
db.Seek(0, 0)
|
|
e := json.NewEncoder(db)
|
|
e.SetIndent("", " ")
|
|
e.Encode(status)
|
|
if status.FuzIsOpen && (status.LastOpened.Equal(status.LastClosed) || status.LastOpened.Before(status.LastClosed)) {
|
|
// the Fuz is newly opened, notify on matrix and write file to survive reboot
|
|
fmt.Println("the Fuz is newly opened, notify on matrix and write file to survive reboot")
|
|
_, err := matrix.SendText(config.MATRIXROOM, config.MATRIXOPENINGMESSAGE)
|
|
if err != nil {
|
|
fmt.Println("err:", err)
|
|
return
|
|
}
|
|
|
|
status.LastOpened = time.Now()
|
|
db.Truncate(0)
|
|
db.Seek(0, 0)
|
|
e := json.NewEncoder(db)
|
|
e.SetIndent("", " ")
|
|
e.Encode(status)
|
|
}
|
|
}
|
|
|
|
// https://github.com/qbit/mcchunkie/blob/53eb25ae3394f326e24782f273584e3e6a4c0e8c/plugins/plugins.go#L52-L66
|
|
// NameRE matches the "friendly" name. This is typically used in tab
|
|
// completion.
|
|
var nameRE = regexp.MustCompile(`@(.+):.+$`)
|
|
|
|
// ToMe returns true of the message pertains to the bot
|
|
func toMe(user, message string) bool {
|
|
u := nameRE.ReplaceAllString(user, "$1")
|
|
return strings.Contains(message, u)
|
|
}
|
|
|
|
// RemoveName removes the friendly name from a given message
|
|
func removeName(user, message string) string {
|
|
n := nameRE.ReplaceAllString(user, "$1")
|
|
return strings.ReplaceAll(message, n+": ", "")
|
|
}
|
|
|
|
func syncMatrix() {
|
|
syncer := matrix.Syncer.(*gomatrix.DefaultSyncer)
|
|
syncer.OnEventType("m.room.message", func(ev *gomatrix.Event) {
|
|
if ev.Sender != matrix.UserID {
|
|
matrix.MarkRead(ev.RoomID, ev.ID)
|
|
if body, ok := ev.Body(); ok && toMe(config.MATRIXUSERNAME, body) {
|
|
cmd := strings.Fields(removeName(config.MATRIXUSERNAME, body))
|
|
if len(cmd) >= 2 {
|
|
switch cmd[0] {
|
|
case "blink":
|
|
switch cmd[1] {
|
|
case "on":
|
|
if status.FuzIsOpen {
|
|
matrix.SendFormattedText(config.MATRIXROOM, fmt.Sprintf("%s: let's make it **blink**! (%s max)", ev.Sender, espMaxBlinkDuration), fmt.Sprintf("%s: let's make it <b>blink</b>! (%s max)", ev.Sender, espMaxBlinkDuration))
|
|
espBlinkDeadline = time.Now().Add(espMaxBlinkDuration)
|
|
} else {
|
|
matrix.SendText(config.MATRIXROOM, fmt.Sprintf("%s: can't do that, space is closed", ev.Sender))
|
|
}
|
|
case "off":
|
|
if status.FuzIsOpen {
|
|
matrix.SendFormattedText(config.MATRIXROOM, fmt.Sprintf("%s: OK it's time to **STOP**!", ev.Sender), fmt.Sprintf("%s: OK it's time to <b>STOP</b>!", ev.Sender))
|
|
espBlinkDeadline = time.Now()
|
|
} else {
|
|
matrix.SendText(config.MATRIXROOM, fmt.Sprintf("%s: can't do that, space is closed", ev.Sender))
|
|
}
|
|
case "status":
|
|
if time.Now().Before(espBlinkDeadline) {
|
|
matrix.SendText(config.MATRIXROOM, fmt.Sprintf("%s: yes it's still blinking!", ev.Sender))
|
|
} else {
|
|
matrix.SendFormattedText(config.MATRIXROOM, fmt.Sprintf("%s: it's **not** blinking", ev.Sender), fmt.Sprintf("%s: it's <b>not</b> blinking", ev.Sender))
|
|
}
|
|
default:
|
|
matrix.SendFormattedText(config.MATRIXROOM, fmt.Sprintf("%s: command is `blink (status|on|off)`", ev.Sender), fmt.Sprintf("%s: command is <code>blink (status|on|off)</code>", ev.Sender))
|
|
}
|
|
return
|
|
}
|
|
}
|
|
matrix.SendText(config.MATRIXROOM, fmt.Sprintf("%s: huh?", ev.Sender))
|
|
}
|
|
}
|
|
})
|
|
|
|
go func() { // set online status every 15 seconds
|
|
for {
|
|
if err := matrix.SetStatus("online", "up and running"); err != nil {
|
|
fmt.Println("error setting matrix status:", err)
|
|
}
|
|
time.Sleep(15 * time.Second)
|
|
}
|
|
}()
|
|
|
|
for {
|
|
if err := matrix.Sync(); err != nil {
|
|
fmt.Println("error syncing with matrix:", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func espBlinkLoop() {
|
|
client := &http.Client{}
|
|
for {
|
|
time.Sleep(time.Second)
|
|
if espIP == "" {
|
|
continue
|
|
}
|
|
if time.Now().Before(espBlinkDeadline) {
|
|
// http.get request to espIP to have it light up
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/admin?enablerotatinglight", espIP), nil)
|
|
if err != nil {
|
|
fmt.Println("error creating request in espBlinkLoop:", err)
|
|
continue
|
|
}
|
|
req.SetBasicAuth(config.ESPUSERNAME, config.ESPPASSWORD)
|
|
fmt.Print("admin?enablerotatinglight ")
|
|
fmt.Println(client.Do(req))
|
|
time.Sleep(7 * time.Second)
|
|
|
|
// http.get request to espIP to shut down light
|
|
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/admin?disablerotatinglight", espIP), nil)
|
|
if err != nil {
|
|
fmt.Println("error creating request in espBlinkLoop:", err)
|
|
continue
|
|
}
|
|
req.SetBasicAuth(config.ESPUSERNAME, config.ESPPASSWORD)
|
|
fmt.Print("admin?disablerotatinglight ")
|
|
fmt.Println(client.Do(req))
|
|
time.Sleep(3 * time.Second)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ifacesIPs() []string { // from https://stackoverflow.com/a/23558495 linked playground
|
|
var ips []string
|
|
ifaces, err := net.Interfaces()
|
|
if err != nil {
|
|
return ips
|
|
}
|
|
for _, iface := range ifaces {
|
|
if iface.Flags&net.FlagUp == 0 {
|
|
continue // interface down
|
|
}
|
|
/*if iface.Flags&net.FlagLoopback != 0 {
|
|
continue // loopback interface
|
|
}*/
|
|
addrs, err := iface.Addrs()
|
|
if err != nil {
|
|
return ips
|
|
}
|
|
for _, addr := range addrs {
|
|
var ip net.IP
|
|
switch v := addr.(type) {
|
|
case *net.IPNet:
|
|
ip = v.IP
|
|
case *net.IPAddr:
|
|
ip = v.IP
|
|
}
|
|
if ip == nil || ip.IsLoopback() {
|
|
continue
|
|
}
|
|
ip = ip.To4()
|
|
if ip == nil {
|
|
continue // not an ipv4 address
|
|
}
|
|
ips = append(ips, ip.String())
|
|
}
|
|
}
|
|
return ips
|
|
}
|
|
|
|
func main() {
|
|
fmt.Println(ifacesIPs())
|
|
go updateUptime()
|
|
go checkClosure()
|
|
go syncMatrix()
|
|
go espBlinkLoop()
|
|
http.HandleFunc("/", rootHandler)
|
|
http.HandleFunc("/api", apiHandler)
|
|
http.HandleFunc("/img", imgHandler)
|
|
http.HandleFunc("/status", statusHandler)
|
|
log.Fatal(http.ListenAndServe(":"+config.PORT, nil))
|
|
}
|