github.com/pbberlin/go-pwa@v0.0.0-20220328105622-7c26e0ca1ab8/pkg/cfg/config.go (about) 1 // package cfg load app scope JSON settings 2 // its init() func needs to be executed first 3 package cfg 4 5 import ( 6 "encoding/json" 7 "fmt" 8 "html/template" 9 "io" 10 "log" 11 "math" 12 "net/http" 13 "net/http/httptest" 14 "os" 15 "strings" 16 "time" 17 ) 18 19 // Headless executes a HTTP handle func 20 // and writes it output into the log 21 func Headless(fnc func(w http.ResponseWriter, r *http.Request), pth string) { 22 log.SetFlags(log.Lshortfile | log.Llongfile) 23 w := httptest.NewRecorder() 24 r := httptest.NewRequest("GET", pth, nil) 25 fnc(w, r) 26 27 res := w.Result() 28 defer res.Body.Close() 29 bts, err := io.ReadAll(res.Body) 30 if err != nil { 31 log.Printf("error reading response of %v: %v", pth, err) 32 } 33 log.Print(string(bts)) 34 } 35 36 func init() { 37 // RunHandleFunc(Load, "/config/load") 38 } 39 40 // 41 // cfg contain app scope data 42 type cfg struct { 43 Title string `json:"title,omitempty"` // app title - into html title tag 44 TitleShort string `json:"title_short,omitempty"` // => manifest JSON 45 Description string `json:"description,omitempty"` // default meta tag description 46 47 ModeHTTPS string `json:"mode_https,omitempty"` 48 49 Domains []string `json:"domains,omitempty"` 50 51 AllowHTTP bool `json:"allow_http,omitempty"` // or forward to HTTPS, only implemented for ModeHTTPS==letsenrypt-extended 52 AutoRedirectHTTP bool `json:"auto_redirect_http,omitempty"` // when HTTP is not allowed: Redirect automatically redirect to HTTPS via web browser 53 54 // serve precompressed .gzip files 55 // default is dynamic compression via gziphandler 56 StaticFilesGZIP bool `json:"static_files_gzip,omitempty"` 57 58 // 59 // dynamically set on app init 60 TplMain *template.Template `json:"-"` // loaded from scaffold.tpl.html 61 62 CSP string `json:"-"` // content security policy 63 JS string `json:"-"` // created from ./app-bucket/js 64 CSS string `json:"-"` // created from ./app-bucket/css 65 66 Dms string `json:"-"` // string representation of domains 67 68 AppDir string `json:"-"` // server side app dir; do we still need this - or is ./... always sufficient? 69 TS string `json:"-"` // prefix 'vs-' and then timestamp of app start 70 SchemaVersion int `json:"schema_version"` // client database schema version 71 72 PrefURI string `json:"pref_uri,omitempty"` 73 } 74 75 var defaultCfg = &cfg{ 76 Title: "your app title", 77 TitleShort: "app123", 78 Description: "default meta tag description", 79 80 ModeHTTPS: "https-localhost-cert", 81 // ModeHTTPS: "letsenrypt-simple", 82 // ModeHTTPS: "letsenrypt-extended", 83 84 Domains: []string{"fmt.zew.de", "fmt.zew.de"}, 85 86 // 87 AllowHTTP: true, 88 AutoRedirectHTTP: true, 89 90 StaticFilesGZIP: true, 91 92 SchemaVersion: 2, 93 } 94 95 func (cfg *cfg) cSP() string { 96 97 /* 98 https://csp.withgoogle.com/docs/index.html 99 https://csp.withgoogle.com/docs/strict-csp.html#example 100 101 default-src - https: http: 102 script-src - 'nonce-{random}' 'unsafe-inline' - for old browsers 103 style-src-elem - 'self' 'nonce-{random}' - strangely, self is required 104 strict-dynamic - unused; 'script-xyz.js'can load additional scripts via script elements 105 object-src - sources for <object>, <embed>, <applet> 106 base-uri - 'self' 'none' - for relative URLs 107 report-uri - https://your-report-collector.example.com/ 108 109 */ 110 csp := "" 111 csp += fmt.Sprintf("default-src https:; ") 112 csp += fmt.Sprintf("base-uri 'none'; ") 113 csp += fmt.Sprintf("object-src 'none'; ") 114 csp += fmt.Sprintf("script-src 'nonce-%s' 'unsafe-inline'; ", cfg.TS) 115 csp += fmt.Sprintf("style-src-elem 'nonce-%s' 'self'; ", cfg.TS) 116 csp += fmt.Sprintf("worker-src https://*/service-worker.js; ") 117 118 return csp 119 } 120 121 func Load(w http.ResponseWriter, r *http.Request) { 122 123 w.Header().Set("Content-Type", "text/plain") 124 fmt.Fprint(w, "config load start\n") 125 126 // write example with defaults 127 { 128 pth := "./app-bucket/server-config/tmp-example-config.json" 129 bts, err := json.MarshalIndent(defaultCfg, "", "\t") 130 if err != nil { 131 fmt.Fprintf(w, "error marshalling defaultCfg %v \n", err) 132 } 133 134 err = os.WriteFile(pth, bts, 0777) 135 if err != nil { 136 fmt.Fprintf(w, "error saving %v, %v \n", pth, err) 137 } 138 } 139 140 // 141 tmpCfg := cfg{} 142 pth := "./app-bucket/server-config/config.json" 143 bts, err := os.ReadFile(pth) 144 if err != nil { 145 fmt.Fprintf(w, "error opening %v, %v \n", pth, err) 146 panic(fmt.Sprintf("need %v", pth)) 147 } else { 148 err := json.Unmarshal(bts, &tmpCfg) 149 if err != nil { 150 fmt.Fprintf(w, "error unmarshalling %v, %v \n", pth, err) 151 panic(fmt.Sprintf("need valid %v", pth)) 152 } else { 153 fmt.Fprintf(w, "config loaded from %v \n", pth) 154 } 155 } 156 157 // 158 // 159 // other dynamic stuff 160 tmpCfg.Dms = strings.Join(tmpCfg.Domains, ", ") 161 162 tmpCfg.AppDir, err = os.Getwd() 163 if err != nil { 164 log.Fatalf("error os.Getwd() %v \n", err) 165 } 166 167 // time stamp 168 // keeping only the last 6 digits 169 now := time.Now().UTC().Unix() 170 fst4 := float64(now) / float64(1000*1000) // first four digits 171 fst4 = math.Floor(fst4) * float64(1000*1000) 172 now -= int64(fst4) 173 174 tmpCfg.TS = fmt.Sprintf("vs-%d", now) 175 176 tmpCfg.CSP = tmpCfg.cSP() 177 178 // 179 { 180 pth := "./app-bucket/tpl/scaffold.tpl.html" 181 var err error 182 tmpCfg.TplMain, err = template.ParseFiles(pth) 183 if err != nil { 184 log.Printf("error parsing %v, %v", pth, err) 185 return 186 } 187 } 188 189 defaultCfg = &tmpCfg // replace pointer at once - should be threadsafe 190 191 fmt.Fprint(w, "config load stop\n\n") 192 193 } 194 195 func Get() *cfg { 196 // this might occur during testing 197 // if defaultCfg == nil { 198 // log.Fatalf("config accessed before it has been loaded") 199 // } 200 return defaultCfg 201 } 202 203 func Set() *cfg { 204 return defaultCfg 205 }