123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602 |
- package utils
- import (
- "encoding/json"
- "math/rand"
- "regexp"
- "net/http"
- "encoding/base64"
- "os"
- "strconv"
- "strings"
- "io/ioutil"
- "fmt"
- "sync"
- "time"
- "path/filepath"
- "github.com/shirou/gopsutil/v3/cpu"
- "github.com/shirou/gopsutil/v3/mem"
- "github.com/shirou/gopsutil/v3/disk"
- "github.com/shirou/gopsutil/v3/net"
- "golang.org/x/net/publicsuffix"
- )
- var ConfigLock sync.Mutex
- var BaseMainConfig Config
- var MainConfig Config
- var IsHTTPS = false
- var NewVersionAvailable = false
- var NeedsRestart = false
- var UpdateAvailable = map[string]bool{}
- var RestartHTTPServer func()
- var ReBootstrapContainer func(string) error
- var LetsEncryptErrors = []string{}
- var CONFIGFOLDER = "/var/lib/cosmos/"
- var DefaultConfig = Config{
- LoggingLevel: "INFO",
- NewInstall: true,
- AutoUpdate: true,
- BlockedCountries: []string{
- },
- HTTPConfig: HTTPConfig{
- HTTPSCertificateMode: "DISABLED",
- GenerateMissingAuthCert: true,
- HTTPPort: "80",
- HTTPSPort: "443",
- Hostname: "0.0.0.0",
- ProxyConfig: ProxyConfig{
- Routes: []ProxyRouteConfig{},
- },
- },
- DockerConfig: DockerConfig{
- DefaultDataPath: "/usr",
- },
- MarketConfig: MarketConfig{
- Sources: []MarketSource{
- },
- },
- ConstellationConfig: ConstellationConfig{
- Enabled: false,
- DNSDisabled: false,
- DNSFallback: "8.8.8.8:53",
- DNSAdditionalBlocklists: []string{
- "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
- "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
- "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
- "https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-only/hosts",
- },
- },
- }
- func FileExists(path string) bool {
- _, err := os.Stat(path)
- if err == nil {
- return true
- }
- Error("Reading file error: ", err)
- return false
- }
- func GetRootAppId() string {
- return "COSMOS"
- }
- func GetPrivateAuthKey() string {
- return MainConfig.HTTPConfig.AuthPrivateKey
- }
- func GetPublicAuthKey() string {
- return MainConfig.HTTPConfig.AuthPublicKey
- }
- var AlphaNumRunes = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
- func GenerateRandomString(n int) string {
- b := make([]rune, n)
- for i := range b {
- b[i] = AlphaNumRunes[rand.Intn(len(AlphaNumRunes))]
- }
- return string(b)
- }
- type HTTPErrorResult struct {
- Status string `json:"status"`
- Message string `json:"message"`
- Code string `json:"code"`
- }
- func HTTPError(w http.ResponseWriter, message string, code int, userCode string) {
- w.WriteHeader(code)
- json.NewEncoder(w).Encode(HTTPErrorResult{
- Status: "error",
- Message: message,
- Code: userCode,
- })
- Error("HTTP Request returned Error "+strconv.Itoa(code)+" : "+message, nil)
- }
- func SetBaseMainConfig(config Config) {
- SaveConfigTofile(config)
- LoadBaseMainConfig(config)
- }
- func ReadConfigFromFile() Config {
- configFile := GetConfigFileName()
- Log("Using config file: " + configFile)
- if CreateDefaultConfigFileIfNecessary() {
- LoadBaseMainConfig(DefaultConfig)
- return DefaultConfig
- }
- file, err := os.Open(configFile)
- if err != nil {
- Fatal("Opening Config File: ", err)
- }
- defer file.Close()
- decoder := json.NewDecoder(file)
- config := Config{}
- err = decoder.Decode(&config)
- // check file is not empty
- if err != nil {
- // check error is not empty
- if err.Error() == "EOF" {
- Fatal("Reading Config File: File is empty.", err)
- }
- // get error string
- errString := err.Error()
- // replace string in error
- m1 := regexp.MustCompile(`json: cannot unmarshal ([A-Za-z\.]+) into Go struct field ([A-Za-z\.]+) of type ([A-Za-z\.]+)`)
- errString = m1.ReplaceAllString(errString, "Invalid JSON in config file.\n > Field $2 is wrong.\n > Type is $1 Should be $3")
- Fatal("Reading Config File: " + errString, err)
- }
- return config
- }
- func LoadBaseMainConfig(config Config) {
- BaseMainConfig = config
- MainConfig = config
- // use ENV to overwrite configs
- if os.Getenv("COSMOS_HTTP_PORT") != "" {
- MainConfig.HTTPConfig.HTTPPort = os.Getenv("COSMOS_HTTP_PORT")
- }
- if os.Getenv("COSMOS_HTTPS_PORT") != "" {
- MainConfig.HTTPConfig.HTTPSPort = os.Getenv("COSMOS_HTTPS_PORT")
- }
- if os.Getenv("COSMOS_HOSTNAME") != "" {
- MainConfig.HTTPConfig.Hostname = os.Getenv("COSMOS_HOSTNAME")
- }
- if os.Getenv("COSMOS_HTTPS_MODE") != "" {
- MainConfig.HTTPConfig.HTTPSCertificateMode = os.Getenv("COSMOS_HTTPS_MODE")
- }
- if os.Getenv("COSMOS_GENERATE_MISSING_AUTH_CERT") != "" {
- MainConfig.HTTPConfig.GenerateMissingAuthCert = os.Getenv("COSMOS_GENERATE_MISSING_AUTH_CERT") == "true"
- }
- if os.Getenv("COSMOS_TLS_CERT") != "" {
- MainConfig.HTTPConfig.TLSCert = os.Getenv("COSMOS_TLS_CERT")
- }
- if os.Getenv("COSMOS_TLS_KEY") != "" {
- MainConfig.HTTPConfig.TLSKey = os.Getenv("COSMOS_TLS_KEY")
- }
- if os.Getenv("COSMOS_AUTH_PRIV_KEY") != "" {
- MainConfig.HTTPConfig.AuthPrivateKey = os.Getenv("COSMOS_AUTH_PRIVATE_KEY")
- }
- if os.Getenv("COSMOS_AUTH_PUBLIC_KEY") != "" {
- MainConfig.HTTPConfig.AuthPublicKey = os.Getenv("COSMOS_AUTH_PUBLIC_KEY")
- }
- if os.Getenv("COSMOS_LOG_LEVEL") != "" {
- MainConfig.LoggingLevel = (LoggingLevel)(os.Getenv("COSMOS_LOG_LEVEL"))
- }
- if os.Getenv("COSMOS_MONGODB") != "" {
- MainConfig.MongoDB = os.Getenv("COSMOS_MONGODB")
- }
- if os.Getenv("COSMOS_SERVER_COUNTRY") != "" {
- MainConfig.ServerCountry = os.Getenv("COSMOS_SERVER_COUNTRY")
- }
- if os.Getenv("COSMOS_CONFIG_FOLDER") != "" {
- Log("Overwriting config folder with " + os.Getenv("COSMOS_CONFIG_FOLDER"))
- CONFIGFOLDER = os.Getenv("COSMOS_CONFIG_FOLDER")
- }
-
- if MainConfig.DockerConfig.DefaultDataPath == "" {
- MainConfig.DockerConfig.DefaultDataPath = "/usr"
- }
-
- if MainConfig.ConstellationConfig.ConstellationHostname == "" {
- // if hostname is a domain add vpn. suffix otherwise use hostname
- if IsDomain(MainConfig.HTTPConfig.Hostname) {
- MainConfig.ConstellationConfig.ConstellationHostname = "vpn." + MainConfig.HTTPConfig.Hostname
- } else {
- MainConfig.ConstellationConfig.ConstellationHostname = MainConfig.HTTPConfig.Hostname
- }
- }
- }
- func GetMainConfig() Config {
- return MainConfig
- }
- func GetBaseMainConfig() Config {
- return BaseMainConfig
- }
- func Sanitize(s string) string {
- return strings.ToLower(strings.TrimSpace(s))
- }
- func SanitizeSafe(s string) string {
- return strings.TrimSpace(s)
- }
- func GetConfigFileName() string {
- if os.Getenv("COSMOS_CONFIG_FOLDER") != "" {
- CONFIGFOLDER = os.Getenv("COSMOS_CONFIG_FOLDER")
- } else if os.Getenv("HOSTNAME") != "" {
- CONFIGFOLDER = "/config/"
- }
-
- configFile := os.Getenv("CONFIG_FILE")
- if configFile == "" {
- configFile = CONFIGFOLDER + "cosmos.config.json"
- }
- return configFile
- }
- func CreateDefaultConfigFileIfNecessary() bool {
- configFile := GetConfigFileName()
- // get folder path
- folderPath := strings.Split(configFile, "/")
- folderPath = folderPath[:len(folderPath)-1]
- folderPathString := strings.Join(folderPath, "/")
- os.MkdirAll(folderPathString, os.ModePerm)
- if _, err := os.Stat(configFile); os.IsNotExist(err) {
- Log("Config file does not exist. Creating default config file.")
- file, err := os.Create(configFile)
- if err != nil {
- Fatal("Creating Default Config File", err)
- }
- defer file.Close()
- encoder := json.NewEncoder(file)
- encoder.SetIndent("", " ")
- err = encoder.Encode(DefaultConfig)
- if err != nil {
- Fatal("Writing Default Config File", err)
- }
- return true
- }
- return false
- }
- func SaveConfigTofile(config Config) {
- configFile := GetConfigFileName()
- CreateDefaultConfigFileIfNecessary()
- file, err := os.OpenFile(configFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm)
- if err != nil {
- Fatal("Opening Config File", err)
- }
- defer file.Close()
- encoder := json.NewEncoder(file)
- encoder.SetIndent("", " ")
- err = encoder.Encode(config)
- if err != nil {
- Fatal("Writing Config File", err)
- }
- Log("Config file saved.")
- }
- func RestartServer() {
- Log("Restarting server...")
- os.Exit(0)
- }
- func LetsEncryptValidOnly(hostnames []string, acceptWildcard bool) []string {
- wrongPattern := `^(localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|.*\.local)$`
- re, _ := regexp.Compile(wrongPattern)
- var validDomains []string
- for _, domain := range hostnames {
- if !re.MatchString(domain) && (acceptWildcard || !strings.Contains(domain, "*")) && !strings.Contains(domain, " ") && !strings.Contains(domain, ",") {
- validDomains = append(validDomains, domain)
- } else {
- Error("Invalid domain found in URLs: " + domain + " it was removed from the certificate to not break Let's Encrypt", nil)
- }
- }
- return validDomains
- }
- func GetAllHostnames(applyWildCard bool, removePorts bool) []string {
- mainHostname := GetMainConfig().HTTPConfig.Hostname
- OverrideWildcardDomains := GetMainConfig().HTTPConfig.OverrideWildcardDomains
- if removePorts {
- mainHostname = strings.Split(mainHostname, ":")[0]
- }
- hostnames := []string{
- mainHostname,
- }
- proxies := GetMainConfig().HTTPConfig.ProxyConfig.Routes
- for _, proxy := range proxies {
- if proxy.UseHost && proxy.Host != "" && !strings.Contains(proxy.Host, ",") && !strings.Contains(proxy.Host, " ") {
- if removePorts {
- hostnames = append(hostnames, strings.Split(proxy.Host, ":")[0])
- } else {
- hostnames = append(hostnames, proxy.Host)
- }
- }
- }
- // remove doubles
- seen := make(map[string]bool)
- uniqueHostnames := []string{}
- for _, hostname := range hostnames {
- if _, ok := seen[hostname]; !ok {
- seen[hostname] = true
- uniqueHostnames = append(uniqueHostnames, hostname)
- }
- }
- if applyWildCard && MainConfig.HTTPConfig.UseWildcardCertificate {
- bareMainHostname, _ := publicsuffix.EffectiveTLDPlusOne(mainHostname)
- Debug("bareMainHostname: " + bareMainHostname)
- filteredHostnames := []string{
- bareMainHostname,
- "*." + bareMainHostname,
- }
- if(OverrideWildcardDomains != "") {
- filteredHostnames = strings.Split(OverrideWildcardDomains, ",")
- }
- for _, hostname := range uniqueHostnames {
- if hostname != bareMainHostname && !strings.HasSuffix(hostname, "." + bareMainHostname) {
- filteredHostnames = append(filteredHostnames, hostname)
- }
- }
- uniqueHostnames = filteredHostnames
- }
- return uniqueHostnames
- }
- func GetAvailableRAM() uint64 {
- vmStat, err := mem.VirtualMemory()
- if err != nil {
- panic(err)
- }
- // Use total available memory as an approximation
- return vmStat.Available
- }
- func StringArrayEquals(a []string, b []string) bool {
- if len(a) != len(b) {
- return false
- }
- for _, value := range a {
- if !StringArrayContains(b, value) {
- return false
- }
- }
- for _, value := range b {
- if !StringArrayContains(a, value) {
- return false
- }
- }
- return true
- }
- func HasAnyNewItem(after []string, before []string) bool {
- for _, value := range after {
- if !StringArrayContains(before, value) {
- return true
- }
- }
- return false
- }
- func StringArrayContains(a []string, b string) bool {
- for _, value := range a {
- if value == b {
- return true
- }
- }
- return false
- }
- func GetServerURL() string {
- ServerURL := ""
- if IsHTTPS {
- ServerURL += "https://"
- } else {
- ServerURL += "http://"
- }
- ServerURL += MainConfig.HTTPConfig.Hostname
- if IsHTTPS && MainConfig.HTTPConfig.HTTPSPort != "443" {
- ServerURL += ":" + MainConfig.HTTPConfig.HTTPSPort
- }
- if !IsHTTPS && MainConfig.HTTPConfig.HTTPPort != "80" {
- ServerURL += ":" + MainConfig.HTTPConfig.HTTPPort
- }
- return ServerURL + "/"
- }
- func ImageToBase64(path string) (string, error) {
- imageFile, err := os.Open(path)
- if err != nil {
- return "", err
- }
- defer imageFile.Close()
- imageData, err := ioutil.ReadAll(imageFile)
- if err != nil {
- return "", err
- }
- encodedData := base64.StdEncoding.EncodeToString(imageData)
- fileExt := strings.ToLower(filepath.Ext(path))
- var mimeType string
- switch fileExt {
- case ".jpg", ".jpeg":
- mimeType = "image/jpeg"
- case ".png":
- mimeType = "image/png"
- case ".gif":
- mimeType = "image/gif"
- case ".bmp":
- mimeType = "image/bmp"
- default:
- return "", fmt.Errorf("unsupported file format: %s", fileExt)
- }
- dataURI := fmt.Sprintf("data:%s;base64,%s", mimeType, encodedData)
- return dataURI, nil
- }
- func Max(x, y int) int {
- if x < y {
- return y
- }
- return x
- }
- func GetCPUUsage() ([]float64) {
- percentages, _ := cpu.Percent(time.Second, false)
- return percentages
- }
- func GetRAMUsage() (uint64) {
- v, _ := mem.VirtualMemory()
- return v.Used
- }
- type DiskStatus struct {
- Path string
- TotalBytes uint64
- UsedBytes uint64
- }
- func GetDiskUsage() []DiskStatus {
- partitions, err := disk.Partitions(false)
- if err != nil {
- Error("Error getting disk partitions", err)
- return nil
- }
- var diskStatuses []DiskStatus
- for _, partition := range partitions {
- usageStat, err := disk.Usage(partition.Mountpoint)
- if err != nil {
- Error("Error getting disk usage", err)
- return nil
- }
- diskStatus := DiskStatus{
- Path: partition.Mountpoint,
- TotalBytes: usageStat.Total,
- UsedBytes: usageStat.Used,
- }
- diskStatuses = append(diskStatuses, diskStatus)
- }
- return diskStatuses
- }
- type NetworkStatus struct {
- BytesSent uint64
- BytesRecv uint64
- }
- func GetNetworkUsage() NetworkStatus {
- initialStat, err := net.IOCounters(true)
- if err != nil {
- Error("Error getting network usage", err)
- return NetworkStatus{}
- }
- time.Sleep(1 * time.Second)
- finalStat, err := net.IOCounters(true)
- if err != nil {
- Error("Error getting network usage", err)
- return NetworkStatus{}
- }
- res := NetworkStatus{
- BytesSent: 0,
- BytesRecv: 0,
- }
-
- for i := range initialStat {
- res.BytesSent += finalStat[i].BytesSent - initialStat[i].BytesSent
- res.BytesRecv += finalStat[i].BytesRecv - initialStat[i].BytesRecv
- }
- return NetworkStatus{}
- }
- func DownloadFile(url string) (string, error) {
- resp, err := http.Get(url)
- if err != nil {
- return "", err
- }
- defer resp.Body.Close()
-
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return "", err
- }
-
- return string(body), nil
- }
- func GetClientIP(req *http.Request) string {
- /*ip := req.Header.Get("X-Forwarded-For")
- if ip == "" {
- ip = req.RemoteAddr
- }*/
- return req.RemoteAddr
- }
- func IsDomain(domain string) bool {
- // contains . and at least a letter and no special characters invalid in a domain
- if strings.Contains(domain, ".") && strings.ContainsAny(domain, "abcdefghijklmnopqrstuvwxyz") && !strings.ContainsAny(domain, " !@#$%^&*()+=[]{}\\|;:'\",/<>?") {
- return true
- }
- return false
- }
|