config.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package config
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "goseg/defaults"
  6. "goseg/structs"
  7. "io/ioutil"
  8. "log/slog"
  9. "net"
  10. "os"
  11. "path/filepath"
  12. "runtime"
  13. "sync"
  14. "time"
  15. )
  16. var (
  17. globalConfig structs.SysConfig
  18. logger = slog.New(slog.NewJSONHandler(os.Stdout, nil))
  19. BasePath = os.Getenv("GS_BASE_PATH")
  20. Architecture = getArchitecture()
  21. DebugMode = false
  22. Ready = false
  23. GSContainers = make(map[string]structs.ContainerState)
  24. checkInterval = 5 * time.Minute
  25. confMutex sync.Mutex
  26. contMutex sync.Mutex
  27. versMutex sync.Mutex
  28. )
  29. // try initializing from system.json on disk
  30. func init() {
  31. logger.Info("Starting GroundSeg")
  32. logger.Info("Urbit is love <3")
  33. for _, arg := range os.Args[1:] {
  34. // trigger this with `./groundseg dev`
  35. if arg == "dev" {
  36. logger.Info("Starting GroundSeg in debug mode")
  37. DebugMode = true
  38. }
  39. }
  40. if BasePath == "" {
  41. // default base path
  42. BasePath = "/opt/nativeplanet/groundseg"
  43. }
  44. pathMsg := fmt.Sprintf("Loading configs from %s", BasePath)
  45. logger.Info(pathMsg)
  46. confPath := filepath.Join(BasePath, "settings", "system.json")
  47. file, err := os.Open(confPath)
  48. if err != nil {
  49. // create a default if it doesn't exist
  50. err = createDefaultConf()
  51. if err != nil {
  52. // panic if we can't create it
  53. errmsg := fmt.Sprintf("Unable to create config! Please elevate permissions. %v", err)
  54. logger.Error(errmsg)
  55. panic(errmsg)
  56. }
  57. }
  58. defer file.Close()
  59. // read the sysconfig to memory
  60. decoder := json.NewDecoder(file)
  61. err = decoder.Decode(&globalConfig)
  62. if err != nil {
  63. errmsg := fmt.Sprintf("Error decoding JSON: %v", err)
  64. logger.Error(errmsg)
  65. }
  66. }
  67. // return the global conf var
  68. func Conf() structs.SysConfig {
  69. confMutex.Lock()
  70. defer confMutex.Unlock()
  71. return globalConfig
  72. }
  73. // tell if we're amd64 or arm64
  74. func getArchitecture() string {
  75. switch runtime.GOARCH {
  76. case "arm64", "aarch64":
  77. return "arm64"
  78. default:
  79. return "amd64"
  80. }
  81. }
  82. // update by passing in a map of key:values you want to modify
  83. func UpdateConf(values map[string]interface{}) error {
  84. // mutex lock to avoid race conditions
  85. confMutex.Lock()
  86. defer confMutex.Unlock()
  87. confPath := filepath.Join(BasePath, "settings", "system.json")
  88. file, err := ioutil.ReadFile(confPath)
  89. if err != nil {
  90. errmsg := fmt.Sprintf("Unable to load config: %v", err)
  91. logger.Error(errmsg)
  92. return err
  93. }
  94. // unmarshal the config to struct
  95. var configMap map[string]interface{}
  96. if err := json.Unmarshal(file, &configMap); err != nil {
  97. errmsg := fmt.Sprintf("Error decoding JSON: %v", err)
  98. logger.Error(errmsg)
  99. return err
  100. }
  101. // update our unmarshaled struct
  102. for key, value := range values {
  103. configMap[key] = value
  104. }
  105. // marshal and persist it
  106. updatedJSON, err := json.MarshalIndent(configMap, "", " ")
  107. if err != nil {
  108. errmsg := fmt.Sprintf("Error encoding JSON: %v", err)
  109. logger.Error(errmsg)
  110. return err
  111. }
  112. // update the globalConfig var
  113. if err := json.Unmarshal(updatedJSON, &globalConfig); err != nil {
  114. errmsg := fmt.Sprintf("Error updating global config: %v", err)
  115. logger.Error(errmsg)
  116. return err
  117. }
  118. if err := ioutil.WriteFile(confPath, updatedJSON, 0644); err != nil {
  119. errmsg := fmt.Sprintf("Error writing to file: %v", err)
  120. logger.Error(errmsg)
  121. return err
  122. }
  123. return nil
  124. }
  125. // we keep map[string]structs.ContainerState in memory to keep track of the containers
  126. // eg if they're running and whether they should be
  127. // modify the desired/actual state of containers
  128. func UpdateContainerState(name string, containerState structs.ContainerState) {
  129. contMutex.Lock()
  130. defer contMutex.Unlock()
  131. GSContainers[name] = containerState
  132. res, _ := json.Marshal(containerState)
  133. logger.Info(fmt.Sprintf("%s:%s", name, string(res)))
  134. }
  135. // get the current container state
  136. func GetContainerState() map[string]structs.ContainerState {
  137. contMutex.Lock()
  138. defer contMutex.Unlock()
  139. return GSContainers
  140. }
  141. // write a default conf to disk
  142. func createDefaultConf() error {
  143. defaultConfig := defaults.SysConfig(BasePath)
  144. path := filepath.Join(BasePath, "settings", "system.json")
  145. if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
  146. return err
  147. }
  148. file, err := os.Create(path)
  149. if err != nil {
  150. return err
  151. }
  152. defer file.Close()
  153. encoder := json.NewEncoder(file)
  154. encoder.SetIndent("", " ")
  155. if err := encoder.Encode(&defaultConfig); err != nil {
  156. return err
  157. }
  158. return nil
  159. }
  160. // check outbound tcp connectivity
  161. // takes ip:port
  162. func NetCheck(netCheck string) bool {
  163. logger.Info("Checking internet access")
  164. internet := false
  165. timeout := 3 * time.Second
  166. conn, err := net.DialTimeout("tcp", netCheck, timeout)
  167. if err != nil {
  168. errmsg := fmt.Sprintf("Check internet access error: %v", err)
  169. logger.Error(errmsg)
  170. } else {
  171. internet = true
  172. _ = conn.Close()
  173. }
  174. return internet
  175. }