pkg/types/serverconfig: json-tagged struct

Also changed the TLS/HTTPS config keys for consistency.

http://camlistore.org/issue/339

Change-Id: I704ec129f91b93ebb20bc1191816166a2f10692d
This commit is contained in:
mpl 2014-01-23 23:40:12 +01:00
parent a629124f44
commit 6707837806
13 changed files with 244 additions and 199 deletions

View File

@ -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",

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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",

View File

@ -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",

View File

@ -24,7 +24,7 @@
"rootName": "blogRoot",
"rootPermanode": [
"/sighelper/",
"sha1-xxxxx"
"sha1-2790ec1ec6fd44b9620b21155c8738aa08d4e3a0"
],
"scaledImage": {
"type": "kv",

View File

@ -9,7 +9,7 @@
"s3": "",
"publish": {
"/blog/": {
"rootPermanode": "sha1-xxxxx",
"rootPermanode": "sha1-2790ec1ec6fd44b9620b21155c8738aa08d4e3a0",
"goTemplate": "blog.html",
"style": "blog-purple.css"
}

View File

@ -74,7 +74,7 @@
"rootName": "picsRoot",
"rootPermanode": [
"/sighelper/",
"sha1-xxxxx"
"sha1-2790ec1ec6fd44b9620b21155c8738aa08d4e3a0"
],
"scaledImage": {
"type": "kv",

View File

@ -9,7 +9,7 @@
"s3": "",
"publish": {
"/pics/": {
"rootPermanode": "sha1-xxxxx",
"rootPermanode": "sha1-2790ec1ec6fd44b9620b21155c8738aa08d4e3a0",
"goTemplate": "gallery.html",
"js": "pics.js",
"style": "pics.css"

View File

@ -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"`
}

View File

@ -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()

View File

@ -27,8 +27,8 @@ web browser and restart the server.</p>
<li><b><code>https</code></b>: if "true", HTTPS is used
<ul>
<li><b><code>HTTPSCertFile</code></b>: if using https</li>
<li><b><code>HTTPSKeyFile</code></b>: if using https</li>
<li><b><code>httpsCert</code></b>: path to the HTTPS certificate file. This is the public file. It should include the concatenation of any required intermediate certs as well.</li>
<li><b><code>httpsKey</code></b>: path to the HTTPS private key file.</li>
</ul>
</li>