utils.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. package utils
  2. import (
  3. "encoding/json"
  4. "math/rand"
  5. "regexp"
  6. "net/http"
  7. "encoding/base64"
  8. "os"
  9. "strconv"
  10. "strings"
  11. "io/ioutil"
  12. "fmt"
  13. "sync"
  14. "time"
  15. "path/filepath"
  16. "github.com/shirou/gopsutil/v3/cpu"
  17. "github.com/shirou/gopsutil/v3/mem"
  18. "github.com/shirou/gopsutil/v3/disk"
  19. "github.com/shirou/gopsutil/v3/net"
  20. "golang.org/x/net/publicsuffix"
  21. )
  22. var ConfigLock sync.Mutex
  23. var BaseMainConfig Config
  24. var MainConfig Config
  25. var IsHTTPS = false
  26. var NewVersionAvailable = false
  27. var NeedsRestart = false
  28. var UpdateAvailable = map[string]bool{}
  29. var RestartHTTPServer func()
  30. var ReBootstrapContainer func(string) error
  31. var LetsEncryptErrors = []string{}
  32. var CONFIGFOLDER = "/var/lib/cosmos/"
  33. var DefaultConfig = Config{
  34. LoggingLevel: "INFO",
  35. NewInstall: true,
  36. AutoUpdate: true,
  37. BlockedCountries: []string{
  38. },
  39. HTTPConfig: HTTPConfig{
  40. HTTPSCertificateMode: "DISABLED",
  41. GenerateMissingAuthCert: true,
  42. HTTPPort: "80",
  43. HTTPSPort: "443",
  44. Hostname: "0.0.0.0",
  45. ProxyConfig: ProxyConfig{
  46. Routes: []ProxyRouteConfig{},
  47. },
  48. },
  49. DockerConfig: DockerConfig{
  50. DefaultDataPath: "/usr",
  51. },
  52. MarketConfig: MarketConfig{
  53. Sources: []MarketSource{
  54. },
  55. },
  56. ConstellationConfig: ConstellationConfig{
  57. Enabled: false,
  58. DNSDisabled: false,
  59. DNSFallback: "8.8.8.8:53",
  60. DNSAdditionalBlocklists: []string{
  61. "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
  62. "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
  63. "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
  64. "https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-only/hosts",
  65. },
  66. },
  67. }
  68. func FileExists(path string) bool {
  69. _, err := os.Stat(path)
  70. if err == nil {
  71. return true
  72. }
  73. Error("Reading file error: ", err)
  74. return false
  75. }
  76. func GetRootAppId() string {
  77. return "COSMOS"
  78. }
  79. func GetPrivateAuthKey() string {
  80. return MainConfig.HTTPConfig.AuthPrivateKey
  81. }
  82. func GetPublicAuthKey() string {
  83. return MainConfig.HTTPConfig.AuthPublicKey
  84. }
  85. var AlphaNumRunes = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
  86. func GenerateRandomString(n int) string {
  87. b := make([]rune, n)
  88. for i := range b {
  89. b[i] = AlphaNumRunes[rand.Intn(len(AlphaNumRunes))]
  90. }
  91. return string(b)
  92. }
  93. type HTTPErrorResult struct {
  94. Status string `json:"status"`
  95. Message string `json:"message"`
  96. Code string `json:"code"`
  97. }
  98. func HTTPError(w http.ResponseWriter, message string, code int, userCode string) {
  99. w.WriteHeader(code)
  100. json.NewEncoder(w).Encode(HTTPErrorResult{
  101. Status: "error",
  102. Message: message,
  103. Code: userCode,
  104. })
  105. Error("HTTP Request returned Error "+strconv.Itoa(code)+" : "+message, nil)
  106. }
  107. func SetBaseMainConfig(config Config) {
  108. SaveConfigTofile(config)
  109. LoadBaseMainConfig(config)
  110. }
  111. func ReadConfigFromFile() Config {
  112. configFile := GetConfigFileName()
  113. Log("Using config file: " + configFile)
  114. if CreateDefaultConfigFileIfNecessary() {
  115. LoadBaseMainConfig(DefaultConfig)
  116. return DefaultConfig
  117. }
  118. file, err := os.Open(configFile)
  119. if err != nil {
  120. Fatal("Opening Config File: ", err)
  121. }
  122. defer file.Close()
  123. decoder := json.NewDecoder(file)
  124. config := Config{}
  125. err = decoder.Decode(&config)
  126. // check file is not empty
  127. if err != nil {
  128. // check error is not empty
  129. if err.Error() == "EOF" {
  130. Fatal("Reading Config File: File is empty.", err)
  131. }
  132. // get error string
  133. errString := err.Error()
  134. // replace string in error
  135. m1 := regexp.MustCompile(`json: cannot unmarshal ([A-Za-z\.]+) into Go struct field ([A-Za-z\.]+) of type ([A-Za-z\.]+)`)
  136. errString = m1.ReplaceAllString(errString, "Invalid JSON in config file.\n > Field $2 is wrong.\n > Type is $1 Should be $3")
  137. Fatal("Reading Config File: " + errString, err)
  138. }
  139. return config
  140. }
  141. func LoadBaseMainConfig(config Config) {
  142. BaseMainConfig = config
  143. MainConfig = config
  144. // use ENV to overwrite configs
  145. if os.Getenv("COSMOS_HTTP_PORT") != "" {
  146. MainConfig.HTTPConfig.HTTPPort = os.Getenv("COSMOS_HTTP_PORT")
  147. }
  148. if os.Getenv("COSMOS_HTTPS_PORT") != "" {
  149. MainConfig.HTTPConfig.HTTPSPort = os.Getenv("COSMOS_HTTPS_PORT")
  150. }
  151. if os.Getenv("COSMOS_HOSTNAME") != "" {
  152. MainConfig.HTTPConfig.Hostname = os.Getenv("COSMOS_HOSTNAME")
  153. }
  154. if os.Getenv("COSMOS_HTTPS_MODE") != "" {
  155. MainConfig.HTTPConfig.HTTPSCertificateMode = os.Getenv("COSMOS_HTTPS_MODE")
  156. }
  157. if os.Getenv("COSMOS_GENERATE_MISSING_AUTH_CERT") != "" {
  158. MainConfig.HTTPConfig.GenerateMissingAuthCert = os.Getenv("COSMOS_GENERATE_MISSING_AUTH_CERT") == "true"
  159. }
  160. if os.Getenv("COSMOS_TLS_CERT") != "" {
  161. MainConfig.HTTPConfig.TLSCert = os.Getenv("COSMOS_TLS_CERT")
  162. }
  163. if os.Getenv("COSMOS_TLS_KEY") != "" {
  164. MainConfig.HTTPConfig.TLSKey = os.Getenv("COSMOS_TLS_KEY")
  165. }
  166. if os.Getenv("COSMOS_AUTH_PRIV_KEY") != "" {
  167. MainConfig.HTTPConfig.AuthPrivateKey = os.Getenv("COSMOS_AUTH_PRIVATE_KEY")
  168. }
  169. if os.Getenv("COSMOS_AUTH_PUBLIC_KEY") != "" {
  170. MainConfig.HTTPConfig.AuthPublicKey = os.Getenv("COSMOS_AUTH_PUBLIC_KEY")
  171. }
  172. if os.Getenv("COSMOS_LOG_LEVEL") != "" {
  173. MainConfig.LoggingLevel = (LoggingLevel)(os.Getenv("COSMOS_LOG_LEVEL"))
  174. }
  175. if os.Getenv("COSMOS_MONGODB") != "" {
  176. MainConfig.MongoDB = os.Getenv("COSMOS_MONGODB")
  177. }
  178. if os.Getenv("COSMOS_SERVER_COUNTRY") != "" {
  179. MainConfig.ServerCountry = os.Getenv("COSMOS_SERVER_COUNTRY")
  180. }
  181. if os.Getenv("COSMOS_CONFIG_FOLDER") != "" {
  182. Log("Overwriting config folder with " + os.Getenv("COSMOS_CONFIG_FOLDER"))
  183. CONFIGFOLDER = os.Getenv("COSMOS_CONFIG_FOLDER")
  184. }
  185. if MainConfig.DockerConfig.DefaultDataPath == "" {
  186. MainConfig.DockerConfig.DefaultDataPath = "/usr"
  187. }
  188. if MainConfig.ConstellationConfig.ConstellationHostname == "" {
  189. // if hostname is a domain add vpn. suffix otherwise use hostname
  190. if IsDomain(MainConfig.HTTPConfig.Hostname) {
  191. MainConfig.ConstellationConfig.ConstellationHostname = "vpn." + MainConfig.HTTPConfig.Hostname
  192. } else {
  193. MainConfig.ConstellationConfig.ConstellationHostname = MainConfig.HTTPConfig.Hostname
  194. }
  195. }
  196. }
  197. func GetMainConfig() Config {
  198. return MainConfig
  199. }
  200. func GetBaseMainConfig() Config {
  201. return BaseMainConfig
  202. }
  203. func Sanitize(s string) string {
  204. return strings.ToLower(strings.TrimSpace(s))
  205. }
  206. func SanitizeSafe(s string) string {
  207. return strings.TrimSpace(s)
  208. }
  209. func GetConfigFileName() string {
  210. if os.Getenv("COSMOS_CONFIG_FOLDER") != "" {
  211. CONFIGFOLDER = os.Getenv("COSMOS_CONFIG_FOLDER")
  212. } else if os.Getenv("HOSTNAME") != "" {
  213. CONFIGFOLDER = "/config/"
  214. }
  215. configFile := os.Getenv("CONFIG_FILE")
  216. if configFile == "" {
  217. configFile = CONFIGFOLDER + "cosmos.config.json"
  218. }
  219. return configFile
  220. }
  221. func CreateDefaultConfigFileIfNecessary() bool {
  222. configFile := GetConfigFileName()
  223. // get folder path
  224. folderPath := strings.Split(configFile, "/")
  225. folderPath = folderPath[:len(folderPath)-1]
  226. folderPathString := strings.Join(folderPath, "/")
  227. os.MkdirAll(folderPathString, os.ModePerm)
  228. if _, err := os.Stat(configFile); os.IsNotExist(err) {
  229. Log("Config file does not exist. Creating default config file.")
  230. file, err := os.Create(configFile)
  231. if err != nil {
  232. Fatal("Creating Default Config File", err)
  233. }
  234. defer file.Close()
  235. encoder := json.NewEncoder(file)
  236. encoder.SetIndent("", " ")
  237. err = encoder.Encode(DefaultConfig)
  238. if err != nil {
  239. Fatal("Writing Default Config File", err)
  240. }
  241. return true
  242. }
  243. return false
  244. }
  245. func SaveConfigTofile(config Config) {
  246. configFile := GetConfigFileName()
  247. CreateDefaultConfigFileIfNecessary()
  248. file, err := os.OpenFile(configFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm)
  249. if err != nil {
  250. Fatal("Opening Config File", err)
  251. }
  252. defer file.Close()
  253. encoder := json.NewEncoder(file)
  254. encoder.SetIndent("", " ")
  255. err = encoder.Encode(config)
  256. if err != nil {
  257. Fatal("Writing Config File", err)
  258. }
  259. Log("Config file saved.")
  260. }
  261. func RestartServer() {
  262. Log("Restarting server...")
  263. os.Exit(0)
  264. }
  265. func LetsEncryptValidOnly(hostnames []string, acceptWildcard bool) []string {
  266. wrongPattern := `^(localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|.*\.local)$`
  267. re, _ := regexp.Compile(wrongPattern)
  268. var validDomains []string
  269. for _, domain := range hostnames {
  270. if !re.MatchString(domain) && (acceptWildcard || !strings.Contains(domain, "*")) && !strings.Contains(domain, " ") && !strings.Contains(domain, ",") {
  271. validDomains = append(validDomains, domain)
  272. } else {
  273. Error("Invalid domain found in URLs: " + domain + " it was removed from the certificate to not break Let's Encrypt", nil)
  274. }
  275. }
  276. return validDomains
  277. }
  278. func GetAllHostnames(applyWildCard bool, removePorts bool) []string {
  279. mainHostname := GetMainConfig().HTTPConfig.Hostname
  280. OverrideWildcardDomains := GetMainConfig().HTTPConfig.OverrideWildcardDomains
  281. if removePorts {
  282. mainHostname = strings.Split(mainHostname, ":")[0]
  283. }
  284. hostnames := []string{
  285. mainHostname,
  286. }
  287. proxies := GetMainConfig().HTTPConfig.ProxyConfig.Routes
  288. for _, proxy := range proxies {
  289. if proxy.UseHost && proxy.Host != "" && !strings.Contains(proxy.Host, ",") && !strings.Contains(proxy.Host, " ") {
  290. if removePorts {
  291. hostnames = append(hostnames, strings.Split(proxy.Host, ":")[0])
  292. } else {
  293. hostnames = append(hostnames, proxy.Host)
  294. }
  295. }
  296. }
  297. // remove doubles
  298. seen := make(map[string]bool)
  299. uniqueHostnames := []string{}
  300. for _, hostname := range hostnames {
  301. if _, ok := seen[hostname]; !ok {
  302. seen[hostname] = true
  303. uniqueHostnames = append(uniqueHostnames, hostname)
  304. }
  305. }
  306. if applyWildCard && MainConfig.HTTPConfig.UseWildcardCertificate {
  307. bareMainHostname, _ := publicsuffix.EffectiveTLDPlusOne(mainHostname)
  308. Debug("bareMainHostname: " + bareMainHostname)
  309. filteredHostnames := []string{
  310. bareMainHostname,
  311. "*." + bareMainHostname,
  312. }
  313. if(OverrideWildcardDomains != "") {
  314. filteredHostnames = strings.Split(OverrideWildcardDomains, ",")
  315. }
  316. for _, hostname := range uniqueHostnames {
  317. if hostname != bareMainHostname && !strings.HasSuffix(hostname, "." + bareMainHostname) {
  318. filteredHostnames = append(filteredHostnames, hostname)
  319. }
  320. }
  321. uniqueHostnames = filteredHostnames
  322. }
  323. return uniqueHostnames
  324. }
  325. func GetAvailableRAM() uint64 {
  326. vmStat, err := mem.VirtualMemory()
  327. if err != nil {
  328. panic(err)
  329. }
  330. // Use total available memory as an approximation
  331. return vmStat.Available
  332. }
  333. func StringArrayEquals(a []string, b []string) bool {
  334. if len(a) != len(b) {
  335. return false
  336. }
  337. for _, value := range a {
  338. if !StringArrayContains(b, value) {
  339. return false
  340. }
  341. }
  342. for _, value := range b {
  343. if !StringArrayContains(a, value) {
  344. return false
  345. }
  346. }
  347. return true
  348. }
  349. func HasAnyNewItem(after []string, before []string) bool {
  350. for _, value := range after {
  351. if !StringArrayContains(before, value) {
  352. return true
  353. }
  354. }
  355. return false
  356. }
  357. func StringArrayContains(a []string, b string) bool {
  358. for _, value := range a {
  359. if value == b {
  360. return true
  361. }
  362. }
  363. return false
  364. }
  365. func GetServerURL() string {
  366. ServerURL := ""
  367. if IsHTTPS {
  368. ServerURL += "https://"
  369. } else {
  370. ServerURL += "http://"
  371. }
  372. ServerURL += MainConfig.HTTPConfig.Hostname
  373. if IsHTTPS && MainConfig.HTTPConfig.HTTPSPort != "443" {
  374. ServerURL += ":" + MainConfig.HTTPConfig.HTTPSPort
  375. }
  376. if !IsHTTPS && MainConfig.HTTPConfig.HTTPPort != "80" {
  377. ServerURL += ":" + MainConfig.HTTPConfig.HTTPPort
  378. }
  379. return ServerURL + "/"
  380. }
  381. func ImageToBase64(path string) (string, error) {
  382. imageFile, err := os.Open(path)
  383. if err != nil {
  384. return "", err
  385. }
  386. defer imageFile.Close()
  387. imageData, err := ioutil.ReadAll(imageFile)
  388. if err != nil {
  389. return "", err
  390. }
  391. encodedData := base64.StdEncoding.EncodeToString(imageData)
  392. fileExt := strings.ToLower(filepath.Ext(path))
  393. var mimeType string
  394. switch fileExt {
  395. case ".jpg", ".jpeg":
  396. mimeType = "image/jpeg"
  397. case ".png":
  398. mimeType = "image/png"
  399. case ".gif":
  400. mimeType = "image/gif"
  401. case ".bmp":
  402. mimeType = "image/bmp"
  403. default:
  404. return "", fmt.Errorf("unsupported file format: %s", fileExt)
  405. }
  406. dataURI := fmt.Sprintf("data:%s;base64,%s", mimeType, encodedData)
  407. return dataURI, nil
  408. }
  409. func Max(x, y int) int {
  410. if x < y {
  411. return y
  412. }
  413. return x
  414. }
  415. func GetCPUUsage() ([]float64) {
  416. percentages, _ := cpu.Percent(time.Second, false)
  417. return percentages
  418. }
  419. func GetRAMUsage() (uint64) {
  420. v, _ := mem.VirtualMemory()
  421. return v.Used
  422. }
  423. type DiskStatus struct {
  424. Path string
  425. TotalBytes uint64
  426. UsedBytes uint64
  427. }
  428. func GetDiskUsage() []DiskStatus {
  429. partitions, err := disk.Partitions(false)
  430. if err != nil {
  431. Error("Error getting disk partitions", err)
  432. return nil
  433. }
  434. var diskStatuses []DiskStatus
  435. for _, partition := range partitions {
  436. usageStat, err := disk.Usage(partition.Mountpoint)
  437. if err != nil {
  438. Error("Error getting disk usage", err)
  439. return nil
  440. }
  441. diskStatus := DiskStatus{
  442. Path: partition.Mountpoint,
  443. TotalBytes: usageStat.Total,
  444. UsedBytes: usageStat.Used,
  445. }
  446. diskStatuses = append(diskStatuses, diskStatus)
  447. }
  448. return diskStatuses
  449. }
  450. type NetworkStatus struct {
  451. BytesSent uint64
  452. BytesRecv uint64
  453. }
  454. func GetNetworkUsage() NetworkStatus {
  455. initialStat, err := net.IOCounters(true)
  456. if err != nil {
  457. Error("Error getting network usage", err)
  458. return NetworkStatus{}
  459. }
  460. time.Sleep(1 * time.Second)
  461. finalStat, err := net.IOCounters(true)
  462. if err != nil {
  463. Error("Error getting network usage", err)
  464. return NetworkStatus{}
  465. }
  466. res := NetworkStatus{
  467. BytesSent: 0,
  468. BytesRecv: 0,
  469. }
  470. for i := range initialStat {
  471. res.BytesSent += finalStat[i].BytesSent - initialStat[i].BytesSent
  472. res.BytesRecv += finalStat[i].BytesRecv - initialStat[i].BytesRecv
  473. }
  474. return NetworkStatus{}
  475. }
  476. func DownloadFile(url string) (string, error) {
  477. resp, err := http.Get(url)
  478. if err != nil {
  479. return "", err
  480. }
  481. defer resp.Body.Close()
  482. body, err := ioutil.ReadAll(resp.Body)
  483. if err != nil {
  484. return "", err
  485. }
  486. return string(body), nil
  487. }
  488. func GetClientIP(req *http.Request) string {
  489. /*ip := req.Header.Get("X-Forwarded-For")
  490. if ip == "" {
  491. ip = req.RemoteAddr
  492. }*/
  493. return req.RemoteAddr
  494. }
  495. func IsDomain(domain string) bool {
  496. // contains . and at least a letter and no special characters invalid in a domain
  497. if strings.Contains(domain, ".") && strings.ContainsAny(domain, "abcdefghijklmnopqrstuvwxyz") && !strings.ContainsAny(domain, " !@#$%^&*()+=[]{}\\|;:'\",/<>?") {
  498. return true
  499. }
  500. return false
  501. }