package main import ( "context" "crypto/tls" "errors" "fmt" "io" "log" "net/http" "os" "strconv" "github.com/spf13/viper" "github.com/urfave/cli/v3" ) var ( server string token string ) 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 makeReqPrint(method string, url string) error { req, err := http.NewRequest(method, url, nil) if err != nil { return err } req.Header.Set("Authorization", token) res, err := http.DefaultClient.Do(req) if err != nil { return err } if res.StatusCode != 200 { fmt.Printf("<-- %d\n", res.StatusCode) } recv, err := io.ReadAll(res.Body) if err != nil { return err } fmt.Printf("%s", recv) return nil } func makeReqRet(method string, url string) ([]byte, error) { req, err := http.NewRequest(method, 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 } 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.0.0") return nil }, }, { Name: "test", Usage: "test server connection", Action: test, }, { Name: "get", Usage: "get door state", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "raw", Usage: "print raw server output", }, }, Action: get, }, { Name: "lock", Usage: "change lock status", Commands: []*cli.Command{ { Name: "do", Usage: "lock the door", Action: createManageLock(true), }, { Name: "undo", Usage: "unlock the door", Action: createManageLock(false), }, }, Action: getLock, }, { Name: "alerts", Usage: "change alerts status", Commands: []*cli.Command{ { Name: "pause", Usage: "pause alerts", Flags: []cli.Flag{ &cli.StringFlag{ Name: "for", Usage: "pause for x seconds", }, }, Action: createManageAlerts(true), }, { Name: "resume", Usage: "resume alerts", Action: createManageAlerts(false), }, }, Action: getAlerts, }, }, } 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) return makeReqPrint(http.MethodGet, url) } func get(ctx context.Context, cmd *cli.Command) error { err := load() if err != nil { return err } opened, err := makeReqRet(http.MethodGet, fmt.Sprintf("%s/read", 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 getLock(context.Context, *cli.Command) error { err := load() if err != nil { return err } locked, err := makeReqRet(http.MethodGet, 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 createManageLock(do bool) func(context.Context, *cli.Command) error { return func(ctx context.Context, cmd *cli.Command) error { err := load() if err != nil { return err } var actionShort string var action string if do { actionShort = "do" action = "lock" } else { actionShort = "undo" action = "unlock" } _, err = makeReqRet(http.MethodPost, fmt.Sprintf("%s/lock/%s", server, actionShort)) if err != nil { return err } fmt.Printf("door was %sed\n", action) return nil } } func getAlerts(context.Context, *cli.Command) error { err := load() if err != nil { return err } alert, err := makeReqRet(http.MethodGet, fmt.Sprintf("%s/alerts", 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 createManageAlerts(pause bool) func(context.Context, *cli.Command) error { return func(ctx context.Context, cmd *cli.Command) error { err := load() if err != nil { return err } var action string if pause { action = "pause" } else { action = "resume" } var rest string var secs int secsRaw := cmd.String("for") if secsRaw != "" { secs, err = strconv.Atoi(secsRaw) if err != nil { return err } rest = fmt.Sprintf("?for=%d", secs) } _, err = makeReqRet(http.MethodPost, fmt.Sprintf("%s/alerts/%s%s", server, action, rest)) if err != nil { return err } if secsRaw != "" { rest = fmt.Sprintf(" for %d seconds", secs) } fmt.Printf("alerts were %sd%s\n", action, rest) return nil } }