main
1package cmd
2
3import (
4 "errors"
5 "fmt"
6 "log/slog"
7 "os"
8 "strconv"
9
10 "github.com/bryfry/mm/internal/aod"
11 "github.com/bryfry/mm/internal/cf"
12 "github.com/bryfry/mm/internal/iplookup"
13 "github.com/spf13/cobra"
14)
15
16const (
17 EnvDie = "MM_POWEROFF_ON_IDLE"
18)
19
20var (
21 _errFmt = "required env variable %s not found or empty"
22 ErrDieRequired = errors.New(fmt.Sprintf(_errFmt, EnvDie))
23)
24
25// TODO:
26// - move cfdns to a goroutine in cf package
27// - move watch into a goroutine
28// - setup context for canx and signals
29func service(cmd *cobra.Command, args []string) error {
30
31 zoneName, zoneNameExists := os.LookupEnv(cf.EnvZoneName)
32 if !zoneNameExists || len(zoneName) == 0 {
33 return cf.ErrZoneNameRequired
34 }
35 subdomainName, subdomainNameExists := os.LookupEnv(cf.EnvSubdomainName)
36 if !subdomainNameExists || len(subdomainName) == 0 {
37 return cf.ErrSubdomainNameRequired
38 }
39 apiToken, apiTokenExists := os.LookupEnv(cf.EnvAPIToken)
40 if !apiTokenExists || len(apiToken) == 0 {
41 return cf.ErrAPITokenRequired
42 }
43 dieRaw, dieExists := os.LookupEnv(EnvDie)
44 if !dieExists || len(dieRaw) == 0 {
45 return ErrDieRequired
46 }
47 die, err := strconv.ParseBool(dieRaw)
48 if err != nil {
49 return fmt.Errorf("failed to parse %s bool=%q: %w", EnvDie, dieRaw, err)
50 }
51
52 ipDetails, err := iplookup.Discover()
53 if err != nil {
54 return err
55 }
56
57 slog.Info("cfdns: ip discovery",
58 slog.String("ip", ipDetails.Address))
59
60 cfdns, err := cf.New(zoneName, subdomainName, apiToken)
61 if err != nil {
62 return err
63 }
64
65 slog.Info("cfdns: dns discovery",
66 slog.String("domain", subdomainName),
67 slog.String("ip", cfdns.IP()))
68
69 if ipDetails.Address != cfdns.IP() {
70 slog.Info("cfdns: update required")
71
72 err = cfdns.SetIP(ipDetails.Address)
73 if err != nil {
74 return err
75 }
76
77 slog.Info("cfdns: update complete",
78 slog.String("domain", subdomainName),
79 slog.String("ip", cfdns.IP()))
80 }
81
82 return aod.Watch(subdomainName, die)
83}