From 67078378068952e607ce60c15de3f3906af6c320 Mon Sep 17 00:00:00 2001 From: mpl Date: Thu, 23 Jan 2014 23:40:12 +0100 Subject: [PATCH] pkg/types/serverconfig: json-tagged struct Also changed the TLS/HTTPS config keys for consistency. http://camlistore.org/issue/339 Change-Id: I704ec129f91b93ebb20bc1191816166a2f10692d --- config/dev-server-config.json | 4 +- pkg/serverinit/genconfig.go | 213 ++++++------------ pkg/serverinit/serverconfig.go | 55 ++++- pkg/serverinit/serverconfig_test.go | 43 ++-- pkg/serverinit/testdata/tls-want.json | 4 +- pkg/serverinit/testdata/tls.json | 4 +- pkg/serverinit/testdata/with_blog-want.json | 2 +- pkg/serverinit/testdata/with_blog.json | 2 +- .../testdata/with_gallery-want.json | 2 +- pkg/serverinit/testdata/with_gallery.json | 2 +- pkg/types/serverconfig/config.go | 82 +++++++ server/camlistored/camlistored.go | 26 +-- website/content/docs/server-config | 4 +- 13 files changed, 244 insertions(+), 199 deletions(-) create mode 100644 pkg/types/serverconfig/config.go diff --git a/config/dev-server-config.json b/config/dev-server-config.json index 2eedc93ee..35037d60a 100644 --- a/config/dev-server-config.json +++ b/config/dev-server-config.json @@ -3,8 +3,8 @@ "baseURL": ["_env", "${CAMLI_BASEURL}"], "auth": ["_env", "${CAMLI_AUTH}"], "https": ["_env", "${CAMLI_TLS}", false], - "TLSCertFile": "config/selfgen_pem.crt", - "TLSKeyFile": "config/selfgen_pem.key", + "httpsCert": "config/selfgen_pem.crt", + "httpsKey": "config/selfgen_pem.key", "prefixes": { "/": { "handler": "root", diff --git a/pkg/serverinit/genconfig.go b/pkg/serverinit/genconfig.go index 0cf9f6dbb..0c2dbcbcc 100644 --- a/pkg/serverinit/genconfig.go +++ b/pkg/serverinit/genconfig.go @@ -28,6 +28,7 @@ import ( "camlistore.org/pkg/jsonconfig" "camlistore.org/pkg/jsonsign" "camlistore.org/pkg/osutil" + "camlistore.org/pkg/types/serverconfig" ) // various parameters derived from the high-level user config @@ -50,36 +51,16 @@ var ( ) func addPublishedConfig(prefixes jsonconfig.Obj, - published jsonconfig.Obj, + published map[string]*serverconfig.Publish, sourceRoot string) ([]interface{}, error) { pubPrefixes := []interface{}{} for k, v := range published { - p, ok := v.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("Wrong type for %s; was expecting map[string]interface{}, got %T", k, v) - } rootName := strings.Replace(k, "/", "", -1) + "Root" - rootPermanode, goTemplate, style, js := "", "", "", "" - for pk, pv := range p { - val, ok := pv.(string) - if !ok { - return nil, fmt.Errorf("Was expecting type string for %s, got %T", pk, pv) - } - switch pk { - case "rootPermanode": - rootPermanode = val - case "goTemplate": - goTemplate = val - case "style": - style = val - case "js": - js = val - default: - return nil, fmt.Errorf("Unexpected key %q in config for %s", pk, k) - } + if !v.Root.Valid() { + return nil, fmt.Errorf("Invalid or missing \"rootPermanode\" key in configuration for %s.", k) } - if rootPermanode == "" || goTemplate == "" { - return nil, fmt.Errorf("Missing key in configuration for %s, need \"rootPermanode\" and \"goTemplate\"", k) + if v.GoTemplate == "" { + return nil, fmt.Errorf("Missing \"goTemplate\" key in configuration for %s.", k) } ob := map[string]interface{}{} ob["handler"] = "publish" @@ -88,17 +69,17 @@ func addPublishedConfig(prefixes jsonconfig.Obj, "blobRoot": "/bs-and-maybe-also-index/", "searchRoot": "/my-search/", "cache": "/cache/", - "rootPermanode": []interface{}{"/sighelper/", rootPermanode}, + "rootPermanode": []interface{}{"/sighelper/", v.Root.String()}, } if sourceRoot != "" { handlerArgs["sourceRoot"] = sourceRoot } - handlerArgs["goTemplate"] = goTemplate - if style != "" { - handlerArgs["css"] = []interface{}{style} + handlerArgs["goTemplate"] = v.GoTemplate + if v.Style != "" { + handlerArgs["css"] = []interface{}{v.Style} } - if js != "" { - handlerArgs["js"] = []interface{}{js} + if v.Javascript != "" { + handlerArgs["js"] = []interface{}{v.Javascript} } // TODO(mpl): we'll probably want to use osutil.CacheDir() if thumbnails.kv // contains private info? same for some of the other "camli-cache" ones? @@ -529,75 +510,25 @@ func genLowLevelPrefixes(params *configPrefixesParams, ownerName string) (m json } // genLowLevelConfig returns a low-level config from a high-level config. -func genLowLevelConfig(conf *Config) (lowLevelConf *Config, err error) { - var ( - baseURL = conf.OptionalString("baseURL", "") - listen = conf.OptionalString("listen", "") - auth = conf.RequiredString("auth") - keyId = conf.RequiredString("identity") - secretRing = conf.RequiredString("identitySecretRing") - tlsOn = conf.OptionalBool("https", false) - tlsCert = conf.OptionalString("HTTPSCertFile", "") - tlsKey = conf.OptionalString("HTTPSKeyFile", "") - - // Blob storage options - blobPath = conf.OptionalString("blobPath", "") - packBlobs = conf.OptionalBool("packBlobs", false) // use diskpacked instead of the default filestorage - s3 = conf.OptionalString("s3", "") // "access_key_id:secret_access_key:bucket[:hostname]" - googlecloudstorage = conf.OptionalString("googlecloudstorage", "") // "clientId:clientSecret:refreshToken:bucket" - googledrive = conf.OptionalString("googledrive", "") // "clientId:clientSecret:refreshToken:parentId" - // Enable the share handler. If true, and shareHandlerPath is empty, - // then shareHandlerPath defaults to "/share/". - shareHandler = conf.OptionalBool("shareHandler", false) - // URL prefix for the share handler. If set, overrides shareHandler. - shareHandlerPath = conf.OptionalString("shareHandlerPath", "") - - // Index options - memoryIndex = conf.OptionalBool("memoryIndex", true) // copy disk-based index to memory on start-up - runIndex = conf.OptionalBool("runIndex", true) // if false: no search, no UI, etc. - dbname = conf.OptionalString("dbname", "") // for mysql, postgres, mongo - mysql = conf.OptionalString("mysql", "") - postgres = conf.OptionalString("postgres", "") - mongo = conf.OptionalString("mongo", "") - sqliteFile = conf.OptionalString("sqlite", "") - kvFile = conf.OptionalString("kvIndexFile", "") - - // Importer options - flickr = conf.OptionalString("flickr", "") - - _ = conf.OptionalList("replicateTo") - publish = conf.OptionalObject("publish") - // alternative source tree, to override the embedded ui and/or closure resources. - // If non empty, the ui files will be expected at - // sourceRoot + "/server/camlistored/ui" and the closure library at - // sourceRoot + "/third_party/closure/lib" - // Also used by the publish handler. - sourceRoot = conf.OptionalString("sourceRoot", "") - - ownerName = conf.OptionalString("ownerName", "") - ) - if err := conf.Validate(); err != nil { - return nil, err - } - +func genLowLevelConfig(conf *serverconfig.Config) (lowLevelConf *Config, err error) { obj := jsonconfig.Obj{} - if tlsOn { - if (tlsCert != "") != (tlsKey != "") { - return nil, errors.New("Must set both TLSCertFile and TLSKeyFile (or neither to generate a self-signed cert)") + if conf.HTTPS { + if (conf.HTTPSCert != "") != (conf.HTTPSKey != "") { + return nil, errors.New("Must set both httpsCert and httpsKey (or neither to generate a self-signed cert)") } - if tlsCert != "" { - obj["TLSCertFile"] = tlsCert - obj["TLSKeyFile"] = tlsKey + if conf.HTTPSCert != "" { + obj["httpsCert"] = conf.HTTPSCert + obj["httpsKey"] = conf.HTTPSKey } else { - obj["TLSCertFile"] = osutil.DefaultTLSCert() - obj["TLSKeyFile"] = osutil.DefaultTLSKey() + obj["httpsCert"] = osutil.DefaultTLSCert() + obj["httpsKey"] = osutil.DefaultTLSKey() } } - if baseURL != "" { - u, err := url.Parse(baseURL) + if conf.BaseURL != "" { + u, err := url.Parse(conf.BaseURL) if err != nil { - return nil, fmt.Errorf("Error parsing baseURL %q as a URL: %v", baseURL, err) + return nil, fmt.Errorf("Error parsing baseURL %q as a URL: %v", conf.BaseURL, err) } if u.Path != "" && u.Path != "/" { return nil, fmt.Errorf("baseURL can't have a path, only a scheme, host, and optional port.") @@ -605,23 +536,24 @@ func genLowLevelConfig(conf *Config) (lowLevelConf *Config, err error) { u.Path = "" obj["baseURL"] = u.String() } - if listen != "" { - obj["listen"] = listen + if conf.Listen != "" { + obj["listen"] = conf.Listen } - obj["https"] = tlsOn - obj["auth"] = auth + obj["https"] = conf.HTTPS + obj["auth"] = conf.Auth username := "" - if dbname == "" { + if conf.DBName == "" { username = osutil.Username() if username == "" { return nil, fmt.Errorf("USER (USERNAME on windows) env var not set; needed to define dbname") } - dbname = "camli" + username + conf.DBName = "camli" + username } var indexerPath string - numIndexers := numSet(mongo, mysql, postgres, sqliteFile, kvFile) + numIndexers := numSet(conf.Mongo, conf.MySQL, conf.PostgreSQL, conf.SQLite, conf.KVFile) + runIndex := conf.RunIndex.Get() switch { case runIndex && numIndexers == 0: return nil, fmt.Errorf("Unless runIndex is set to false, you must specify an index option (kvIndexFile, mongo, mysql, postgres, sqlite).") @@ -629,19 +561,19 @@ func genLowLevelConfig(conf *Config) (lowLevelConf *Config, err error) { return nil, fmt.Errorf("With runIndex set true, you can only pick exactly one indexer (mongo, mysql, postgres, sqlite).") case !runIndex && numIndexers != 0: return nil, fmt.Errorf("With runIndex disabled, you can't specify any of mongo, mysql, postgres, sqlite.") - case mysql != "": + case conf.MySQL != "": indexerPath = "/index-mysql/" - case postgres != "": + case conf.PostgreSQL != "": indexerPath = "/index-postgres/" - case mongo != "": + case conf.Mongo != "": indexerPath = "/index-mongo/" - case sqliteFile != "": + case conf.SQLite != "": indexerPath = "/index-sqlite/" - case kvFile != "": + case conf.KVFile != "": indexerPath = "/index-kv/" } - entity, err := jsonsign.EntityFromSecring(keyId, secretRing) + entity, err := jsonsign.EntityFromSecring(conf.Identity, conf.IdentitySecretRing) if err != nil { return nil, err } @@ -650,33 +582,33 @@ func genLowLevelConfig(conf *Config) (lowLevelConf *Config, err error) { return nil, err } - nolocaldisk := blobPath == "" + nolocaldisk := conf.BlobPath == "" if nolocaldisk { - if s3 == "" && googlecloudstorage == "" { + if conf.S3 == "" && conf.GoogleCloudStorage == "" { return nil, errors.New("You need at least one of blobPath (for localdisk) or s3 or googlecloudstorage configured for a blobserver.") } - if s3 != "" && googlecloudstorage != "" { + if conf.S3 != "" && conf.GoogleCloudStorage != "" { return nil, errors.New("Using S3 as a primary storage and Google Cloud Storage as a mirror is not supported for now.") } } - if shareHandler && shareHandlerPath == "" { - shareHandlerPath = "/share/" + if conf.ShareHandler && conf.ShareHandlerPath == "" { + conf.ShareHandlerPath = "/share/" } prefixesParams := &configPrefixesParams{ - secretRing: secretRing, - keyId: keyId, + secretRing: conf.IdentitySecretRing, + keyId: conf.Identity, indexerPath: indexerPath, - blobPath: blobPath, - packBlobs: packBlobs, + blobPath: conf.BlobPath, + packBlobs: conf.PackBlobs, searchOwner: blob.SHA1FromString(armoredPublicKey), - shareHandlerPath: shareHandlerPath, - flickr: flickr, - memoryIndex: memoryIndex, + shareHandlerPath: conf.ShareHandlerPath, + flickr: conf.Flickr, + memoryIndex: conf.MemoryIndex.Get(), } - prefixes := genLowLevelPrefixes(prefixesParams, ownerName) + prefixes := genLowLevelPrefixes(prefixesParams, conf.OwnerName) var cacheDir string if nolocaldisk { // Whether camlistored is run from EC2 or not, we use @@ -685,7 +617,7 @@ func genLowLevelConfig(conf *Config) (lowLevelConf *Config, err error) { // See http://code.google.com/p/camlistore/issues/detail?id=85 cacheDir = filepath.Join(tempDir(), "camli-cache") } else { - cacheDir = filepath.Join(blobPath, "cache") + cacheDir = filepath.Join(conf.BlobPath, "cache") } if !noMkdir { if err := os.MkdirAll(cacheDir, 0700); err != nil { @@ -694,47 +626,47 @@ func genLowLevelConfig(conf *Config) (lowLevelConf *Config, err error) { } published := []interface{}{} - if len(publish) > 0 { + if len(conf.Publish) > 0 { if !runIndex { return nil, fmt.Errorf("publishing requires an index") } - published, err = addPublishedConfig(prefixes, publish, sourceRoot) + published, err = addPublishedConfig(prefixes, conf.Publish, conf.SourceRoot) if err != nil { return nil, fmt.Errorf("Could not generate config for published: %v", err) } } if runIndex { - addUIConfig(prefixesParams, prefixes, "/ui/", published, sourceRoot) + addUIConfig(prefixesParams, prefixes, "/ui/", published, conf.SourceRoot) } - if mysql != "" { - addMySQLConfig(prefixes, dbname, mysql) + if conf.MySQL != "" { + addMySQLConfig(prefixes, conf.DBName, conf.MySQL) } - if postgres != "" { - addPostgresConfig(prefixes, dbname, postgres) + if conf.PostgreSQL != "" { + addPostgresConfig(prefixes, conf.DBName, conf.PostgreSQL) } - if mongo != "" { - addMongoConfig(prefixes, dbname, mongo) + if conf.Mongo != "" { + addMongoConfig(prefixes, conf.DBName, conf.Mongo) } - if sqliteFile != "" { - addSQLiteConfig(prefixes, sqliteFile) + if conf.SQLite != "" { + addSQLiteConfig(prefixes, conf.SQLite) } - if kvFile != "" { - addKVConfig(prefixes, kvFile) + if conf.KVFile != "" { + addKVConfig(prefixes, conf.KVFile) } - if s3 != "" { - if err := addS3Config(prefixesParams, prefixes, s3); err != nil { + if conf.S3 != "" { + if err := addS3Config(prefixesParams, prefixes, conf.S3); err != nil { return nil, err } } - if googledrive != "" { - if err := addGoogleDriveConfig(prefixes, googledrive); err != nil { + if conf.GoogleDrive != "" { + if err := addGoogleDriveConfig(prefixes, conf.GoogleDrive); err != nil { return nil, err } } - if googlecloudstorage != "" { - if err := addGoogleCloudStorageConfig(prefixes, googlecloudstorage); err != nil { + if conf.GoogleCloudStorage != "" { + if err := addGoogleCloudStorageConfig(prefixes, conf.GoogleCloudStorage); err != nil { return nil, err } } @@ -742,8 +674,7 @@ func genLowLevelConfig(conf *Config) (lowLevelConf *Config, err error) { obj["prefixes"] = (map[string]interface{})(prefixes) lowLevelConf = &Config{ - Obj: obj, - configPath: conf.configPath, + Obj: obj, } return lowLevelConf, nil } diff --git a/pkg/serverinit/serverconfig.go b/pkg/serverinit/serverconfig.go index 1bb923cd5..b4fb3aeb2 100644 --- a/pkg/serverinit/serverconfig.go +++ b/pkg/serverinit/serverconfig.go @@ -25,10 +25,12 @@ import ( "expvar" "fmt" "io" + "io/ioutil" "log" "net/http" "net/http/pprof" "os" + "path/filepath" "runtime" rpprof "runtime/pprof" "strconv" @@ -41,6 +43,7 @@ import ( "camlistore.org/pkg/importer" "camlistore.org/pkg/index" "camlistore.org/pkg/jsonconfig" + "camlistore.org/pkg/types/serverconfig" ) const camliPrefix = "/camli/" @@ -371,6 +374,16 @@ type Config struct { configPath string // Filesystem path } +// detectConfigChange returns an informative error if conf contains obsolete keys. +func detectConfigChange(conf jsonconfig.Obj) error { + oldHTTPSKey, oldHTTPSCert := conf.OptionalString("HTTPSKeyFile", ""), conf.OptionalString("HTTPSCertFile", "") + if oldHTTPSKey != "" || oldHTTPSCert != "" { + return fmt.Errorf("Config keys %q and %q have respectively been renamed to %q and %q, please fix your server config.", + "HTTPSKeyFile", "HTTPSCertFile", "httpsKey", "httpsCert") + } + return nil +} + // Load returns a low-level "handler config" from the provided filename. // If the config file doesn't contain a top-level JSON key of "handlerConfig" // with boolean value true, the configuration is assumed to be a high-level @@ -385,19 +398,39 @@ func Load(filename string) (*Config, error) { configPath: filename, } - if lowLevel := obj.OptionalBool("handlerConfig", false); !lowLevel { - conf, err = genLowLevelConfig(conf) - if err != nil { - return nil, fmt.Errorf( - "Failed to transform user config file %q into internal handler configuration: %v", - filename, err) - } - if v, _ := strconv.ParseBool(os.Getenv("CAMLI_DEBUG_CONFIG")); v { - jsconf, _ := json.MarshalIndent(conf.Obj, "", " ") - log.Printf("From high-level config, generated low-level config: %s", jsconf) - } + if lowLevel := obj.OptionalBool("handlerConfig", false); lowLevel { + return conf, nil } + if err := detectConfigChange(obj); err != nil { + return nil, err + } + + absConfigPath, err := filepath.Abs(filename) + if err != nil { + return nil, fmt.Errorf("Failed to expand absolute path for %s: %v", filename, err) + } + b, err := ioutil.ReadFile(absConfigPath) + if err != nil { + return nil, fmt.Errorf("Could not read %s: %v", absConfigPath, err) + } + var hiLevelConf serverconfig.Config + if err := json.Unmarshal(b, &hiLevelConf); err != nil { + return nil, fmt.Errorf("Could not unmarshal %s into a serverconfig.Config: %v", absConfigPath, err) + } + + conf, err = genLowLevelConfig(&hiLevelConf) + if err != nil { + return nil, fmt.Errorf( + "Failed to transform user config file %q into internal handler configuration: %v", + filename, err) + } + if v, _ := strconv.ParseBool(os.Getenv("CAMLI_DEBUG_CONFIG")); v { + jsconf, _ := json.MarshalIndent(conf.Obj, "", " ") + log.Printf("From high-level config, generated low-level config: %s", jsconf) + } + conf.configPath = absConfigPath + return conf, nil } diff --git a/pkg/serverinit/serverconfig_test.go b/pkg/serverinit/serverconfig_test.go index d60849a35..51463c144 100644 --- a/pkg/serverinit/serverconfig_test.go +++ b/pkg/serverinit/serverconfig_test.go @@ -33,12 +33,13 @@ import ( "camlistore.org/pkg/jsonconfig" "camlistore.org/pkg/serverinit" "camlistore.org/pkg/test" + "camlistore.org/pkg/types/serverconfig" ) var updateGolden = flag.Bool("update_golden", false, "Update golden *.want files") const ( - // relativeRing points to a real secret ring, but serverconfig + // relativeRing points to a real secret ring, but serverinit // rewrites it to be an absolute path. We then canonicalize // it to secringPlaceholder in the golden files. relativeRing = "../jsonsign/testdata/test-secring.gpg" @@ -94,30 +95,33 @@ type namedReadSeeker struct { func (n namedReadSeeker) Name() string { return n.name } func (n namedReadSeeker) Close() error { return nil } +// configParser returns a custom jsonconfig ConfigParser whose reader rewrites "/path/to/secring" to the absolute path of the jsonconfig test-secring.gpg file. func configParser() *jsonconfig.ConfigParser { - // Make a custom jsonconfig ConfigParser whose reader rewrites "/path/to/secring" to the absolute - // path of the jsonconfig test-secring.gpg file. - secRing, err := filepath.Abs("../jsonsign/testdata/test-secring.gpg") - if err != nil { - panic(err) - } return &jsonconfig.ConfigParser{ Open: func(path string) (jsonconfig.File, error) { - slurpBytes, err := ioutil.ReadFile(path) + slurp, err := replaceRingPath(path) if err != nil { return nil, err } - slurp := strings.Replace(string(slurpBytes), secringPlaceholder, secRing, 1) - return namedReadSeeker{path, strings.NewReader(slurp)}, nil + return namedReadSeeker{path, bytes.NewReader(slurp)}, nil }, } } -func testConfig(name string, t *testing.T) { - obj, err := configParser().ReadFile(name) +// replaceRingPath returns the contents of the file at path with secringPlaceholder replaced with the absolute path of relativeRing. +func replaceRingPath(path string) ([]byte, error) { + secRing, err := filepath.Abs(relativeRing) if err != nil { - t.Fatal(err) + return nil, fmt.Errorf("Could not get absolute path of %v: %v", relativeRing, err) } + slurpBytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + return bytes.Replace(slurpBytes, []byte(secringPlaceholder), []byte(secRing), 1), nil +} + +func testConfig(name string, t *testing.T) { wantedError := func() error { slurp, err := ioutil.ReadFile(strings.Replace(name, ".json", ".err", 1)) if os.IsNotExist(err) { @@ -128,7 +132,16 @@ func testConfig(name string, t *testing.T) { } return errors.New(string(slurp)) } - lowLevelConf, err := serverinit.GenLowLevelConfig(&serverinit.Config{Obj: obj}) + b, err := replaceRingPath(name) + if err != nil { + t.Fatalf("Could not read %s: %v", name, err) + } + var hiLevelConf serverconfig.Config + if err := json.Unmarshal(b, &hiLevelConf); err != nil { + t.Fatalf("Could not unmarshal %s into a serverconfig.Config: %v", name, err) + } + + lowLevelConf, err := serverinit.GenLowLevelConfig(&hiLevelConf) if g, w := strings.TrimSpace(fmt.Sprint(err)), strings.TrimSpace(fmt.Sprint(wantedError())); g != w { t.Fatalf("test %s: got GenLowLevelConfig error %q; want %q", name, g, w) } @@ -161,7 +174,7 @@ func testConfig(name string, t *testing.T) { } func canonicalizeGolden(t *testing.T, v []byte) []byte { - localPath, err := filepath.Abs("../jsonsign/testdata/test-secring.gpg") + localPath, err := filepath.Abs(relativeRing) if err != nil { t.Fatal(err) } diff --git a/pkg/serverinit/testdata/tls-want.json b/pkg/serverinit/testdata/tls-want.json index 25ee3bf9c..dad44d07c 100644 --- a/pkg/serverinit/testdata/tls-want.json +++ b/pkg/serverinit/testdata/tls-want.json @@ -1,6 +1,6 @@ { - "TLSCertFile": "/tls.crt", - "TLSKeyFile": "/tls.key", + "httpsCert": "/tls.crt", + "httpsKey": "/tls.key", "auth": "userpass:camlistore:pass3179", "https": true, "listen": "1.2.3.4:443", diff --git a/pkg/serverinit/testdata/tls.json b/pkg/serverinit/testdata/tls.json index 8f840927a..6502687a4 100644 --- a/pkg/serverinit/testdata/tls.json +++ b/pkg/serverinit/testdata/tls.json @@ -1,8 +1,8 @@ { "listen": "1.2.3.4:443", "https": true, - "HTTPSCertFile": "/tls.crt", - "HTTPSKeyFile": "/tls.key", + "httpsCert": "/tls.crt", + "httpsKey": "/tls.key", "auth": "userpass:camlistore:pass3179", "blobPath": "/tmp/blobs", "identity": "26F5ABDA", diff --git a/pkg/serverinit/testdata/with_blog-want.json b/pkg/serverinit/testdata/with_blog-want.json index f45abd639..93782364f 100644 --- a/pkg/serverinit/testdata/with_blog-want.json +++ b/pkg/serverinit/testdata/with_blog-want.json @@ -24,7 +24,7 @@ "rootName": "blogRoot", "rootPermanode": [ "/sighelper/", - "sha1-xxxxx" + "sha1-2790ec1ec6fd44b9620b21155c8738aa08d4e3a0" ], "scaledImage": { "type": "kv", diff --git a/pkg/serverinit/testdata/with_blog.json b/pkg/serverinit/testdata/with_blog.json index cfd98200d..802189ce1 100644 --- a/pkg/serverinit/testdata/with_blog.json +++ b/pkg/serverinit/testdata/with_blog.json @@ -9,7 +9,7 @@ "s3": "", "publish": { "/blog/": { - "rootPermanode": "sha1-xxxxx", + "rootPermanode": "sha1-2790ec1ec6fd44b9620b21155c8738aa08d4e3a0", "goTemplate": "blog.html", "style": "blog-purple.css" } diff --git a/pkg/serverinit/testdata/with_gallery-want.json b/pkg/serverinit/testdata/with_gallery-want.json index 126deb5c9..5bfd3eb8e 100644 --- a/pkg/serverinit/testdata/with_gallery-want.json +++ b/pkg/serverinit/testdata/with_gallery-want.json @@ -74,7 +74,7 @@ "rootName": "picsRoot", "rootPermanode": [ "/sighelper/", - "sha1-xxxxx" + "sha1-2790ec1ec6fd44b9620b21155c8738aa08d4e3a0" ], "scaledImage": { "type": "kv", diff --git a/pkg/serverinit/testdata/with_gallery.json b/pkg/serverinit/testdata/with_gallery.json index 2a9747f08..0fac43266 100644 --- a/pkg/serverinit/testdata/with_gallery.json +++ b/pkg/serverinit/testdata/with_gallery.json @@ -9,7 +9,7 @@ "s3": "", "publish": { "/pics/": { - "rootPermanode": "sha1-xxxxx", + "rootPermanode": "sha1-2790ec1ec6fd44b9620b21155c8738aa08d4e3a0", "goTemplate": "gallery.html", "js": "pics.js", "style": "pics.css" diff --git a/pkg/types/serverconfig/config.go b/pkg/types/serverconfig/config.go new file mode 100644 index 000000000..dcaff3c53 --- /dev/null +++ b/pkg/types/serverconfig/config.go @@ -0,0 +1,82 @@ +/* +Copyright 2014 The Camlistore Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package serverconfig provides types related to the server configuration file. +package serverconfig + +import ( + "camlistore.org/pkg/blob" + "camlistore.org/pkg/types" +) + +// Config holds the values from the JSON (high-level) server config file that is exposed to users (and is by default at osutil.UserServerConfigPath). From this simpler configuration, a complete, low-level one, is generated by serverinit.genLowLevelConfig, and used to configure the various Camlistore components. +type Config struct { + Auth string `json:"auth"` // auth scheme and values (ex: userpass:foo:bar). + BaseURL string `json:"baseURL,omitempty"` // Base URL the server advertizes. For when behind a proxy. + Listen string `json:"listen"` // address (of the form host|ip:port) on which the server will listen on. + Identity string `json:"identity"` // GPG identity. + IdentitySecretRing string `json:"identitySecretRing"` // path to the secret ring file. + // alternative source tree, to override the embedded ui and/or closure resources. + // If non empty, the ui files will be expected at + // sourceRoot + "/server/camlistored/ui" and the closure library at + // sourceRoot + "/third_party/closure/lib" + // Also used by the publish handler. + SourceRoot string `json:"sourceRoot,omitempty"` + OwnerName string `json:"ownerName,omitempty"` + + // Blob storage. + BlobPath string `json:"blobPath,omitempty"` // path to the directory containing the blobs. + PackBlobs bool `json:"packBlobs,omitempty"` // use diskpacked instead of the default filestorage. + S3 string `json:"s3,omitempty"` // Amazon S3 credentials: access_key_id:secret_access_key:bucket[:hostname]. + GoogleCloudStorage string `json:"googlecloudstorage,omitempty"` // Google Cloud credentials: clientId:clientSecret:refreshToken:bucket. + GoogleDrive string `json:"googledrive,omitempty"` // Google Drive credentials: clientId:clientSecret:refreshToken:parentId. + ShareHandler bool `json:"shareHandler,omitempty"` // enable the share handler. If true, and shareHandlerPath is empty then shareHandlerPath will default to "/share/" when generating the low-level config. + ShareHandlerPath string `json:"shareHandlerPath,omitempty"` // URL prefix for the share handler. If set, overrides shareHandler. + + // HTTPS. + HTTPS bool `json:"https,omitempty"` // enable HTTPS. + HTTPSCert string `json:"httpsCert,omitempty"` // path to the HTTPS certificate file. + HTTPSKey string `json:"httpsKey,omitempty"` // path to the HTTPS key file. + + // Index. + MemoryIndex types.InvertedBool `json:"memoryIndex,omitempty"` // copy disk-based index to memory on start-up. + RunIndex types.InvertedBool `json:"runIndex,omitempty"` // if logically false: no search, no UI, etc. + DBName string `json:"dbname,omitempty"` // name of the database for mysql, postgres, mongo. + KVFile string `json:"kvIndexFile,omitempty"` // path to the kv file, for indexing with github.com/cznic/kv. + MySQL string `json:"mysql,omitempty"` // MySQL credentials (username@host:password), for indexing with MySQL. + Mongo string `json:"mongo,omitempty"` // MongoDB credentials ([username:password@]host), for indexing with MongoDB. + PostgreSQL string `json:"postgres,omitempty"` // PostgreSQL credentials (username@host:password), for indexing with PostgreSQL. + SQLite string `json:"sqlite,omitempty"` // path to the SQLite file, for indexing with SQLite. + + ReplicateTo []interface{} `json:"replicateTo,omitempty"` // NOOP for now. + // Publish maps a URL prefix path used as a root for published paths (a.k.a. a camliRoot path), to the configuration of the publish handler that serves all the published paths under this root. + Publish map[string]*Publish `json:"publish,omitempty"` + + // TODO(mpl): map of importers instead? + Flickr string `json:"flickr,omitempty"` // flicker importer. +} + +// Publish holds the server configuration values specific to publishing, i.e. to a publish handler. +type Publish struct { + // Root is the permanode used as the root for all the paths served by this publish handler. The camliRoot value that is the root path for this handler is a property of this permanode. + Root blob.Ref `json:"rootPermanode"` + // GoTemplate is the name of the Go template file used by this publish handler to represent the data. This file should live in server/camlistored/ui/. + GoTemplate string `json:"goTemplate"` + // Javascript is the name of an optional javascript file used for additional features. This file should live in server/camlistored/ui/. + Javascript string `json:"js,omitempty"` + // Style is the name of an optional css file. This file should live in server/camlistored/ui/. + Style string `json:"style,omitempty"` +} diff --git a/server/camlistored/camlistored.go b/server/camlistored/camlistored.go index 5b232d4c5..4ffa74ef9 100644 --- a/server/camlistored/camlistored.go +++ b/server/camlistored/camlistored.go @@ -46,6 +46,7 @@ import ( "camlistore.org/pkg/misc" "camlistore.org/pkg/osutil" "camlistore.org/pkg/serverinit" + "camlistore.org/pkg/types/serverconfig" "camlistore.org/pkg/webserver" // Storage options: @@ -223,27 +224,12 @@ func findConfigFile(file string) (absPath string, isNewConfig bool, err error) { return } -type defaultConfigFile struct { - Listen string `json:"listen"` - HTTPS bool `json:"https"` - Auth string `json:"auth"` - Identity string `json:"identity"` - IdentitySecretRing string `json:"identitySecretRing"` - KVFile string `json:"kvIndexFile"` - BlobPath string `json:"blobPath"` - MySQL string `json:"mysql"` - Mongo string `json:"mongo"` - Postgres string `json:"postgres"` - SQLite string `json:"sqlite"` - S3 string `json:"s3"` - ReplicateTo []interface{} `json:"replicateTo"` - Publish struct{} `json:"publish"` -} - var defaultListenAddr = ":3179" +// TODO(mpl): move this func to pkg/types/serverconfig as well. + func newDefaultConfigFile(path string) error { - conf := defaultConfigFile{ + conf := serverconfig.Config{ Listen: defaultListenAddr, HTTPS: false, Auth: "localhost", @@ -323,12 +309,12 @@ func initSQLiteDB(path string) error { } func setupTLS(ws *webserver.Server, config *serverinit.Config, listen string) { - cert, key := config.OptionalString("TLSCertFile", ""), config.OptionalString("TLSKeyFile", "") + cert, key := config.OptionalString("httpsCert", ""), config.OptionalString("httpsKey", "") if !config.OptionalBool("https", true) { return } if (cert != "") != (key != "") { - exitf("TLSCertFile and TLSKeyFile must both be either present or absent") + exitf("httpsCert and httpsKey must both be either present or absent") } defCert := osutil.DefaultTLSCert() diff --git a/website/content/docs/server-config b/website/content/docs/server-config index ced89271f..f51444b07 100644 --- a/website/content/docs/server-config +++ b/website/content/docs/server-config @@ -27,8 +27,8 @@ web browser and restart the server.

  • https: if "true", HTTPS is used