package main import ( "bytes" "context" "crypto/tls" "encoding/json" "errors" "fmt" "io" "log" "net/http" "os" "strconv" "github.com/spf13/viper" "github.com/urfave/cli/v3" ) var ( server string token string ) type ChangeLockReq struct { Locked bool `json:"locked"` } type ChangeAlertReq struct { Alert bool `json:"alert"` For int `json:"for"` } func load() error { viper.SetConfigName("config") viper.SetConfigType("toml") viper.AddConfigPath("$HOME/.local/share/door-alarm") err := viper.ReadInConfig() if err != nil { return err } server = viper.GetString("server") if server == "" { return errors.New("'server' property is required") } token = viper.GetString("token") if token == "" { return errors.New("'token' property is required") } return nil } func makeGetReq(url string) ([]byte, error) { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } req.Header.Set("Authorization", token) res, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer func() { res.Body.Close() }() if res.StatusCode != 200 { fmt.Printf("<-- %d\n", res.StatusCode) } return io.ReadAll(res.Body) } func makePostReq(url string, body any) ([]byte, error) { bodyRaw, err := json.Marshal(body) if err != nil { return nil, err } req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(bodyRaw)) if err != nil { return nil, err } req.Header.Set("Authorization", token) req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer func() { _ = res.Body.Close() }() if res.StatusCode != 200 { fmt.Printf("<-- %d\n", res.StatusCode) } return io.ReadAll(res.Body) } func main() { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} cmd := &cli.Command{ Name: "doorctl", Usage: "door-alarm server administration tool", Commands: []*cli.Command{ { Name: "version", Usage: "print version and exit", Action: func(context.Context, *cli.Command) error { fmt.Println("version 1.1.0") return nil }, }, { Name: "test", Usage: "test server connection", Action: test, }, { Name: "open", Usage: "get door state (opened or closed)", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "raw", Usage: "print raw server output", }, }, Action: getOpened, }, { Name: "lock", Usage: "change lock status", Commands: []*cli.Command{ { Name: "on", Usage: "lock the door", Action: createManageLock(true), }, { Name: "off", Usage: "unlock the door", Action: createManageLock(false), }, }, Action: getLocked, }, { Name: "alert", Usage: "change alert status", Commands: []*cli.Command{ { Name: "on", Usage: "resume alerts", Action: createManageAlert(true), }, { Name: "off", Usage: "pause alerts", Flags: []cli.Flag{ &cli.StringFlag{ Name: "for", Usage: "pause for x seconds", }, }, Action: createManageAlert(false), }, }, Action: getAlert, }, }, } err := cmd.Run(context.Background(), os.Args) if err != nil { log.Fatal(err) } } func test(context.Context, *cli.Command) error { err := load() if err != nil { return err } url := fmt.Sprintf("%s/", server) data, err := makeGetReq(url) if err != nil { return err } fmt.Printf("%s", data) return nil } func getOpened(ctx context.Context, cmd *cli.Command) error { err := load() if err != nil { return err } opened, err := makeGetReq(fmt.Sprintf("%s/open", server)) if err != nil { return err } if cmd.Bool("raw") { fmt.Println(opened) } else { if string(opened) == "true\n" { fmt.Println("door is open") } else { fmt.Println("door is closed") } } return nil } func getLocked(context.Context, *cli.Command) error { err := load() if err != nil { return err } locked, err := makeGetReq(fmt.Sprintf("%s/lock", server)) if err != nil { return err } if string(locked) == "true\n" { fmt.Println("door is locked") } else { fmt.Println("door is unlocked") } return nil } func getAlert(context.Context, *cli.Command) error { err := load() if err != nil { return err } alert, err := makeGetReq(fmt.Sprintf("%s/alert", server)) if err != nil { return err } if string(alert) == "true\n" { fmt.Println("alerts are active") } else { fmt.Println("alerts are paused") } return nil } func createManageLock(locked bool) func(context.Context, *cli.Command) error { return func(ctx context.Context, cmd *cli.Command) error { err := load() if err != nil { return err } var data ChangeLockReq data.Locked = locked _, err = makePostReq(fmt.Sprintf("%s/lock", server), data) if err != nil { return err } var action string if locked { action = "locked" } else { action = "unlocked" } fmt.Printf("door was %s\n", action) return nil } } func createManageAlert(alert bool) func(context.Context, *cli.Command) error { return func(ctx context.Context, cmd *cli.Command) error { err := load() if err != nil { return err } var data ChangeAlertReq data.Alert = alert var secs int secsRaw := cmd.String("for") if secsRaw != "" { secs, err = strconv.Atoi(secsRaw) if err != nil { return err } data.For = secs } _, err = makePostReq(fmt.Sprintf("%s/alert", server), data) if err != nil { return err } var rest string if secsRaw != "" { rest = fmt.Sprintf(" for %d seconds", secs) } var action string if alert { action = "resumed" } else { action = "paused" } fmt.Printf("alerts were %sd%s\n", action, rest) return nil } }