func handleSetupChange(rw http.ResponseWriter, req *http.Request) { hilevelConf, err := jsonconfig.ReadFile(osutil.UserServerConfigPath()) if err != nil { httputil.ServeError(rw, req, err) return } if !xsrftoken.Valid(req.FormValue("token"), serverKey, "user", "wizardSave") { http.Error(rw, "Form expired. Press back and reload form.", http.StatusBadRequest) log.Printf("invalid xsrf token=%q", req.FormValue("token")) return } hasChanged := false var el interface{} publish := jsonconfig.Obj{} for k, v := range req.Form { if _, ok := hilevelConf[k]; !ok { if k != "gallery" && k != "blog" { continue } } switch k { case "https", "shareHandler": b, err := strconv.ParseBool(v[0]) if err != nil { httputil.ServeError(rw, req, fmt.Errorf("%v field expects a boolean value", k)) } el = b default: el = v[0] } if reflect.DeepEqual(hilevelConf[k], el) { continue } hasChanged = true hilevelConf[k] = el } // "publish" wasn't checked yet if !reflect.DeepEqual(hilevelConf["publish"], publish) { hilevelConf["publish"] = publish hasChanged = true } if hasChanged { err = rewriteConfig(&hilevelConf, osutil.UserServerConfigPath()) if err != nil { httputil.ServeError(rw, req, err) return } err = osutil.RestartProcess() if err != nil { log.Fatal("Failed to restart: " + err.Error()) http.Error(rw, "Failed to restart process", 500) return } } sendWizard(rw, req, hasChanged) }
func TestStarts(t *testing.T) { td, err := ioutil.TempDir("", "camlistored-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(td) fakeHome := filepath.Join(td, "fakeHome") confDir := filepath.Join(fakeHome, "conf") defer pushEnv("HOME", fakeHome)() defer pushEnv("HOMEPATH", fakeHome)() defer pushEnv("APPDATA", filepath.Join(fakeHome, "appdata"))() defer pushEnv("CAMLI_CONFIG_DIR", confDir)() if _, err := os.Stat(osutil.CamliConfigDir()); !os.IsNotExist(err) { t.Fatalf("expected conf dir %q to not exist", osutil.CamliConfigDir()) } if !strings.Contains(osutil.CamliBlobRoot(), td) { t.Fatalf("blob root %q should contain the temp dir %q", osutil.CamliBlobRoot(), td) } if _, err := os.Stat(osutil.CamliBlobRoot()); !os.IsNotExist(err) { t.Fatalf("expected blobroot dir %q to not exist", osutil.CamliBlobRoot()) } if fi, err := os.Stat(osutil.UserServerConfigPath()); !os.IsNotExist(err) { t.Errorf("expected no server config file; got %v, %v", fi, err) } mkdir(t, confDir) *flagOpenBrowser = false defaultListenAddr = ":0" up := make(chan struct{}) down := make(chan struct{}) dead := make(chan int, 1) osExit = func(status int) { dead <- status close(dead) runtime.Goexit() } go Main(up, down) select { case status := <-dead: t.Errorf("os.Exit(%d) before server came up", status) return case <-up: t.Logf("server is up") case <-time.After(10 * time.Second): t.Fatal("timeout starting server") } if _, err := os.Stat(osutil.UserServerConfigPath()); err != nil { t.Errorf("expected a server config file; got %v", err) } down <- struct{}{} <-dead }
func (c *reindexdpCmd) RunCommand(args []string) error { var path string switch { case len(args) == 0: cfg, err := serverconfig.Load(osutil.UserServerConfigPath()) if err != nil { return err } prefixes, ok := cfg.Obj["prefixes"].(map[string]interface{}) if !ok { return fmt.Errorf("No 'prefixes' object in low-level (or converted) config file %s", osutil.UserServerConfigPath()) } paths := []string{} for prefix, vei := range prefixes { pmap, ok := vei.(map[string]interface{}) if !ok { log.Printf("prefix %q value is a %T, not an object", prefix, vei) continue } pconf := jsonconfig.Obj(pmap) handlerType := pconf.RequiredString("handler") handlerArgs := pconf.OptionalObject("handlerArgs") // no pconf.Validate, as this is a recover tool if handlerType != "storage-diskpacked" { continue } if handlerArgs == nil { log.Printf("no handlerArgs for %q", prefix) continue } aconf := jsonconfig.Obj(handlerArgs) path = aconf.RequiredString("path") // no aconv.Validate, as this is a recover tool if path != "" { paths = append(paths, path) } } if len(paths) == 0 { return fmt.Errorf("Server config file %s doesn't specify a disk-packed storage handler.", osutil.UserServerConfigPath()) } if len(paths) > 1 { return fmt.Errorf("Ambiguity. Server config file %s d specify more than 1 disk-packed storage handler. Please specify one of: %v", osutil.UserServerConfigPath(), paths) } path = paths[0] case len(args) == 1: path = args[0] default: return errors.New("More than 1 argument not allowed") } if path == "" { return errors.New("no path is given/found") } return diskpacked.Reindex(path, c.overwrite) }
func sendWizard(rw http.ResponseWriter, req *http.Request, hasChanged bool) { config, err := jsonconfig.ReadFile(osutil.UserServerConfigPath()) if err != nil { httputil.ServeError(rw, req, err) return } err = flattenPublish(config) if err != nil { httputil.ServeError(rw, req, err) return } funcMap := template.FuncMap{ "printWizard": printWizard, "showField": func(inputName string) bool { if _, ok := ignoredFields[inputName]; ok { return false } return true }, "genXSRF": func() string { return xsrftoken.Generate(serverKey, "user", "wizardSave") }, } body := ` <form id="WizardForm" method="POST" enctype="multipart/form-data"> <table> {{range $k,$v := .}}{{if showField $k}}<tr><td>{{printf "%v" $k}}</td><td><input type="text" size="30" name ="{{printf "%v" $k}}" value="{{printWizard $v}}" ></td></tr>{{end}}{{end}} </table> <input type="hidden" name="token" value="{{genXSRF}}"> <input type="submit" form="WizardForm" value="Save"> (Will restart server.)</form>` if hasChanged { body += `<p> Configuration succesfully rewritten </p>` } tmpl, err := template.New("wizard").Funcs(funcMap).Parse(topWizard + body + bottomWizard) if err != nil { httputil.ServeError(rw, req, err) return } err = tmpl.Execute(rw, config) if err != nil { httputil.ServeError(rw, req, err) return } }
func (c *reindexdpCmd) RunCommand(args []string) error { var path string switch { case len(args) == 0: cfg, err := serverconfig.Load(osutil.UserServerConfigPath()) if err != nil { return err } prefixes := cfg.RequiredObject("prefixes") if err := cfg.Validate(); err != nil { return fmt.Errorf("configuration error in root object's keys: %v", err) } for prefix, vei := range prefixes { pmap, ok := vei.(map[string]interface{}) if !ok { log.Printf("prefix %q value is a %T, not an object", prefix, vei) continue } pconf := jsonconfig.Obj(pmap) handlerType := pconf.RequiredString("handler") handlerArgs := pconf.OptionalObject("handlerArgs") // no pconf.Validate, as this is a recover tool if handlerType != "storage-diskpacked" { continue } if handlerArgs == nil { log.Printf("no handlerArgs for %q", prefix) continue } aconf := jsonconfig.Obj(handlerArgs) path = aconf.RequiredString("path") // no aconv.Validate, as this is a recover tool if path != "" { break } } case len(args) == 1: path = args[0] default: return errors.New("More than 1 argument not allowed") } if path == "" { return errors.New("no path is given/found") } return diskpacked.Reindex(path, c.overwrite) }
// serverKeyId returns the public gpg key id ("identity" field) // from the user's server config , if any. // It returns the empty string otherwise. func serverKeyId() string { serverConfigFile := osutil.UserServerConfigPath() if _, err := wkfs.Stat(serverConfigFile); err != nil { if os.IsNotExist(err) { return "" } log.Fatalf("Could not stat %v: %v", serverConfigFile, err) } obj, err := jsonconfig.ReadFile(serverConfigFile) if err != nil { return "" } keyId, ok := obj["identity"].(string) if !ok { return "" } return keyId }
func (c *dumpconfigCmd) RunCommand(args []string) error { var file string switch { case len(args) == 0: file = osutil.UserServerConfigPath() case len(args) == 1: file = args[0] default: return errors.New("More than 1 argument not allowed") } cfg, err := serverconfig.Load(file) if err != nil { return err } ll, err := json.MarshalIndent(cfg.Obj, "", " ") if err != nil { return err } _, err = os.Stdout.Write(ll) return err }
// loadConfig returns the server's parsed config file, locating it using the provided arg. // // The arg may be of the form: // - empty, to mean automatic (will write a default high-level config if // no cloud config is available) // - a filepath absolute or relative to the user's configuration directory, // - a URL func loadConfig(arg string) (conf *serverinit.Config, isNewConfig bool, err error) { if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") { contents, err := slurpURL(arg, 256<<10) if err != nil { return nil, false, err } conf, err = serverinit.Load(contents) return conf, false, err } var absPath string switch { case arg == "": absPath = osutil.UserServerConfigPath() _, err = wkfs.Stat(absPath) if err != nil { if !os.IsNotExist(err) { return } conf, err = serverinit.DefaultEnvConfig() if err != nil || conf != nil { return } err = wkfs.MkdirAll(osutil.CamliConfigDir(), 0700) if err != nil { return } log.Printf("Generating template config file %s", absPath) if err = serverinit.WriteDefaultConfigFile(absPath, sqlite.CompiledIn()); err == nil { isNewConfig = true } } case filepath.IsAbs(arg): absPath = arg default: absPath = filepath.Join(osutil.CamliConfigDir(), arg) } conf, err = serverinit.LoadFile(absPath) return }
// findConfigFile returns the absolute path of the user's // config file. // The provided file may be absolute or relative // to the user's configuration directory. // If file is empty, a default high-level config is written // for the user. func findConfigFile(file string) (absPath string, err error) { switch { case file == "": absPath = osutil.UserServerConfigPath() _, err = os.Stat(absPath) if os.IsNotExist(err) { err = os.MkdirAll(osutil.CamliConfigDir(), 0700) if err != nil { return } log.Printf("Generating template config file %s", absPath) err = newDefaultConfigFile(absPath) } return case filepath.IsAbs(file): absPath = file default: absPath = filepath.Join(osutil.CamliConfigDir(), file) } _, err = os.Stat(absPath) return }
func sendWizard(rw http.ResponseWriter, req *http.Request, hasChanged bool) { config, err := jsonconfig.ReadFile(osutil.UserServerConfigPath()) if err != nil { httputil.ServerError(rw, req, err) return } err = flattenPublish(config) if err != nil { httputil.ServerError(rw, req, err) return } funcMap := template.FuncMap{ "printWizard": printWizard, "inputIsGallery": func(inputName string) bool { return inputName == "gallery" }, } body := `<form id="WizardForm" action="setup" method="post" enctype="multipart/form-data">` body += `{{range $k,$v := .}}{{printf "%v" $k}} <input type="text" size="30" name ="{{printf "%v" $k}}" value="{{printWizard $v}}" {{if inputIsGallery $k}}placeholder="/pics/,sha1-xxxx,pics.css"{{end}}><br />{{end}}` body += `<input type="submit" form="WizardForm" value="Save"></form>` if hasChanged { body += `<p> Configuration succesfully rewritten </p>` } tmpl, err := template.New("wizard").Funcs(funcMap).Parse(topWizard + body + bottomWizard) if err != nil { httputil.ServerError(rw, req, err) return } err = tmpl.Execute(rw, config) if err != nil { httputil.ServerError(rw, req, err) return } }
// findConfigFile returns the absolute path of the user's // config file. // The provided file may be absolute or relative // to the user's configuration directory. // If file is empty, a default high-level config is written // for the user. func findConfigFile(file string) (absPath string, isNewConfig bool, err error) { switch { case file == "": absPath = osutil.UserServerConfigPath() _, err = os.Stat(absPath) if os.IsNotExist(err) { err = os.MkdirAll(osutil.CamliConfigDir(), 0700) if err != nil { return } log.Printf("Generating template config file %s", absPath) if err = serverinit.WriteDefaultConfigFile(absPath, sqlite.CompiledIn()); err == nil { isNewConfig = true } } return case filepath.IsAbs(file): absPath = file default: absPath = filepath.Join(osutil.CamliConfigDir(), file) } _, err = os.Stat(absPath) return }
func handleSetupChange(rw http.ResponseWriter, req *http.Request) { hilevelConf, err := jsonconfig.ReadFile(osutil.UserServerConfigPath()) if err != nil { httputil.ServerError(rw, req, err) return } hasChanged := false var el interface{} publish := jsonconfig.Obj{} for k, v := range req.Form { if _, ok := hilevelConf[k]; !ok { if k != "gallery" && k != "blog" { continue } } switch k { case "https": b, err := strconv.ParseBool(v[0]) if err != nil { httputil.ServerError(rw, req, fmt.Errorf("https field expects a boolean value")) } el = b case "replicateTo": // TODO(mpl): figure out why it is always seen as different from the conf el = []interface{}{} if len(v[0]) > 0 { els := []string{} vals := strings.Split(v[0], ",") els = append(els, vals...) el = els } // TODO(mpl): "handler,rootPermanode[,style]" for each published entity for now. // we will need something more readable later probably case "gallery", "blog": if len(v[0]) > 0 { pub := strings.Split(v[0], ",") if len(pub) < 2 || len(pub) > 3 { // no need to fail loudly for now as we'll probably change this format continue } handler := jsonconfig.Obj{} handler["template"] = k handler["rootPermanode"] = pub[1] if len(pub) > 2 { handler["style"] = pub[2] } publish[pub[0]] = handler } continue default: el = v[0] } if reflect.DeepEqual(hilevelConf[k], el) { continue } hasChanged = true hilevelConf[k] = el } // "publish" wasn't checked yet if !reflect.DeepEqual(hilevelConf["publish"], publish) { hilevelConf["publish"] = publish hasChanged = true } if hasChanged { err = rewriteConfig(&hilevelConf, osutil.UserServerConfigPath()) if err != nil { httputil.ServerError(rw, req, err) return } } sendWizard(rw, req, hasChanged) return }
// lazy config parsing when there's a known client already. // The client c may be nil. func (c *Client) parseConfig() { if android.OnAndroid() { panic("parseConfig should never have been called on Android") } if configDisabled { panic("parseConfig should never have been called with CAMLI_DISABLE_CLIENT_CONFIG_FILE set") } configPath := osutil.UserClientConfigPath() if _, err := wkfs.Stat(configPath); os.IsNotExist(err) { if c != nil && c.isSharePrefix { return } errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'camput init' to generate it.", configPath) if keyId := serverKeyId(); keyId != "" { hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'camput init -gpgkey %v'", keyId, osutil.UserServerConfigPath(), keyId) errMsg += hint } log.Fatal(errMsg) } // TODO: instead of using jsonconfig, we could read the file, and unmarshall into the structs that we now have in pkg/types/clientconfig. But we'll have to add the old fields (before the name changes, and before the multi-servers change) to the structs as well for our gracefull conversion/error messages to work. conf, err := osutil.NewJSONConfigParser().ReadFile(configPath) if err != nil { log.Fatal(err.Error()) } cfg := jsonconfig.Obj(conf) if singleServerAuth := cfg.OptionalString("auth", ""); singleServerAuth != "" { newConf, err := convertToMultiServers(cfg) if err != nil { log.Print(err) } else { cfg = newConf } } config = &clientconfig.Config{ Identity: cfg.OptionalString("identity", ""), IdentitySecretRing: cfg.OptionalString("identitySecretRing", ""), IgnoredFiles: cfg.OptionalList("ignoredFiles"), } serversList := make(map[string]*clientconfig.Server) servers := cfg.OptionalObject("servers") for alias, vei := range servers { // An alias should never be confused with a host name, // so we forbid anything looking like one. if isURLOrHostPort(alias) { log.Fatalf("Server alias %q looks like a hostname; \".\" or \";\" are not allowed.", alias) } serverMap, ok := vei.(map[string]interface{}) if !ok { log.Fatalf("entry %q in servers section is a %T, want an object", alias, vei) } serverConf := jsonconfig.Obj(serverMap) server := &clientconfig.Server{ Server: cleanServer(serverConf.OptionalString("server", "")), Auth: serverConf.OptionalString("auth", ""), IsDefault: serverConf.OptionalBool("default", false), TrustedCerts: serverConf.OptionalList("trustedCerts"), } if err := serverConf.Validate(); err != nil { log.Fatalf("Error in servers section of config file for server %q: %v", alias, err) } serversList[alias] = server } config.Servers = serversList if err := cfg.Validate(); err != nil { printConfigChangeHelp(cfg) log.Fatalf("Error in config file: %v", err) } }
func parseConfig() { if android.OnAndroid() { return } configPath := osutil.UserClientConfigPath() if _, err := os.Stat(configPath); os.IsNotExist(err) { errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'camput init' to generate it.", configPath) if keyId := serverKeyId(); keyId != "" { hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'camput init -gpgkey %v'", keyId, osutil.UserServerConfigPath(), keyId) errMsg += hint } log.Fatal(errMsg) } conf, err := jsonconfig.ReadFile(configPath) if err != nil { log.Fatal(err.Error()) } cfg := jsonconfig.Obj(conf) config = &clientConfig{ auth: cfg.OptionalString("auth", ""), server: cfg.OptionalString("server", ""), identity: cfg.OptionalString("identity", ""), identitySecretRing: cfg.OptionalString("identitySecretRing", osutil.IdentitySecretRing()), trustedCerts: cfg.OptionalList("trustedCerts"), ignoredFiles: cfg.OptionalList("ignoredFiles"), } if err := cfg.Validate(); err != nil { printConfigChangeHelp(cfg) log.Fatalf("Error in config file: %v", err) } }
func parseConfig() { if android.OnAndroid() { return } configPath := osutil.UserClientConfigPath() if _, err := os.Stat(configPath); os.IsNotExist(err) { errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'camput init' to generate it.", configPath) if keyId := serverKeyId(); keyId != "" { hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'camput init -gpgkey %v'", keyId, osutil.UserServerConfigPath(), keyId) errMsg += hint } log.Fatal(errMsg) } var err error if config, err = jsonconfig.ReadFile(configPath); err != nil { log.Fatal(err.Error()) return } }
func (c *reindexdpCmd) RunCommand(args []string) error { var path string var indexConf jsonconfig.Obj switch len(args) { case 0: case 1: path = args[0] default: return errors.New("More than 1 argument not allowed") } cfg, err := serverinit.LoadFile(osutil.UserServerConfigPath()) if err != nil { return err } prefixes, ok := cfg.Obj["prefixes"].(map[string]interface{}) if !ok { return fmt.Errorf("No 'prefixes' object in low-level (or converted) config file %s", osutil.UserServerConfigPath()) } paths, confs := []string{}, []jsonconfig.Obj{} for prefix, vei := range prefixes { pmap, ok := vei.(map[string]interface{}) if !ok { log.Printf("prefix %q value is a %T, not an object", prefix, vei) continue } pconf := jsonconfig.Obj(pmap) handlerType := pconf.RequiredString("handler") handlerArgs := pconf.OptionalObject("handlerArgs") // no pconf.Validate, as this is a recover tool if handlerType != "storage-diskpacked" { continue } log.Printf("handlerArgs of %q: %v", prefix, handlerArgs) if handlerArgs == nil { log.Printf("no handlerArgs for %q", prefix) continue } aconf := jsonconfig.Obj(handlerArgs) apath := aconf.RequiredString("path") // no aconv.Validate, as this is a recover tool if apath == "" { log.Printf("path is missing for %q", prefix) continue } if path != "" && path != apath { continue } paths = append(paths, apath) confs = append(confs, aconf) } if len(paths) == 0 { return fmt.Errorf("Server config file %s doesn't specify a disk-packed storage handler.", osutil.UserServerConfigPath()) } if len(paths) > 1 { return fmt.Errorf("Ambiguity. Server config file %s d specify more than 1 disk-packed storage handler. Please specify one of: %v", osutil.UserServerConfigPath(), paths) } path = paths[0] if path == "" { return errors.New("no path is given/found") } // If no index is specified, the default will be used (as on the regular path). if mi := confs[0]["metaIndex"]; mi != nil { if mi, ok := mi.(map[string]interface{}); ok { indexConf = jsonconfig.Obj(mi) } } log.Printf("indexConf: %v", indexConf) return diskpacked.Reindex(path, c.overwrite, indexConf) }